Socket网络编程(六)——简易聊天室案例

目录

  • 聊天室数据传输设计
    • 客户端、服务器数据交互
    • 数据传输协议
    • 服务器、多客户端模型
    • 客户端如何发送消息到另外一个客户端
    • 2个以上设备如何交互数据?
  • 聊天室消息接收实现
    • 代码结构
    • client客户端重构
    • server服务端重构
      • 自身描述信息的构建
      • 重构TCPServer.java
      • 基于synchronized 解决多线程操作的安全问题
    • 聊天室Server/Client启动、测试
    • 源码下载

聊天室数据传输设计

  • 必要条件:客户端、服务器
  • 必要约束:数据传输协议
  • 原理:服务器监听消息来源、客户端链接服务器并发送消息到服务器

客户端、服务器数据交互

20240229-145006-Go.png

client 发送消息到服务器端,服务器端回复消息也就是回送消息。

数据传输协议

20240229-145145-zG.png

数据在传输的时候,需要在尾部追加换行符,也就是说原来5个字节的数据,在实际传输时,是有6个字节长度的。

服务器、多客户端模型

20240229-145351-SU.png
在客户端有多个情况下,客户端都会向服务器端进行发送消息;想要在PC发送消息给服务器端时,也让安卓、平板等终端都能收到,其操作应该是,当PC端发送一条消息到服务器端之后,服务器端得到该数据后,它会把这条数据发送(回送)给当前连接的客户端。而这些当前连接的客户端收到这条消息后,就实现了把PC消息发送到手机的过程。

20240229-145449-mB.png

客户端如何发送消息到另外一个客户端

每个客户端都是服务器也是客户端?
答:不是

2个以上设备如何交互数据?

答:约定一个基础的数据格式,这里使用回车换行符来作为信息的截断
客户端-服务器-转发到客户端,如下图:
20240229-145850-t9.png

User1发送消息到服务端,服务端将消息转发给其他的客户端(比如User2),从而实现聊天室的功能

聊天室消息接收实现

代码结构

20240229-170606-4z.png

代码分为四个module,分别为clink、constants、client、server。

  • clink:该module为提供工具类进行校验与流处理。
  • constants:基础的共用类代码
  • server:服务端代码,需要依赖 clink、constants两个module
  • client:客户端代码,需要依赖 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单个程序同时启动多个窗口的方法:

  1. 启动main方法
    20240301-171559-Pt.png

  2. 勾选运行运行多个
    20240301-171650-l3.png

  3. 保存退出就可以了

测试结果如下:

  1. 先启动服务端,再启动三个客户端
    20240301-171752-6g.png
    20240301-171809-S0.png

  2. 服务端和客户端发消息
    服务端发送:我是服务端
    客户端发送客户端1、客户端2、客户端3
    20240301-171954-5i.png
    20240301-172007-It.png

  3. 其中一个客户端退出,不影响其他客户端和服务端发送消息
    20240301-172133-bs.png

至此,socket简易,聊天室重构完成

源码下载

下载地址:https://gitee.com/qkongtao/socket_study/tree/master/src/main/java/cn/kt/socket/SocketDemo_chatroom

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/505061.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM 第四部分—垃圾回收相关概念 2

System.gc() 在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存 然而System.gc()调用附带一个免责声明,无法保证对垃…

LLC谐振变换器变频移相混合控制MATLAB仿真

微❤关注“电气仔推送”获得资料(专享优惠) 基本控制原理 为了实现变换器较小的电压增益,同时又有较 高的效率,文中在变频控制的基础上加入移相控制, 在这种控制策略下,变换器通过调节一次侧开关管 的开关…

Mybatis | 动态SQL

目录: 动态SQL中的 “元素” :\<if>元素\<choose>、\<when>、\<otherwise>元素\<where>、\<trim>元素\<set>元素\<foreach>元素\<bind>元素 作者简介 &#xff1a;一只大皮卡丘&#xff0c;计算机专业学生&#xff0c;正…

探索浏览器录屏Web API 接口的应用前景与限制

title: 探索浏览器录屏web api 接口的应用前景与限制 date: 2024/3/2 15:38:51 updated: 2024/3/2 15:38:51 tags: 录屏流程简化实时录制传输跨平台兼容隐私保护问题浏览器兼容性数据处理存储替代方案探索 一、浏览器录屏Web API 接口的优点&#xff1a; 简化录屏流程&#x…

Springboot+vue的商业辅助决策系统的设计与实现(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的商业辅助决策系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的商业辅助决策系统的设计与实现&#xff0c;采…

【Easyx】easyx从入门到精通 — 初步入门

easyx 初步入门 1 安装easyx图形库2 如何使用Easyx3 效果初试4 基本图形绘制4.1 绘制点4.2 绘制直线4.3 绘制圆形4.4 绘制矩形4.5 绘制椭圆4.6 绘制圆角矩形4.7 绘制扇形 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇…

【前端素材】推荐优质电商类后台管理系统网页Vuesax平台模板(附源码)

一、需求分析 在线后台管理系统是指供管理员或运营人员使用的Web应用程序&#xff0c;用于管理和监控网站、应用程序或系统的运行和数据。它通常包括一系列工具和功能&#xff0c;用于管理用户、内容、权限、数据等。下面是关于在线后台管理系统的详细分析&#xff1a; 1、功…

产品经理学习-产品运营《社群活跃度打造》

目录&#xff1a; 社群运营普遍问题 社群是否需要活跃 提升活跃的方法 衡量社群的3个标准 社群运营普遍问题 在做社群运营的时候通常会进入一个相似的循环&#xff0c;拉群后会活跃一段时间变成广告群&#xff0c;不断的发商品链接、广告&#xff0c;一段时候后社群变成了一…

GWO优化高斯回归预测(matlab代码)

GWO-高斯回归预测matlab代码 GWO&#xff08;Grey Wolf Optimizer&#xff0c;灰狼优化算法&#xff09;是一种群智能优化算法&#xff0c;由澳大利亚格里菲斯大学的Mirjalili等人于2014年提出。这种算法的设计灵感来源于灰狼群体的捕食行为&#xff0c;其核心思想在于模仿灰狼…

Android ANR 日志分析定位

ANR 是 Android 应用程序中的 "Application Not Responding" 的缩写&#xff0c;中文意思是 "应用程序无响应"。这是当应用程序在 Android 系统上运行时&#xff0c;由于某种原因不能及时响应用户输入事件或执行一个操作&#xff0c;导致界面无法更新&…

node.js最准确历史版本下载

先进入官网:Node.js https://nodejs.org/en 嫌其他博客多可以到/release下载:Node.js,在blog后面加/release https://nodejs.org/en/blog/release/ 点击next翻页,同样的道理

【打工日常】使用docker部署在线Photopea用于linux下替代ps

一、Photopea介绍 linux没有ps适配&#xff0c;对于有时候工作来说确实不方便&#xff0c;我找了很久&#xff0c;才找到了一款功能可以跟ps接近的在线软件&#xff0c;使用docker部署就可以了。它是ps的最佳替代品之一&#xff0c;其界面几乎与ps相同&#xff0c;只不过它是在…