今天我要向大家介紹的是自己編寫的一個比較簡單的服務器和客戶機程序,注意一下哦,比較簡單。好了,閑話休提,砸門直入主題。
小編先從客戶機和服務器的模型開始講解。簡單來說,我們實現的這種模型呢,我們每一個用戶稱為一個客戶機,用戶之間的通信之間需要一個中轉,所有客戶機的通信都依托於這個中轉,很明顯,這個中轉,就是砸門的服務器了。整個模型比較簡單明了,那么,接下來我們就直接進入實現階段。
我們從實現服務器開始。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 (