当前位置: 首页 > news >正文

WebSocket实战:打造实时在线聊天室

一、websocket的概念

我们应该了解的是websocket是ISO参考模型的应用层的协议之一

它是一种在TCP连接上进行全双工也就是双向通信的协议。有了这个协议,客户端和服务器之间就可以随时互相发送数据,不再像传统HTTP那样一问一答了也就是请求响应。当客户端和服务器的连接一旦建立,不手动关闭或者设置自动关闭就会一直保持。

  • 全双工:允许数据在两个方向同时进行传输
  • 半双工:允许数据在两个方向上传输,但是某个时间段内只允许一个方向的传输

二、为什么我们需要WebSocket?

比如我们想要实现网上聊天室或者网上棋类在线对战游戏

HTTP 是“请求-响应”模式: 客户端发起请求,服务器被动响应。

但这种模式有几个问题:

问题举例结果
无法实时比如股票价格变化,用户得不断刷新页面才看到新数据用户体验差
请求多、浪费资源比如每隔500ms轮询一次服务器增加带宽、服务器压力
响应延迟服务端不能主动通知客户端数据总是滞后

轮询

如果我们使用websocket

连接过程如下

同理客户端2的websocket的建立流程也如上图,握手后就“切换”成 WebSocket 协议,不再是 HTTP 请求

基于websocket的网上五子棋对战的大致流程

WebsocketAPI

客户端浏览器API(了解即可)

1.websocket对象创建(javascript)

let was = new WebSocket(URL)

// URL 说明

// 格式 : 协议://ip地址/访问路径

// 协议 : 协议名称为ws 也就是websocket

2.websocket对象相关事件
事件事件处理程序描述
openws.onopen连接建立时触发
messagews.onmessage客户端接收到服务器发送的数据时触发
closews.onclose连接关闭时触发
errorws.onerror连接异常时触发
3.websocket对象提供的方法

ws.send() 通过websocket对象调用该方法发送数据给服务端(前后端连接使用)

