网站建设公司兴田德润实惠,wordpress模块,电子商务是学什么的,给个网址好人有好报目录 聊天室数据传输设计客户端、服务器数据交互数据传输协议服务器、多客户端模型客户端如何发送消息到另外一个客户端2个以上设备如何交互数据#xff1f; 聊天室消息接收实现代码结构client客户端重构server服务端重构自身描述信息的构建重构TCPServer.java基于synchronize… 目录 聊天室数据传输设计客户端、服务器数据交互数据传输协议服务器、多客户端模型客户端如何发送消息到另外一个客户端2个以上设备如何交互数据 聊天室消息接收实现代码结构client客户端重构server服务端重构自身描述信息的构建重构TCPServer.java基于synchronized 解决多线程操作的安全问题 聊天室Server/Client启动、测试源码下载 聊天室数据传输设计
必要条件客户端、服务器必要约束数据传输协议原理服务器监听消息来源、客户端链接服务器并发送消息到服务器
客户端、服务器数据交互 client 发送消息到服务器端服务器端回复消息也就是回送消息。
数据传输协议 数据在传输的时候需要在尾部追加换行符也就是说原来5个字节的数据在实际传输时是有6个字节长度的。
服务器、多客户端模型 在客户端有多个情况下客户端都会向服务器端进行发送消息想要在PC发送消息给服务器端时也让安卓、平板等终端都能收到其操作应该是当PC端发送一条消息到服务器端之后服务器端得到该数据后它会把这条数据发送回送给当前连接的客户端。而这些当前连接的客户端收到这条消息后就实现了把PC消息发送到手机的过程。 客户端如何发送消息到另外一个客户端
每个客户端都是服务器也是客户端 答不是
2个以上设备如何交互数据
答约定一个基础的数据格式这里使用回车换行符来作为信息的截断 客户端-服务器-转发到客户端如下图
User1发送消息到服务端服务端将消息转发给其他的客户端比如User2从而实现聊天室的功能
聊天室消息接收实现
代码结构 代码分为四个module分别为clink、constants、client、server。
clink该module为提供工具类进行校验与流处理。constants基础的共用类代码server服务端代码需要依赖 clink、constants两个moduleclient客户端代码需要依赖 clink、constants两个module
clink、constants的工具类基础数据类参考前面 TCP点对点传输的代码逻辑
client客户端重构
初版代码和TCP点对点传输的基本一致聊天室主要在TCPServer端进行转发所以Client不需要代码重构。
server服务端重构
初版代码和TCP点对点传输的基本一致要实现聊天室消息接收则需要进行重构。主要重构 TCPServer.java 、ClientHandler.java类。
ClientHandler.java - 消息转发 原有的消息在收到后就只是打印到控制台
// 打印到屏幕
System.out.println(str);而实现聊天室功能需要将收到的消息进行通知出去。这里可以通过 CloseNotify() 接口进行实现。这里对该接口进行改造并新增转发的接口方法来将消息通知回去。 /*** 消息回调*/public interface ClientHandlerCallback {// 自身不安比通知void onSelfClosed(ClientHandler handler);// 收到消息时通知void onNewMessageArrived(ClientHandler handler,String msg);}在将消息打印到屏幕的同时将消息通知出去 // 打印到屏幕System.out.println(str);clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);调用onNewMessageArrived方法从而进行转发。这里主要是把当前收到的消息传递回去同时也要把自身传递回去。
自身描述信息的构建
新增clientInfo类变量 private final String clientInfo;自身描述信息初始化 public ClientHandler(Socket socket, ClientHandlerCallback clientHandlerCallback) throws IOException {this.socket socket;this.readHandler new ClientReadHandler(socket.getInputStream());this.writeHandler new ClientWriteHandler(socket.getOutputStream());this.clientHandlerCallback clientHandlerCallback;// 新增自身描述信息this.clientInfo A[ socket.getInetAddress().getHostAddress() ] P[ socket.getPort() ];System.out.println(新客户端连接 clientInfo);}public String getClientInfo() {return clientInfo;}重构TCPServer.java
重构 clientHandler.ClientHandlerCallback的两个回调方法这里要将之提到TCPServer.java类上。
让TCPServer.java 实现 clientHandler.ClientHandlerCallback接口。并实现两个方法 Overridepublic synchronized void onSelfClosed(ClientHandler handler) {}Overridepublic void onNewMessageArrived(ClientHandler handler, String msg) {}并将 客户端构建溢出线程的remove操作迁移到 onSelfClosed() 方法实现内 Overridepublic synchronized void onSelfClosed(ClientHandler handler) {clientHandlerList.remove(handler);}原有的ClientHandler异步线程处理逻辑如下 // 客户端构建异步线程ClientHandler clientHandler new ClientHandler(client,handler - clientHandlerList.remove(handler));重构后如下 // 客户端构建异步线程ClientHandler clientHandler new ClientHandler(client,TCPServer.this);消息转发 /*** 转发消息给其他客户端* param handler* param msg*/Overridepublic void onNewMessageArrived(ClientHandler handler, String msg) {// 打印到屏幕System.out.println(Received- handler.getClientInfo() : msg);// 转发forwardingThreadPoolExecutor.execute(()-{for (ClientHandler clientHandler : clientHandlerList){if(clientHandler.equals(handler)){// 跳过自己continue;}// 向其他客户端投递消息clientHandler.send(msg);}});}基于synchronized 解决多线程操作的安全问题
由于这里有对 clientHandlerList集合的删除、添加、遍历等操作这涉及到对所有客户端的操作在多线程的环境下默认的List不是线程安全的所以存在多线程的安全问题。 public void stop() {if (mListener ! null) {mListener.exit();}synchronized (TCPServer.this){for (ClientHandler clientHandler : clientHandlerList) {clientHandler.exit();}clientHandlerList.clear();}// 停止线程池forwardingThreadPoolExecutor.shutdownNow();}public synchronized void broadcast(String str) {for (ClientHandler clientHandler : clientHandlerList) {clientHandler.send(str);}}/*** 删除当前消息* param handler*/Overridepublic synchronized void onSelfClosed(ClientHandler handler) {clientHandlerList.remove(handler);}/*** 转发消息给其他客户端* param handler* param msg*/Overridepublic void onNewMessageArrived(ClientHandler handler, String msg) {// 打印到屏幕System.out.println(Received- handler.getClientInfo() : msg);// 转发}这里加类锁来保证删除操作的线程安全。
关于添加操作的线程安全问题解决如下 try {// 客户端构建异步线程ClientHandler clientHandler new ClientHandler(client,TCPServer.this);// 读取数据并打印clientHandler.readToPrint();// 添加同步处理synchronized (TCPServer.this) {clientHandlerList.add(clientHandler);}} catch (IOException e) {e.printStackTrace();System.out.println(客户端连接异常 e.getMessage());}异步转发 // 转发clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);在ClientHandler.java中上述代码所在的线程是主要线程会一直有消息进来所以不能做同步处理那样会导致当前线程阻塞从而导致后面进来的消息无法及时处理。
所以当 onNewMessageArrived将消息抛出去之后TCPServer.java的实现要采取异步转发的方式退给其他客户端。创建一个新的单例线程池来做转发的操作
新增转发线程池 // 转发线程池private final ExecutorService forwardingThreadPoolExecutor;public TCPServer(int port) {this.port port;this.forwardingThreadPoolExecutor Executors.newSingleThreadExecutor();}转发投递消息给其他客户端 /*** 转发消息给其他客户端* param handler* param msg*/Overridepublic void onNewMessageArrived(ClientHandler handler, String msg) {// 打印到屏幕System.out.println(Received- handler.getClientInfo() : msg);// 转发forwardingThreadPoolExecutor.execute(()-{synchronized (TCPServer.this){for (ClientHandler clientHandler : clientHandlerList){if(clientHandler.equals(handler)){// 跳过自己continue;}// 向其他客户端投递消息clientHandler.send(msg);}}});}防止客户端下线后依旧重复发送的问题
ClientHandler.java - ClientWriteHandler /*** 发送到客户端* param str*/void send(String str) {// 如果已经发送完成就返回if(done){return;}executorService.execute(new WriteRunnable(str));}聊天室Server/Client启动、测试
idea单个程序同时启动多个窗口的方法 启动main方法 勾选运行运行多个 保存退出就可以了
测试结果如下 先启动服务端再启动三个客户端 服务端和客户端发消息 服务端发送我是服务端 客户端发送客户端1、客户端2、客户端3 其中一个客户端退出不影响其他客户端和服务端发送消息
至此socket简易聊天室重构完成
源码下载
下载地址https://gitee.com/qkongtao/socket_study/tree/master/src/main/java/cn/kt/socket/SocketDemo_chatroom