手写一个Java聊天室·自定义协议与多线程实践

手写一个Java聊天室·自定义协议与多线程实践 一.项目概述这是一个基于Java Socket和自定义通信协议的简易聊天室。服务端负责管理客户端连接和消息的接收与转发客户端通过控制台输入消息并实时接收消息。✅️完整代码已嵌入笔记可自行运行尝试~二.服务端核心模块定义成员变量ss、sockerList 实例变量特点属于对象也就是new Server创建出来的那个具体实例调用方式必须通过对象名.变量名访问egserver.socketListsocketList用于存储所有socketnickMap 静态变量特点属于类本身而不属于某个具体的对象调用方式可以直接通过类名.变量名访问egServer.nickMap通过哈希表存储每个客户对应的昵称ServerSocket ssnull; ArrayListSocket socketListnew ArrayList(); //存储每个Socket 对应的昵称 static HashMapSocket,String nickMapnew HashMap();init方法——初始化服务端ssnew ServerSocket(9527);会在本机的9527端口上创建一个服务端套接字开始监听客户端连接需求public void init(){ try { ssnew ServerSocket(9527); } catch (IOException e) { throw new RuntimeException(e); } }listen方法——等待客户端连接ss.accept是一个阻塞方法没有客户接入时就会卡在这里。当有客户端连接进来时accept会返回一个Socket对象代表与这个客户的通信通道public Socket listen(){ try { return ss.accept(); } catch (IOException e) { throw new RuntimeException(e); } }send方法——发送消息先将字符串消息转换为字节数组读取消息的长度利用write方法发送长度利用write方法发送消息内容这是自定义协议的核心先发长度再发内容调用flush 相当于点击发送按钮public void send(String message, OutputStream os){ try { //用DataOutputStream 包装方便写int DataOutputStream dosnew DataOutputStream(os); byte[] msgsmessage.getBytes(UTF-8); int lenmsgs.length; dos.writeInt(len);//发送长度 dos.write(msgs); dos.flush(); } catch (IOException e) { throw new RuntimeException(e); } }receive方法——接收消息先读取消息长度创建一个长度为len的字节数组从输入流中读取len个字节填充进去得到消息完整的字节数据将字节数组转换回字符串得到原始消息内容获取发送消息的客户端昵称通过循环遍历所有客户端找到与当前连接相同的那个socket打印消息返回消息内容public String receive(InputStream in,Socket socket){ try { //用DataInputStream 包装方便读int DataInputStream disnew DataInputStream(in); int lendis.readInt(); byte[] msgsnew byte[len]; dis.readFully(msgs); String msgnew String(msgs,UTF-8); String senderNamenickMap.get(socket); for (int i 0; i socketList.size(); i) { Socket socket2socketList.get(i); if (socket2socket){ System.out.println(收到用户senderName消息msg); } } return msg; } catch (IOException e) { throw new RuntimeException(e); } }proClientMsg方法——读取客户端消息新建线程在线程内完成以下内容创建输入流对象调用receive方法来接收昵称并将昵称存入nickMap中输出用户上线消息循环内转发消息给客户端遍历所有客户端如果不是发送者本人就转发消息public void proClientMsg(Socket socket){ new Thread(()-{ InputStream in null; try { in socket.getInputStream(); //接收昵称 String nickNamereceive(in,socket); nickMap.put(socket,nickName); System.out.println(用户nickName端口socket.getPort()上线); } catch (IOException e) { throw new RuntimeException(e); } while (true){ try { String msgreceive(in,socket); String senderNamenickMap.get(socket); //转发消息给客户端 for (int i 0; i socketList.size(); i) { Socket socket1socketList.get(i); if (socket1!socket){ send(收到用户senderName的消息msg,socket1.getOutputStream()); } } } catch (IOException e) { throw new RuntimeException(e); } } }).start(); }主函数——程序入口创建对象调用初始化方法新建线程在线程内完成以下内容调用监听方法将每个socket存入数组中调用读取消息方法static void main() { Server servernew Server(); //初始化 server.init(); //监听连接部分 new Thread(()-{ while (true) { System.out.println(监听连接中……); Socket socket server.listen(); System.out.println(socket.getRemoteSocketAddress() 连接成功); server.socketList.add(socket); server.proClientMsg(socket);//处理客户端消息每个客户端对应各自的线程 } }).start();三.客户端核心模块定义成员变量socket、nickNameinit方法——初始化客户端new Socket(127.0.0.1,9527); 创建一个Socket连接连接到本机的服务端监听端口public void init(){ try { socketnew Socket(127.0.0.1,9527); } catch (IOException e) { throw new RuntimeException(e); } }send方法——发送消息与服务端send方法完全一致public void send(String message){ try { OutputStream ossocket.getOutputStream(); DataOutputStream dosnew DataOutputStream(os); byte[] msgsmessage.getBytes(UTF-8); int lenmsgs.length; dos.writeInt(len); dos.write(msgs);//编写 dos.flush();//发送 } catch (IOException e) { throw new RuntimeException(e); } }readMsg方法——接收消息与服务端receive方法完全一致public String readMsg(){ try { InputStream inssocket.getInputStream(); DataInputStream disnew DataInputStream(ins); int lendis.readInt(); byte[] msgsnew byte[len]; dis.readFully(msgs); String msgnew String(msgs,UTF-8); return msg; } catch (IOException e) { throw new RuntimeException(e); } }主函数——程序入口创建对象调用初始化方法输入昵称并发送开启一个线程开启循环发送消息开启循环接收消息static void main() { Client clientnew Client(); //初始化 client.init(); //输入昵称 Scanner scanner1new Scanner(System.in); System.out.println(请输入你的昵称); client.nickNamescanner1.nextLine(); client.send(client.nickName); //接收消息 new Thread(()-{ while (true){ String msg client.readMsg(); System.out.println(msg); System.out.println(请输入消息); } }).start(); //发送消息 Scanner scannernew Scanner(System.in); while (true){ System.out.println(请输入消息); String messagescanner.nextLine(); client.send(message); } }