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对象相关事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | ws.onopen | 连接建立时触发 |
message | ws.onmessage | 客户端接收到服务器发送的数据时触发 |
close | ws.onclose | 连接关闭时触发 |
error | ws.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的网络聊天室
已实现同一个用户不可在多端同时登录