<script>let ws = new WebSocket("ws://localhost/chat");ws.onopen = function(){console.log("connect successfully");}ws.onmessage() = function(event){//通过event.data可以获取到服务器发送给客户端的数据console.log("event data:" + event.data);}ws.onerror() = function(){console.log("error connection");}ws.onclose() = function(){console.log("close connection: ");}</script>    

服务端后端API

Java WebSocket 应用程序由一系列的Endpoint组成。Endpoint是一个Java类,代表WebSocket连接的一段,对于服务端,我们可以认为是处理具体WebSocket消息的接口。

我们可以通过两种方式定义Endpoint:

  • 编程式:继承类javax.websocket.Endpoint并实现其方法        
  • 注解式:即定义一个POJO,并添加@ServerEndpoint相关注解

Endpoint具有一定的生命周期我们并不深入研究

在其声明周期中有以下方法可使用:

方法描述注解
onOpen()当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法@OnOpen
onClose()当会话关闭时调用@OnClose
onError()当连接过程异常时调用@OnError

服务端如何接受客户端发送的数据呢?

  • 编程式:通过添加MessageHandler消息处理器来接受消息 (了解即可)      
  • 注解式:在定义的Endpoint时,通过@OnMessage注解指定接收消息的方法

服务端如何推送数据给客户端呢?

发送消息则由RemoteEndpoint完成,其实例由Session维护

  • 通过session.getBasicRemote获取同步消息发送的实例,然后调用sendXxx()方法发送消息
  • 通过session.getAsyncRemote获取异步消息发送实例,然后调用sendXxx()方法发送消息
@ServerEndpoint("/chat")
//在@ServerEndpoint注解中加入路径
@Component
public class ChatEndpoint {@OnOpen//连接建立时被调用//这里的Session是WebSocket的Session而非Http的HttpSessionpublic void onOpen(Session session, EndpointConfig config){}@OnMessage//message接收到客户端发来的信息public void onMessage(String message){}@OnClosepublic void onClose(Session session){}
}

在线聊天室实现

具体流程分析

消息格式

客户端发给服务端

Json格式

{"toName":"zjk" ,  "message" : "你好"}

服务端发给客户端

  • 系统消息格式:

{"system":true , "fromName": null , "message":["张三","王五"]} 

  • 推送给某一个用户的消息格式:

{"system":false, "fromName": "张三", "message":"你好"}

代码实现

引入必要的依赖坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

编写配置类

在 Spring 框架中,@Bean 是一个用于告诉 Spring 容器 我要自己手动创建一个对象并交给你管理的注解。

它常用于配合 @Configuration 注解的类中,用来显式声明一个 Spring 容器中的 Bean,用于方法上,方法返回值被注册为 Bean。

@Component注解是一个用于告诉Spring容器 你帮我来创建一个对象并管理,Spring 自动扫描并创建,类被扫描注册为 Bean。

/*** WebSocket配置类* @author 赵家康* @date 2025/7/8*/
@Configuration
public class WebSocketConfig {@Bean//注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解public ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}

编写配置类,用于获取Httpsession对象

/*** 配置类* 用于从 ServerEndpointConfig 中 获取 httpSession 对象*/
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request,HandshakeResponse response) {HttpSession httpSession = (HttpSession)request.getHttpSession();//将 httpsession 存入 ServerEndpointConfig对象,方便于websocket在OnOpen()建立链接后获取sec.getUserProperties().put(HttpSession.class.getName(),httpSession);}
}

在@ServerEndpoint注解中引入配置器

@ServerEndpoint(value = "/caht" , configurator = GetHttpSessionConfigurator.class)

什么是 ServerEndpointExporter

它是 Spring Boot 内嵌容器 + WebSocket 结合使用时必须的一个类

  • 它会在应用启动时扫描所有使用了 @ServerEndpoint("/xxx") 注解的类;

  • 并把这些类注册成真正的 WebSocket 端点(endpoint);

  • 也就是说,没有它,Spring Boot 不知道你的 @ServerEndpoint 类是干啥的,WebSocket 就无法正常工作。

项目效果

项目gitee地址:赵家康/基于WebSocket的网络聊天室

已实现同一个用户不可在多端同时登录

http://www.dtcms.com/a/269645.html

相关文章:

  • NealFun安卓版:创意无限,娱乐至上
  • 学习设计模式《十七》——状态模式
  • 干货分享 | TSMaster DBC编辑器操作指南:功能详解+实战示例
  • Spring Boot 事务失效问题详解:原因、场景与解决方案
  • Spring Boot + Easy Excel 自定义复杂样式导入导出
  • [Swarm] Result对象 | 智能体切换 | Response对象 | muduo review
  • Android.mk拷贝文件、文件夹
  • 5 种备份和恢复安卓短信的方法
  • 音频主动降噪技术
  • 快手播放量是什么意思?浏览量等于播放量吗
  • Spring注解驱动开发
  • Rust 的 Copy 语义:深入浅出指南
  • 广度优先与深度优先遍历核心逻辑理解及实践
  • Java零基础笔记07(Java编程核心:面向对象编程 {类,static关键字})
  • CompareFace人脸识别算法环境部署
  • 项目进度受外包团队影响,如何管控交付节奏
  • 原生屏幕旋转算法(AccelSensor)
  • C++STL详解(一):string类
  • 分布式理论:CAP、Base理论
  • 【机器学习深度学习】为什么分类任务中类别比例应接近 1:1?
  • gloo 多卡训练
  • MiniMind:3小时训练26MB微型语言模型,开源项目助力AI初学者快速入门
  • CANDENCE 17.4 进行元器件缓存更新
  • Python爬虫实战:研究phonenumbers工具相关技术
  • Git 提交规范-备忘
  • 【STM32】ADC模数转换基本原理
  • EtherCAT与Profinet协议转换在工业自动化中的应用:以汇川伺服驱动器为例
  • 【FR801xH】富芮坤FR801xH之全功能按键案例
  • JVM系列六:JVM性能调优实战指南
  • Java基础回顾(1)