如何用Java編寫一個簡單的服務器和客戶機


今天我要向大家介紹的是自己編寫的一個比較簡單的服務器和客戶機程序,注意一下哦,比較簡單。好了,閑話休提,砸門直入主題。

        小編先從客戶機和服務器的模型開始講解。簡單來說,我們實現的這種模型呢,我們每一個用戶稱為一個客戶機,用戶之間的通信之間需要一個中轉,所有客戶機的通信都依托於這個中轉,很明顯,這個中轉,就是砸門的服務器了。整個模型比較簡單明了,那么,接下來我們就直接進入實現階段。

        我們從實現服務器開始。Java提供了這樣的一個類,ServerSocket。我們通過實例化它的方式來創建一個服務器。注意一下,創建服務器需要一個參數port。port指的是端口號。我們知道一台計算機上運行着許多程序,那么,對於另一台計算機來說,它想要連接到這台服務器的這個程序,該如何區分呢?首先當然是根據IP連接到這台計算機,那么,然后呢?對的,你沒有想錯,就是通過我們的port端口號了。正常情況下,計算機會自動我們創建的程序分配一個port,但是,因為我們創建的是提供給別的計算機連接的服務器,所以我們需要指定一個端口號供給外部計算機連接。注意一下,計算機有的端口號已經被其他程序占用了,要想查看,你可以通過在cmd命令行輸入netstat -an來查詢。在這里,我們給定我們的端口號為空閑的9999。

復制代碼
 1     public static void main(String[] args) {  2 new Server().CreateServer(9999);  3  }  4 public void CreateServer(int port){  5 try {  6 ServerSocket serversocket = new ServerSocket(port);  7 } catch (IOException e) {  8  e.printStackTrace();  9  } 10 }
復制代碼

       接下來,我們讓服務器進入等待客戶機連接的狀態。通過查看ServerSocket源碼我們會發現,它里面有一個accept方法,其返回值是Socket類型。

1 Socket socket = serversocket.accept();

        Socket是Java中的另一個用於通信的類, 我們稱它為套接字。事實上,小編是這樣看的,Socket相當於建立在服務器和客戶端之間的一根通道。我們可以從socket獲取它的輸入輸出流對象,也就是我們的InputStream和OutputStream。這兩個輸入輸出都是采用字節傳輸的方法,如果你想要讀取一行數據可以通過BufferReader bufferreader = new BufferedReader(new InputStreamReader(is))的方式,將InputStream轉化成bufferRead之后調用readLine方法就可以了。需要注意的是對於readLine這個方法,/r/n表示消息的讀取結束。因此需要對消息進行+“/r/n”的操作來保證讀取的正常進行。

        因為在等待客戶機連接的時候,程序進入阻塞狀態,這個時候程序無法進行下去,而且,這樣的寫法也只能夠供給一個客戶機連接。為了能夠實現簡單客戶機和服務器的對話,我們需要采用多線程的方式去實現它,並且將實例化socket對象以及之后的這部分代碼放入到while循環當中。為了能夠實現客戶機與客戶機的通信,我們建立了一個消息處理類,並且實例化了一個數組隊列去存儲它,然后將這個隊列作為消息處理類的一個屬性。這樣所有的問題就得到了完美的解決。

復制代碼
 1     public void creatserver(int port) {  2 try {  3 ServerSocket serversocket = new ServerSocket(port);  4 System.out.println("服務器已經開啟,正在等待客戶機連接...");  5 while (true) {  6 Socket sk = serversocket.accept();  7 chatMessage cm = new chatMessage(sk, list);  8  list.add(cm);  9  cm.start(); 10  } 11 } catch (IOException e) { 12  e.printStackTrace(); 13  } 14 }
復制代碼

       消息處理類當中的內容,主要是實現對消息的封裝處理等等,以及對消息的鑒別。諸如消息是群發還是私發,如果是群發,那么遍歷整個消息處理類的隊列,如果是私發,那么同樣需要遍歷整個隊列去尋找我們需要私發的用戶名,然后將消息傳給它。一些其他的注意就不再贅述了,小編在代碼中也給出了相應的注釋,特別提出一點,當socket關閉,連接斷開,我們同樣需要關閉我們的IO流,否則占用資源是比較嚴重的。

