1 简介
WebSocket是一种全双工通信协议,它允许客户端和服务器之间建立持久化的双向连接,从而在不频繁创建HTTP请求的情况下进行实时数据传输。与传统的HTTP协议相比,WebSocket更适合需要实时数据更新的应用场景,如聊天应用、实时股票行情、在线游戏等。
WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
WebSocket可以通过wss://
前缀实现加密通信,类似于HTTPS。由于WebSocket协议本身没有对数据的加密和身份验证机制,因此在使用时通常结合TLS/SSL进行加密,并通过认证机制确保通信的安全性。
1.1 工作原理
1) 握手阶段
WebSocket连接从客户端发起一个标准的HTTP请求开始,但这个请求的头部包含一些额外的字段,表示客户端希望升级到WebSocket协议。这是一个GET请求,并且包含以下几个重要的头部字段:
Upgrade: websocket
:表示客户端希望将这个连接升级为WebSocket。Connection: Upgrade
:与Upgrade
字段配合使用,表明连接类型的变化。Sec-WebSocket-Key
:一个由客户端生成的随机的Base64编码的key,服务器将用它来生成一个响应密钥以确认握手。Sec-WebSocket-Version
:表示WebSocket协议的版本,通常为13。
2) 服务器响应
如果服务器支持WebSocket,它将回应一个HTTP 101状态码,表示协议切换成功,并且在响应头部包含以下字段:
Upgrade: websocket
:确认协议升级。Connection: Upgrade
:与Upgrade
字段配合使用。Sec-WebSocket-Accept
:服务器根据客户端提供的Sec-WebSocket-Key
,加上一个固定的字符串,然后通过SHA-1加密并进行Base64编码后生成的值。这个值用于确认握手的有效性。
握手完成后,连接正式切换为WebSocket协议,客户端和服务器可以开始通过这个持久连接进行双向通信。
3) 数据帧传输
一旦连接建立,客户端和服务器之间的数据传输以帧的形式进行。WebSocket的数据帧由一个小的头部和可变长度的数据负载组成,帧可以携带文本数据(UTF-8编码的字符串)或二进制数据。WebSocket支持以下几种帧类型:
- 文本帧:用于传输文本数据。
- 二进制帧:用于传输二进制数据。
- 关闭帧:用于表示连接关闭。
- Ping帧:客户端或服务器发送Ping帧以测试连接的连通性。
- Pong帧:用于响应Ping帧。
4) 连接关闭
WebSocket连接可以由客户端或服务器任意一方发起关闭。关闭连接时,必须发送一个关闭帧,其中可以包含关闭码和关闭原因。收到关闭帧后,另一方应尽快发送自己的关闭帧并关闭连接。
1.2 WebSocket 优缺点分析
1)优点
- 低延迟:由于WebSocket连接是持久的,消除了频繁创建和关闭连接的开销,延迟较低,非常适合需要实时数据更新的应用。
- 全双工通信:与HTTP的请求-响应模式不同,WebSocket支持全双工通信,客户端和服务器可以同时发送和接收消息。
- 节省带宽:WebSocket在建立连接后没有HTTP协议的头部负载,可以节省带宽。
2) 缺点
- 复杂性:WebSocket的实现和调试可能比传统的HTTP协议要复杂,尤其是在处理连接状态、网络问题和安全性方面。
- 不适用于所有应用:对于一些不需要实时通信的应用,WebSocket可能显得过于复杂和不必要。
1.3 适用场景
- 实时聊天应用:WebSocket可以实现低延迟的消息传递,非常适合实时聊天。
- 实时推送:如新闻、体育比分、金融市场数据等。
- 多人在线游戏:需要低延迟的数据同步和通信。
- 协作编辑工具:如在线文档编辑、代码协作等,WebSocket可以实现实时的内容同步。
对比 HTTP:
- HTTP适用于大多数Web应用和API场景,其简单的请求-响应模型、无状态特性和广泛的工具支持,使其成为Web开发的标准选择。
- WebSocket则适合需要实时、双向数据传输的应用场景,如聊天、在线游戏和金融数据推送。虽然实现上更为复杂,但其在特定场景下的性能和效率优势是HTTP无法比拟的。
详细比较:
- **通信模式:**HTTP 是请求-响应模式,单项且无状态的;WebSocket 是全双工通信,双向且持续连接。
- **连接管理:**HTTP通常是短连接,每次请求都重新建立连接;WebSocket是长连接,握手后保持连接,适合频繁数据交换。
- **使用场景:**HTTP适用于网页加载、API调用等不需要实时更新的场景;WebSocket适用于实时聊天、在线游戏等需要低延迟、实时数据更新的应用。
HTTP和WebSocket底层都是TCP连接。
2 Demo 实例
1) 创建 SpringBoot 项目
2) 引入 Maven 依赖
<!--导入websocket依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
3) 导入前端页面
<!DOCTYPE HTML>
<html><head><meta charset="UTF-8"><title>WebSocket Demo</title></head><body><input id="text" type="text" /><button onclick="send()">发送消息</button><button onclick="closeWebSocket()">关闭连接</button><div id="message"></div></body><script type="text/javascript">var websocket = null;var clientId = Math.random().toString(36).substr(2);//判断当前浏览器是否支持WebSocketif('WebSocket' in window){//连接WebSocket节点websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);}else{alert('Not support websocket')}//连接发生错误的回调方法websocket.onerror = function(){setMessageInnerHTML("error");};//连接成功建立的回调方法websocket.onopen = function(){setMessageInnerHTML("连接成功");}//接收到消息的回调方法websocket.onmessage = function(event){setMessageInnerHTML(event.data);}//连接关闭的回调方法websocket.onclose = function(){setMessageInnerHTML("close");}//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function(){websocket.close();}//将消息显示在网页上function setMessageInnerHTML(innerHTML){document.getElementById('message').innerHTML += innerHTML + '<br/>';}//发送消息function send(){var message = document.getElementById('text').value;websocket.send(message);}//关闭连接function closeWebSocket() {websocket.close();}</script>
</html>
4) 编写后端 websocket 服务类
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {// 存放会话对象private static Map<String, Session> sessionMap = new HashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {System.out.println("客户端:" + sid + "建立连接");sessionMap.put(sid, session);}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message, @PathParam("sid") String sid) {System.out.println("收到来自客户端:" + sid + "的信息:" + message);}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(@PathParam("sid") String sid) {System.out.println("连接断开:" + sid);sessionMap.remove(sid);}/*** 群发*/public void sendToAllClient(String message) {Collection<Session> sessions = sessionMap.values();for (Session session : sessions) {try {// 服务器向客户端发送消息session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}
}
5)编写 websocket 配置类
这个配置类的主要作用是启用WebSocket支持。当Spring容器启动时,它会自动扫描所有标记了@ServerEndpoint
注解的类,并将它们注册为WebSocket端点,允许客户端通过这些端点进行WebSocket通信。 如果你在Spring Boot项目中使用内置的Web容器(如Tomcat、Jetty等),并且使用WebSocket,则需要这个ServerEndpointExporter
来将WebSocket端点自动注册到容器中。
@Configuration
public class WebSocketConfiguration {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
6) 定时任务模拟后端发送给用户端消息
@Component
public class WebSocketTask {@Autowiredprivate WebSocketServer webSocketServer;/*** 通过websocket每隔五秒向客户发送信息*/@Scheduled(cron = "0/5 * * * * ?")public void sendMessageToClient() {webSocketServer.sendToAllClient("这是来自服务端的消息" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));}
}
3 结果展示
前端结果:
除了自己能主动给后端发送信息以外,前段每隔五秒还能收到后端发来的信息。
后端结果:
服务端自动接收前段发来的信息,同时会在定时任务中,每隔五秒给前端发送一条信息。
代码获取:https://gitee.com/strivezhangp/java-study-log.git 切换到相关分支即可拉取。