Java基於Socket實現多人聊天室
本文實例為大傢分享瞭Java基於Socket實現簡易版多人聊天室的具體代碼,供大傢參考,具體內容如下
一、 聊天室需求
1、一個服務端,多個客戶端;
2、實現客戶端和服務端的交互;
3、客戶端發送信息,服務端收到信息,再轉發給其他客戶端;
4、上下線時顯示哪個客戶端上下線並且顯示在線客戶端數量;
二、代碼分析
1. 建立連接
客戶端類,創建發送端Socket對象,用自己的IP地址和端口號,與服務端建立連接。
class Client:
//用於與服務端通信的Socket private Socket socket; public Client() throws Exception { /* * 初始化Socket的同時要制定服務端的IP地址和端口號 * ip地址用於我們在網絡上找到服務端的所在計算在 * 端口號用於找到服務器上的服務端應用程序。 * * 實例化Socket的過程就是連接服務端的過程若 * 服務端無響應,這裡得構造方法得拋出異常。 * */ try { System.out.println("正在連接服務器......"); //localhost 127.0.0.1 socket = new Socket("LAPTOP-TCK59O6Q",8888); System.out.println("與服務端連接完畢"); } catch (Exception e) { System.out.println("初始化失敗"); throw e; } }
服務端類,使用構造方法初始化服務端,創建接收端的Socket對象
class Server:
private ServerSocket server; //構造方法初始化服務端 public Server() throws IOException { //實例化serverSocket的同時,指定服務端的端口號; try { server = new ServerSocket(8888); allOut = new ArrayList<PrintWriter>(); } catch (Exception e) { System.out.println("服務端初始化失敗"); throw e; } }
2. 客戶端發送信息
在客戶端的類中寫一個start()方法,start()是客戶端發送信息給服務端的方法
獲取輸出流對象,把鍵盤錄入的信息發送到服務端。
class Client:
public void start() throws Exception { /* * 客戶端開始工作的方法 */ try { //啟動用於讀取服務端發送消息的線程 ServerHandler handler = new ServerHandler(); //ServerHandler是自己寫的類,實現Runnable接口,有多線程功能 Thread t = new Thread(handler); t.start(); //將數據發送到服務端 OutputStream out = socket.getOutputStream();//獲取輸出流對象 OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");//轉化成utf-8格式 PrintWriter pw = new PrintWriter(osw,true); Scanner scan = new Scanner(System.in); while(true) { String message = scan.nextLine();//得到鍵盤錄入的信息 pw.println(message);//把信息輸出到服務端 } } catch (Exception e) { System.out.println("客戶端運行失敗"); throw e; } }
服務端工作的start()方法,accept()方法與客戶端連接上
class Server:
//服務端工作的方法 public void start() throws IOException { /* * ServerSocket提供瞭一個accept的方法,該方法是一個阻塞方法, * 用於監聽其打開的8888端口;當一個客戶端通過該端口與服務端連接時, * accept方法就會解除阻塞,然後創建一個socket實例並返回, * socket的作用就是與剛剛連接上的客戶端進行通信。 */ while(true) { System.out.println("等待客戶端連接..."); Socket socket = server.accept(); System.out.println("一個客戶端連接瞭!"); //啟動一個線程來處理客戶端的交互工作 ClientHandler hander = new ClientHandler(socket); Thread t = new Thread(hander); t.start(); } }
3. 開啟多線程、服務端接收讀取信息並廣播
因為服務端與多個客戶端相連,所以要用多線程,即一個客戶端用一條線程。
在服務端類中創建一個內部類ClientHandler實現Runnable接口並重寫run()方法創建線程
屬性有客戶端的Socket對象
有參構造方法中通過客戶端的Socket獲取到其地址host,並且把地址打印出來
這樣在main()方法中,實例化服務端類的對象之後,start方法開啟服務端,當有客戶端連接上時,就能輸出這個客戶端的ip地址。
ClientHandler類要重寫run()方法,使用輸入流InputStream讀取客戶端發來的信息,再使用輸出流OutputStream給所有客戶端廣播收到的信息、用戶上下線和在線人數
class Server:
/** * ClientHandler * 該線程類是與指定的客戶端進行交互工作; * @author zxm * */ class ClientHandler implements Runnable{ //當前線程客戶端的Socket private Socket socket; //該客戶端的地址 private String host; public ClientHandler(Socket socket) { this.socket=socket; /* * 通過socket獲取遠程計算機地址 * 對於服務端而言,遠程計算機就是客戶端 */ InetAddress address = socket.getInetAddress(); //獲取ip地址 host = address.getHostAddress(); System.out.println("host"+host); } @Override public void run() { PrintWriter pw = null; try { //廣播給所有客戶端,當前用戶上線瞭 sendMessage("["+host+"]上線瞭"); OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8"); pw = new PrintWriter(osw,true); //將該客戶的輸出流存入共享集合,以便消息可以廣播給該客戶端 addOut(pw); //廣播當前在線人數 sendMessage("當前在線人數["+allOut.size()+"]"); //處理來自客戶端的數據 InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in,"utf-8"); BufferedReader br = new BufferedReader(isr); /* * 服務端讀取客戶端發送過來的每一句字符時 * 由於客戶端所在的操作系統不同,這裡客戶端斷開時結果也不同 * windows客戶端開始br.readLine拋出異常 * Linux客戶端斷開是返回null * */ String message = null; while((message = br.readLine())!=null) { sendMessage(host+"說:"+message); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { //將該客戶端的輸出流從共享集合中刪除 removeOut(pw); //廣播給所有客戶端,當前用戶下線 sendMessage("["+host+"]下線瞭"); //廣播當前在線人數 sendMessage("當前在線人數["+allOut.size()+"]"); try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
因此服務端類中要寫一個sendMessage()方法,為瞭在接收到一個客戶端的信息後,把這個信息轉發給所有客戶端,這就是廣播的效果。
寫一個集合allOut,用來存放所有客戶端的輸出流,客戶端的數量就是集合裡元素的個數
再寫兩個方法,一個是addOut()方法把上線客戶端的輸出流放進集合(在run方法中使用addOut(),獲取到啟用新線程的客戶端的輸出流,把輸出流加到集合中),
另一個是removeOut()方法拿出集合(同理run()方法中使用,把socket關閉的客戶端的輸出流移除集合)。
所以sendMessage()方法的參數就是某個客戶端發的字符串信息message,遍歷allOut集合,把message在每個輸出流中打印,用PrintWrite類中的print方法。
當客戶端連接服務端時,sendMessage()方法打印這個服務端的地址加上上線瞭,同理客戶端關閉socket的時候打印下線瞭,
同時上下線後再打印allOut集合的大小,也就是當前連接服務端的客戶端數量,就是在線人數。
class Server:
//存放所有客戶端的輸出流的集合,用於廣播 private List<PrintWriter> allOut; //將給定的輸出流放入共享集合 private synchronized void addOut(PrintWriter out){ allOut.add(out); } //將給定的輸出流移除共享集合 private synchronized void removeOut(PrintWriter out){ allOut.remove(out); } //將給定的消息發給多個客戶端 private synchronized void sendMessage(String message) { for(PrintWriter out:allOut) { out.println(message); } }
4. 客戶端讀取信息
這個時候所有的客戶端都收到瞭某個客戶發的消息,但是還沒讀,所以客戶端類中要加輸入流才能讀取,
創建ServerHandler類實現Runnable接口,輸入流讀取並輸出。
class Client:
class ServerHandler implements Runnable{ /** * 該線程用於讀取服務端發送過來的消息,並輸出到 * 客戶端的控制臺上 * @author zxm * */ @Override public void run() { try { InputStream in = socket.getInputStream();//輸入流 InputStreamReader isr = new InputStreamReader(in,"UTF-8");//以utf-8讀 BufferedReader br = new BufferedReader(isr); String message = null; while((message=br.readLine())!=null) { System.out.println(message); } } catch (Exception e) { e.printStackTrace(); } } }
三、完整代碼
1. 客戶端
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; /** * 聊天室服務端 * @author zxm */ public class Client { //用於與服務端通信的Socket private Socket socket; public Client() throws Exception { /* * 初始化Socket的同時要制定服務端的IP地址和端口號 * ip地址用於我們在網絡上找到服務端的所在計算在 * 端口號用於找到服務器上的服務端應用程序。 * * *實例化Socket的過程就是連接服務端的過程若 * 服務端無響應,這裡得構造方法得拋出異常。 * */ try { System.out.println("正在連接服務器......"); //localhost 127.0.0.1 socket = new Socket("LAPTOP-TCK59O6Q",8888); System.out.println("與服務端連接完畢"); } catch (Exception e) { System.out.println("初始化失敗"); throw e; } } public void start() throws Exception { /* * 客戶端開始工作的方法 */ try { //啟動用於讀取服務端發送消息的線程 ServerHandler handler = new ServerHandler(); //ServerHandler是自己寫的類,實現Runnable接口,有多線程功能 Thread t = new Thread(handler); t.start(); //將數據發送到服務端 OutputStream out = socket.getOutputStream();//獲取輸出流對象 OutputStreamWriter osw = new OutputStreamWriter(out,"utf-8");//轉化成utf-8格式 PrintWriter pw = new PrintWriter(osw,true); Scanner scan = new Scanner(System.in); while(true) { String message = scan.nextLine();//得到鍵盤錄入的信息 pw.println(message);//把信息輸出到服務端 } } catch (Exception e) { System.out.println("客戶端運行失敗"); throw e; } } public static void main(String[] args) throws Exception { try { Client client = new Client(); client.start(); } catch (Exception e) { System.out.println("客戶端運行失敗"); e.printStackTrace(); } } class ServerHandler implements Runnable{ /** * 該線程用於讀取服務端發送過來的消息,並輸出到 * 客戶端的控制臺上 * @author zxm * */ @Override public void run() { try { InputStream in = socket.getInputStream();//輸入流 InputStreamReader isr = new InputStreamReader(in,"UTF-8");//以utf-8讀 BufferedReader br = new BufferedReader(isr); String message = null; while((message=br.readLine())!=null) { System.out.println(message); } } catch (Exception e) { e.printStackTrace(); } } } }
2. 服務端
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; /** * 聊天室服務端 * @author zxm */ public class Server { /* * 運行在服務端的socket * 該類的作用是: * 1.申請服務端口,客戶端就是通過它申請的服務端口連接上服務端應用的。 * 2.監聽申請的服務端口感知客戶端的連接,並創建一個socket與該客戶通信。 */ private ServerSocket server; //存放所有客戶端的輸出流的集合,用於廣播 private List<PrintWriter> allOut; //將給定的輸出流放入共享集合 private synchronized void addOut(PrintWriter out){ allOut.add(out); } //將給定的輸出流移除共享集合 private synchronized void removeOut(PrintWriter out){ allOut.remove(out); } //將給定的消息發給多個客戶端 private synchronized void sendMessage(String message) { for(PrintWriter out:allOut) { out.println(message); } } //構造方法初始化服務端 public Server() throws IOException { //實例化serverSocket的同時,指定服務端的端口號; try { server = new ServerSocket(8888); allOut = new ArrayList<PrintWriter>(); } catch (Exception e) { System.out.println("服務端初始化失敗"); throw e; } } //服務端工作的方法 public void start() throws IOException { /* * ServerSocket提供瞭一個accept的方法,該方法是一個阻塞方法, * 用於監聽其打開的8888端口;當一個客戶端通過該端口與服務端連接時, * accept方法就會解除阻塞,然後創建一個socket實例並返回, * socket的作用就是與剛剛連接上的客戶端進行通信。 */ while(true) { System.out.println("等待客戶端連接..."); Socket socket = server.accept(); System.out.println("一個客戶端連接瞭!"); //啟動一個線程來處理客戶端的交互工作 ClientHandler hander = new ClientHandler(socket); Thread t = new Thread(hander); t.start(); } } public static void main(String[] args) throws Exception { Server server = new Server(); server.start(); } /** * 該線程類是與指定的客戶端進行交互工作; * @author zxm * */ class ClientHandler implements Runnable{ //當前線程客戶端的Socket private Socket socket; //該客戶端的地址 private String host; public ClientHandler(Socket socket) { this.socket=socket; /* * 通過socket獲取遠程計算機地址 * 對於服務端而言,遠程計算機就是客戶端 */ InetAddress address = socket.getInetAddress(); //獲取ip地址 host = address.getHostAddress(); System.out.println("host"+host); } @Override public void run() { PrintWriter pw = null; try { //廣播給所有客戶端,當前用戶上線瞭 sendMessage("["+host+"]上線瞭"); OutputStream out = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8"); pw = new PrintWriter(osw,true); //將該客戶的輸出流存入共享集合,以便消息可以廣播給該客戶端 addOut(pw); //廣播當前在線人數 sendMessage("當前在線人數["+allOut.size()+"]"); //處理來自客戶端的數據 InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in,"utf-8"); BufferedReader br = new BufferedReader(isr); /* * 服務端讀取客戶端發送過來的每一句字符時 * 由於客戶端所在的操作系統不同,這裡客戶端斷開時結果也不同 * windows客戶端開始br.readLine拋出異常 * Linux客戶端斷開是返回null * */ String message = null; while((message = br.readLine())!=null) { sendMessage(host+"說:"+message); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { //將該客戶端的輸出流從共享集合中刪除 removeOut(pw); //廣播給所有客戶端,當前用戶下線 sendMessage("["+host+"]下線瞭"); //廣播當前在線人數 sendMessage("當前在線人數["+allOut.size()+"]"); try { socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java Socket實現簡易聊天室
- Java實現聊天機器人
- Java基於TCP協議的Socket通信
- Java 全面掌握網絡編程篇
- 解決Java中socket使用getInputStream()阻塞問題