復制代碼
  1 public class chatMessage extends Thread {  2 // 客戶機的賬號  3 public String username;  4 // 客戶機的密碼  5 public String password;  6 // // 客戶機的開啟狀態  7 // public volatile boolean state;  8 // 客戶機對象  9 public Socket s;  10 // 客戶機的輸入流  11 public InputStream is;  12 // 客戶機的輸出流  13 public OutputStream os;  14 // 緩沖(讀一行數據)  15 public BufferedReader br;  16 // 存儲所有客戶機對象的數組隊列  17 public ArrayList<chatMessage> list;  18  19 /**  20  * chatMessage的構造方法,包括IO流的建立  21  *  22  * @param s  23  * Socket對象  24  * @param list  25  * 存儲chatMessage對象的數組隊列  26  * @throws IOException  27  * 拋出IO異常  28 */  29 public chatMessage(Socket s, ArrayList<chatMessage> list)  30 throws IOException {  31 this.s = s;  32 this.list = list;  33 is = s.getInputStream();  34 os = s.getOutputStream();  35 br = new BufferedReader(new InputStreamReader(is));  36  }  37  38 /**  39  * 服務器轉發消息  40  *  41  * @param str  42  * 轉發的消息內容  43  * @throws IOException  44  * 拋出IO異常  45 */  46 public void sendMessage(String str) throws IOException {  47 os.write((str + "\r\n").getBytes());  48  }  49  50 /**  51  * 服務器把消息廣播到所有的客戶機  52  *  53  * @param str  54  * 廣播的消息內容  55  * @param code  56  * 消息類型的判斷,1表示為提示成員登錄,2表示為廣播消息  57  * @throws IOException  58  * 拋出的IO異常  59 */  60 public void sendAllMessage(String str, int code) throws IOException {  61 for (int index = 0; index < list.size(); index++) {  62 if (list.get(index) != this && code == 1) {  63  list.get(index).sendMessage(str);  64 } else if (code == 2) {  65  list.get(index).sendMessage(str);  66  }  67  }  68 System.out.print(str + "\r\n");  69  }  70  71 /**  72  * 讀取來自客戶端的消息  73  *  74  * @return 返回一個字符串  75  * @throws IOException  76  * 拋出IO異常  77 */  78 public String readMessage() throws IOException {  79 return br.readLine();  80  }  81  82 /**  83  * 服務器把消息轉發給某人  84  *  85  * @param str1  86  * 消息內容  87  * @param str2  88  * 消息的接收人  89 */  90 public void sendMessageToOthers(String str1, String str2) {  91 for (int i = 0; i < list.size(); i++) {  92 if (str2.equals(list.get(i).username)) {  93 try {  94  list.get(i).sendMessage(str1);  95 } catch (IOException e) {  96  e.printStackTrace();  97  }  98  }  99  } 100  } 101 102 /** 103  * 線程啟動 從客戶機獲取登錄信息,分別賦給username以及password 給對應客戶機發送“歡迎來到聊天室” 104  * 給所有的客戶機發送username+“來到聊天室” 105  * 106  * 107  * 108  * 109  * 110  * 111 */ 112 public void run() { 113 try { 114 String str = readMessage(); 115  makeMessage(str); 116 sendMessage("歡迎來到聊天室"); 117 String string = username + "來到聊天室"; 118 sendAllMessage(str, 1); 119  String s; 120 while (!(s = readMessage()).equals("bye")) { 121 if (s.charAt(0) == '@') { 122 int nig = s.indexOf(" "); 123 String ns = s.substring(1, nig); 124 String valuestring = s.substring(nig + 1, s.length()); 125 for (int j = 0; j < list.size(); j++) { 126 if (ns.equals(list.get(j).username)) { 127  list.get(j).sendMessage( 128 username + "對你說:" + valuestring); 129  } 130  } 131 } else { 132 s = username + "說:" + s; 133 sendAllMessage(s, 2); 134  } 135  } 136 sendAllMessage(username + "離開了聊天室", 1); 137  close(); 138 } catch (IOException e) { 139  e.printStackTrace(); 140  } 141  } 142 143 /** 144  * 關閉所有IO 145  * 146  * @throws IOException 147  * 拋出IO異常 148 */ 149 public void close() throws IOException { 150  is.close(); 151  os.close(); 152  br.close(); 153  s.close(); 154  } 155 156 /** 157  * 對首次登錄時客戶機發送的登錄信息進行處理,給username以及password賦值 158  * 159  * @param str 160  * 首次登錄客戶機發送的信息 161 */ 162 public void makeMessage(String str) { 163 int index = str.indexOf(","); 164 username = str.substring(0, index); 165 password = str.substring(index + 1, str.length()); 166  } 167 }
復制代碼

        客戶端的編寫跟服務器差不多,主要需要注意的一些內容上的匹配,比如小編的服務器首先需要進行一次對賬號密碼內容的接收,也就是說需要讀取一條消息后正常進行。小編稍微加了一點界面內容,比較簡單,所以也比較一般,大家稍微看看,不必過多在意界面的內容。

        這個是登錄界面的代碼:

復制代碼
 1 package Client0802;  2  3 import java.awt.Dimension;  4 import java.awt.FlowLayout;  5 import java.awt.Font;  6  7 import javax.swing.JButton;  8 import javax.swing.JFrame;  9 import javax.swing.JLabel; 10 import javax.swing.JPasswordField; 11 import javax.swing.JTextField; 12 13 public class Login extends JFrame { 14 public static void main(String[] args) { 15 new Login().initUI(); 16  } 17 18 public void initUI() { 19 this.setTitle("Login"); 20 this.setSize(350, 300); 21 this.setLayout(new FlowLayout()); 22 this.setLocationRelativeTo(null); 23 this.setDefaultCloseOperation(3); 24 Dimension dim1 = new Dimension(100, 60); 25 Dimension dim2 = new Dimension(200, 60); 26 Font font = new Font("宋體", Font.BOLD, 20); 27 JLabel jlone = new JLabel("賬號 :"); 28  jlone.setPreferredSize(dim1); 29  jlone.setFont(font); 30 this.add(jlone); 31 JTextField jtf = new JTextField(); 32  jtf.setPreferredSize(dim2); 33  jtf.setFont(font); 34 this.add(jtf); 35 JLabel jltwo = new JLabel("密碼 :"); 36  jltwo.setPreferredSize(dim1); 37  jltwo.setFont(font); 38 this.add(jltwo); 39 JPasswordField jpwf = new JPasswordField(); 40  jpwf.setPreferredSize(dim2); 41  jpwf.setFont(font); 42 this.add(jpwf); 43 JButton jbt = new JButton("登錄"); 44 jbt.setPreferredSize(new Dimension(80, 40)); 45 this.add(jbt); 46 47 this.setVisible(true); 48 listener lis = new listener(this, jtf, jpwf); 49  jbt.addActionListener(lis); 50  } 51 52 public static void Log(String str) { 53  System.out.println(str); 54  } 55 }
復制代碼

 

        這個是處理點擊登錄的監聽器:

復制代碼
  1 package Client0802;  2  3 import java.awt.Dimension;  4 import java.awt.FlowLayout;  5 import java.awt.TextArea;  6 import java.awt.event.ActionEvent;  7 import java.awt.event.ActionListener;  8 import java.io.BufferedReader;  9 import java.io.IOException;  10 import java.io.InputStream;  11 import java.io.InputStreamReader;  12 import java.io.OutputStream;  13 import java.net.Socket;  14 import java.net.UnknownHostException;  15  16 import javax.swing.JButton;  17 import javax.swing.JFrame;  18 import javax.swing.JPasswordField;  19 import javax.swing.JTextField;  20  21 public class listener implements ActionListener {  22 public JFrame jf;  23 public JFrame newjf;  24 public InputStream is;  25 public OutputStream os;  26 public BufferedReader br;  27 public JTextField jtf, cou;  28 public JPasswordField jpwf;  29  30 public listener(JFrame jf, JTextField jtf, JPasswordField jpwf) {  31 this.jf = jf;  32 this.jtf = jtf;  33 this.jpwf = jpwf;  34  }  35  36 /*  37  * 實例化客戶機,連接到服務器 實例化IO流 將得到的賬戶和密碼發送給服務器 關閉原有登錄界面 建立一個顯示界面  38 */  39 public void actionPerformed(ActionEvent e) {  40 try {  41 Socket s = new Socket("127.0.0.1", 7777);  42 System.out.print("連接到了服務器");  43 is = s.getInputStream();  44 os = s.getOutputStream();  45 br = new BufferedReader(new InputStreamReader(is));  46 String s1 = jtf.getText();  47 String s2 = String.valueOf(jpwf.getPassword());  48 sendMessage(s1 + "," + s2 + "\r\n");  49  jf.dispose();  50 newjf = new JFrame();  51 newjf.setTitle("communication...");  52 newjf.setSize(800, 600);  53 newjf.setLocationRelativeTo(null);  54 newjf.setDefaultCloseOperation(3);  55 newjf.setLayout(new FlowLayout());  56 TextArea ta = new TextArea();  57 ta.setPreferredSize(new Dimension(790, 300));  58  newjf.add(ta);  59 cou = new JTextField();  60 cou.setPreferredSize(new Dimension(700, 150));  61  newjf.add(cou);  62 JButton jb = new JButton("發送");  63 jb.setPreferredSize(new Dimension(80, 150));  64  newjf.add(jb);  65 newjf.setVisible(true);  66 getMessage gm = new getMessage(br, ta);  67  gm.start();  68 jb.addActionListener(new ActionListener() {  69 public void actionPerformed(ActionEvent e) {  70 try {  71 os.write((cou.getText() + "\r\n").getBytes());  72 cou.setText("");  73 } catch (IOException e1) {  74  e1.printStackTrace();  75  }  76  }  77  });  78 } catch (UnknownHostException e1) {  79  e1.printStackTrace();  80 } catch (IOException e1) {  81  e1.printStackTrace();  82  }  83  }  84  85 /**  86  * 發送消息到服務器  87  *  88  * @param str  89 */  90 public void sendMessage(String str) {  91 try {  92  os.write(str.getBytes());  93 } catch (IOException e) {  94  e.printStackTrace();  95  }  96  }  97  98 /**  99  * 初始化一個顯示界面 100 */ 101 public void initUI() { 102 newjf = new JFrame(); 103 newjf.setTitle("communication..."); 104 newjf.setSize(800, 600); 105 newjf.setLocationRelativeTo(null); 106 newjf.setDefaultCloseOperation(3); 107 newjf.setLayout(new FlowLayout()); 108 TextArea ta = new TextArea(); 109 ta.setPreferredSize(new Dimension(790, 300)); 110  newjf.add(ta); 111 cou = new JTextField(); 112 cou.setPreferredSize(new Dimension(700, 150)); 113  newjf.add(cou); 114 JButton jb = new JButton("發送"); 115 jb.setPreferredSize(new Dimension(80, 150)); 116  newjf.add(jb); 117 jb.addActionListener(new ActionListener() { 118 public void actionPerformed(ActionEvent e) { 119 try { 120 os.write((cou.getText() + "\r\n").getBytes()); 121 cou.setText(""); 122 } catch (IOException e1) { 123  e1.printStackTrace(); 124  } 125  } 126  }); 127 newjf.setVisible(true); 128 boolean temp = true; 129 while (temp) { 130 temp = false; 131 getMessage gm = new getMessage(br, ta); 132  gm.start(); 133  } 134  } 135 }
復制代碼

 

        這個是用於控制接收服務器消息的線程類:

 

復制代碼
 1 package Client0802;  2  3 import java.awt.TextArea;  4 import java.io.BufferedReader;  5 import java.io.IOException;  6  7 public class getMessage extends Thread {  8 public BufferedReader br;  9 public TextArea ta; 10 11 public getMessage(BufferedReader br, TextArea ta) { 12 this.br = br; 13 this.ta = ta; 14  } 15 16 public void run() { 17 while (true) { 18 try { 19 String str = br.readLine(); 20 if (str != null && str != "") { 21 ta.append(str + "\n"); 22  } 23 } catch (IOException e) { 24  e.printStackTrace(); 25  } 26  } 27  } 28 }
復制代碼

 

        大概內容就這么多拉,其實小編還有很多內容沒有設計到,比如說發送圖片啊,比如說文件的傳輸啊,這些都還沒有動手實現,但是,整個過程其實都差不太多,只是傳輸用到的IO流類名不太一樣而已,這不是什么大問題。我相信以大家的聰明才智都能夠解決!最后小編將就通信協議這一塊稍微做一點總結。

        協議是什么。協議是指雙方共同設計的必須滿足的要求。在通信這一塊,在客戶機服務器模型這一塊,就是指規定消息的編寫格式。這也就是為什么小編之前說的編寫客戶機和服務器的內容查差不了多少了。對於一個完整的通信系統來說,協議非常重要,就像老師群發了一封郵件給所有人,但是,郵件是用日語寫的,大家看不懂,這就是導致的后果。協議也一樣,客戶機給服務器發送許許多多的字節,服務器無法區分有幾部分,每一部分是什么意思,那么,服務器就無法對消息進行處理。就像一段暗號或者說是密文,你可以這么去定義你的協議,比如,消息的第幾個字節表示什么,消息第幾個字節到第幾個字節代表什么。當然這還是比較簡單的通信協議,各位看官可以去網上查一查那些比較有名的軟件的協議,QQ、微信等等。當然,有一些軟件的協議是不公開的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM