Java基于Socket实现多人聊天室

Kande ·
更新时间:2024-09-20
· 1880 次阅读

本文实例为大家分享了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();                 }             }         }     } }



JAVA 聊天室 socket

需要 登录 后方可回复, 如果你还没有账号请 注册新账号