WebSocket通信是使用WebSocket的通信协议,它在2011年被IETF标准化为RFC 6455。WebSocket协议提供了一个在单个TCP连接上进行全双工通信的渠道,允许客户端和服务器之间保持持久连接,支持双向数据传输,使得服务器和客户端之间可以发送实时的消息。
传统的Web应用中,为了实现实时更新,通常使用的技术如轮询(Polling)、长轮询(Long Polling)或Comet技术等,这些方法效率较低且实时性较差。相比之下,WebSocket通过一次握手后建立持久连接,允许服务器主动向客户端推送数据,极大地减少了延迟,并提高了效率。
WebSocket协议的设计初衷是为了替代HTTP在需要低延迟、双向通信的应用场景中的使用,但它并不是直接基于HTTP协议,而是在建立连接时通过HTTP协议进行一次握手请求。一旦握手成功,HTTP协议就会升级为WebSocket协议,之后的通信就不再依赖于HTTP了。
特点:
- 全双工通信,支持服务器主动向客户端发送消息。
- 低延迟,适合实时应用。
- 基于TCP,提供了可靠的传输通道。
- 支持跨域通信。
- 支持TLS/SSL加密,确保安全传输。
适用场景:
- 实时通信应用(如聊天、视频会议)。
- 在线游戏和多人互动应用。
- 实时数据分析和监控系统。
- 如在线文档编辑、项目管理工具等。
1、WebSocket和Spring WebSocket理解
Spring WebSocket模块支持使用WebSocket协议进行通信。通过Spring WebSocket支持,开发者可以轻松地在他们的应用中实现WebSocket功能,包括处理客户端和服务器之间的实时双向通信。
简单理解:
Spring WebSocket是Spring的一个模块,用于实现WebSocket相关功能,同时加扩展了WebSocket相关功能。
Spring框架不仅简化了WebSocket协议的使用,还提供了一种兼容性解决方案——SockJS,用于在不支持WebSocket协议的环境中模拟WebSocket行为。此外,Spring还提供了STOMP协议的支持,使得消息可以在客户端和服务器之间更加结构化地传递。
Spring WebSocket特点:
- WebSocket协议:这是基础的全双工通信协议,允许服务器和客户端进行持续的交互,无需发起新的HTTP请求。
- SockJS:为了解决浏览器或网络环境不支持WebSocket的情况,Spring提供了SockJS支持,它可以在WebSocket不可用时,自动降级到其他技术如AJAX长轮询等。
- STOMP协议:简单文本导向的消息传输协议(Simple Text Oriented Messaging Protocol),用于定义消息如何在客户端和服务器间交换。Spring WebSocket模块可以通过STOMP提供更高级的消息传递模式,比如订阅/发布模式。
综上所述:
WebSocket是一个底层的通信协议,用于实现实时双向通信。Spring WebSocket是一个基于 Spring 框架的模块,主要用于实现WebSocket协议通信,并通过SockJS和STOMP增强了其功能性和灵活性。如果你正在构建一个Spring Boot应用,并需要WebSocket功能,Spring WebSocket是一个很好的选择。
2、WebSocket工作原理
建立WebSocket连接的过程涉及从HTTP请求开始,通过一次握手升级到WebSocket协议,然后在客户端和服务器之间建立一个持久的TCP连接进行双向通信。
示例图:
从TCP三次握手开始,我们来解释下WebSocket的工作原理。
(1)、TCP的3次握手
在WebSocket连接建立之前,客户端和服务端之间首先需要通过TCP握手来建立一个可靠的连接。
TCP三次握手过程:
1、SYN:客户端发送一个带有SYN(同步序列编号)标志的数据包到服务器,表示请求建立连接。
2、SYN-ACK:服务器接收到SYN包后,发送一个带有SYN和ACK(确认字符)标志的数据包作为回应,表示同意建立连接并确认收到了客户端的请求。
3、ACK:客户端收到SYN-ACK包后,发送一个带有ACK标志的数据包给服务器,确认收到了服务器的响应,并完成连接的建立。
此时,TCP连接已经建立,接下来可以进行更高层次的协议通信。
(2)、发送HTTP请求与WebSocket协议升级
在TCP连接建立之后,客户端会发起一个HTTP请求,请求将连接升级为WebSocket协议。
客户端发起的HTTP请求示例:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
解释:
- Upgrade: websocket和Connection: Upgrade:表示客户端希望将连接升级到WebSocket协议。
- Sec-WebSocket-Key:由客户端生成的一个随机字符串,经过Base64编码,用于验证握手过程的安全性。
- Sec-WebSocket-Version:使用的WebSocket协议版本。
- Origin:指示请求来源,防止跨域攻击。
服务器返回的HTTP响应示例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
解释:
- 101 Switching Protocols:表示服务器同意升级协议。
- Upgrade: websocket和Connection: Upgrade:确认升级到WebSocket协议。
- Sec-WebSocket-Accept:服务器使用Sec-WebSocket-Key和一个固定的GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)计算出的一个SHA-1哈希值,并进行Base64编码。这一步用于确保握手的安全性和完整性。
(3)、WebSocket数据帧交换
一旦升级握手成功,TCP连接就被“升级”为WebSocket连接,客户端和服务器可以通过已建立的TCP连接交换数据帧。
数据帧的基本结构:
每个WebSocket数据帧由以下几个部分组成。
- FIN: 1位,是否为最后一帧。
- RSV1, RSV2, RSV3: 各1位,保留位,默认为0。
- Opcode: 4位,操作码,指示帧的数据类型或目的。
- 0x0: 续帧
- 0x1: 文本帧 (Text)
- 0x2: 二进制帧 (Binary)
- 0x8: 关闭连接
- 0x9: ping
- 0xA: pong
- MASK: 1位,掩码标志,客户端发给服务器的消息必须被掩码处理。
- Payload length: 7位、7+16位或7+64位,表示有效载荷数据的长度。
- Masking-key: 如果MASK标志设置为1,则接下来的4字节是掩码密钥。
- Payload data: 实际的有效载荷数据。
数据帧示例:发送文本消息"Hello"
1、FIN设置为1,因为这是一个完整的消息。
2、Opcode设置为0x1,因为这是一个文本帧。
3、MASK设置为1,并生成一个随机的4字节掩码。
4、Payload length设置为5(因为"Hello"包含5个字符)。
5、Masking-key是随机生成的4字节值。
6、Payload data是"Hello",但在发送前需要根据掩码加密。
完整示例:
FIN: 1
RSV1, RSV2, RSV3: 0
Opcode: 0x1
MASK: 1
Payload length: 5
Masking-key: <random 4-byte key> // 4个字节数据
Payload data: Hello // 是经过Masking-key加密处理后的数据,明文是Hello
(4)、关闭连接
当一方决定关闭连接时,它会发送一个关闭帧。
关闭帧示例:
FIN: 1
RSV1, RSV2, RSV3: 0
Opcode: 0x8 (关闭帧)
MASK: 1 (如果是由客户端发送)
Payload length: 2 (关闭原因代码)
Payload data: 1000 (例如,正常关闭)
接收方接收到关闭帧后,也会发送一个关闭帧作为确认,并关闭TCP连接。
(5)、工作原理总结
1、TCP三次握手
- 客户端发送SYN。
- 服务器响应SYN-ACK。
- 客户端发送ACK,建立TCP连接。
2、HTTP请求与WebSocket协议升级
- 客户端发送带有特定头信息的HTTP请求,请求升级到WebSocket协议。
- 服务器检查请求,如果支持WebSocket并同意升级,则返回一个确认响应。
3、WebSocket数据帧交换
- 握手成功后,客户端和服务器通过已建立的TCP连接交换WebSocket数据帧,实现全双工通信。
4、关闭连接
- 当一方决定关闭连接时,发送关闭帧,另一方确认并关闭TCP连接。
这种机制确保了WebSocket能够在保持低延迟的同时提供高效的双向通信能力,非常适合实时应用如在线聊天、游戏、股票交易等场景。
3、 WebSocket和STOMP
WebSocket是一个低级别的协议,提供了一个双向通信的管道。STOMP是一种高层级的消息传递协议,可以运行在WebSocket或其他传输协议(如TCP)之上。通过WebSocket传输STOMP消息,可以在客户端和服务器之间实现更加结构化的消息传递模式,比如发布/订阅、点对点消息等。
(1)、STOMP消息报文格式
STOMP消息由命令行、头信息和消息体组成。每条消息以换行符\n分隔各个部分,并且整个消息以空行结束(即两个连续的换行符\n\n)。
示例:发送一条STOMP CONNECT命令
CONNECT
accept-version:1.2
host:stomp.example.com
^@
解释:
- CONNECT:这是STOMP命令,表示客户端请求与服务器建立连接。
- accept-version:1.2:指定支持的STOMP版本。
- host:stomp.example.com:指定目标主机。
- ^@:表示消息体为空,用一个空字节(ASCII码为0)来表示。
示例:发送一条SUBSCRIBE命令
SUBSCRIBE
id:sub-001
destination:/topic/greetings
^@
解释:
- SUBSCRIBE:表示客户端订阅某个主题。
- id:sub-001:订阅的唯一标识符。
- destination:/topic/greetings:要订阅的主题地址。
示例:发送一条SEND命令
SEND
destination:/queue/work
Hello, STOMP!
^@
解释:
- SEND:表示发送消息到指定的目的地。
- destination:/queue/work:消息的目的地。
- Hello, STOMP!:消息的内容。
(2)、在WebSocket上发送STOMP消息
当在一个已经建立的WebSocket连接上发送STOMP消息时,这些STOMP消息会被作为WebSocket数据帧的有效载荷数据发送。
具体来说:
1、WebSocket数据帧
- WebSocket数据帧包含头部信息(如FIN、Opcode、MASK等)和有效载荷数据。
- 对于STOMP消息,WebSocket的有效载荷数据就是完整的STOMP消息内容(包括命令行、头信息和消息体)。
2、混合发送WebSocket和STOMP消息
- WebSocket允许在同一连接上交替发送不同类型的WebSocket数据帧。因此,在同一WebSocket连接上,既可以发送普通的WebSocket消息,也可以发送STOMP消息。
- 通常情况下,一旦建立了WebSocket连接并升级为STOMP协议,后续的大部分通信会使用STOMP消息格式来进行结构化的消息传递。
(3)、网络数据封装说明
使用WebSocket发送STOMP消息,实际是将STOMP消息作为WebSocket数据帧的负载。实际上在往底层深入,也是同样的道理,如WebSocket数据帧实际上也是TCP数据包的负载(可能是多个)。每个TCP数据包又被转换为其他物理信号包进行物理传输。
具体关系如下:
1、应用层(Application Layer)
- STOMP消息:这是你要发送的实际消息内容,包含命令、头信息和消息体。
2、传输层(Transport Layer) - WebSocket数据帧:STOMP消息作为WebSocket数据帧的有效载荷部分进行传输。WebSocket提供了一个全双工的双向通信通道。
3、网络层(Network Layer) - TCP数据包:WebSocket数据帧被分割成多个TCP数据包进行网络传输。TCP协议确保这些数据包能够可靠地到达目的地,并按顺序重组。
4、链路层(Link Layer) - 以太网帧(或其他链路层协议):TCP数据包被封装在以太网帧或其他链路层协议的数据包中,通过物理网络进行传输。
以太网帧包括以下部分: - 目标MAC地址和源MAC地址:标识通信双方的硬件地址。
- 类型/长度字段:指示上层协议类型(如IP)。
- 数据段:实际要传输的数据,即TCP数据包。
示例以太网帧:
<destination MAC> <source MAC> <type/length> <TCP packet>
最终网络传输:
最终在网络上传输的是以太网帧,这些帧包含了封装好的TCP数据包,而TCP数据包又包含了WebSocket数据帧,最后WebSocket数据帧中包含了STOMP消息。
4、客户端代码示例
假设你已经通过WebSocket建立了连接,并希望使用STOMP发送和接收消息:
(1)、建立WebSocket连接
代码示例:(js脚本)
const socket = new WebSocket('ws://example.com/stomp-endpoint');
(2)、发送STOMP CONNECT命令
代码示例:(js脚本)
socket.send(CONNECT\naccept-version:1.2\nhost:example.com\n\n\u0000);
(3)、发送STOMP SUBSCRIBE命令
代码示例:(js脚本)
socket.send(SUBSCRIBE\nid:sub-001\ndestination:/topic/greetings\n\n\u0000);
(4)、发送STOMP SEND命令
代码示例:(js脚本)
socket.send(SEND\ndestination:/queue/work\n\nHello, STOMP!\n\u0000);
(5)、接收STOMP消息
代码示例:(js脚本)
socket.onmessage = function(event) {
console.log('Received:', event.data);
};
5、服务端代码示例
(1)、依赖项
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.2</version>
</dependency>
(2)、发送消息代码示例
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
public class WebSocketExample extends WebSocketClient {
public WebSocketExample(URI serverUri) {
super(serverUri);
}
@Override
public void onOpen(ServerHandshake handshakedata) {
System.out.println("Connected to server");
// 发送消息到服务器
send("Hello, Server!");
}
@Override
public void onMessage(String message) {
System.out.println("Received from server: " + message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("Connection closed: " + reason);
}
@Override
public void onError(Exception ex) {
ex.printStackTrace();
}
public static void main(String[] args) {
try {
// 创建WebSocket客户端并连接到服务器
WebSocketExample client = new WebSocketExample(new URI("ws://localhost:8080"));
client.connect();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
说明:
- onOpen:当连接成功时调用,可以在这里发送消息。
- onMessage:当接收到服务器的消息时调用。
- onClose:当连接关闭时调用。
- onError:当发生错误时调用。
接收消息:
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/websocket")
public class WebSocketConsumer {
@OnMessage
public void onMessage(String message) {
System.out.println("Received message: " + message);
// 回复消息
try {
// 这里可以回复消息给客户端
} catch (IOException e) {
e.printStackTrace();
}
}
}
6、总结
- WebSocket提供底层的通信管道:WebSocket建立连接后,可以通过它发送任意类型的数据帧。
- STOMP 提供高层级的消息协议:在WebSocket之上使用STOMP,可以实现更结构化的消息传递机制。
- 混合发送WebSocket和STOMP消息:WebSocket支持在同一连接上发送不同类型的消息,但实际应用中,一旦切换到STOMP协议,大多数消息将遵循STOMP格式。
这种方式使得WebSocket成为了一个灵活的基础通信层,而STOMP则提供了方便的消息管理和路由功能。
7、扩展一下:通信类型分类
根据通信过程中数据传输的方向和方式,通信类型可以分为以下几种:
1、单工通信(Simplex Communication)
单工通信是指数据只能在一个方向上传输,即只能由发送方发送数据,接收方只能接收数据,不能反向发送数据。
特点:
- 数据传输是单向的。
- 发送方和接收方的角色固定,不能互换。
- 适用于只需要单向传输的应用场景。
适用场景:
- 广播电台或电视台:信号从广播站发送到接收设备,接收设备不能发送信号回广播站。
- 早期的对讲机系统:某些对讲机系统只允许一方发送,另一方接收。
2、半双工通信(Half-Duplex Communication)
半双工通信是指数据可以在两个方向上传输,但不能同时进行。也就是说,发送方和接收方不能在同一时间发送和接收数据,必须轮流进行。
特点:
- 数据传输是双向的,但不能同时进行。
- 发送方和接收方需要协调,确保一方发送时另一方处于接收状态。
- 相比全双工通信,带宽利用率较低,因为每次只能在一个方向上传输数据。
适用场景:
- 对讲机:双方可以互相通话,但一次只能有一方说话,另一方倾听。
- 早期的以太网(Ethernet):某些早期的以太网设备使用半双工模式,一次只能有一个设备发送数据。
- Wi-Fi:在某些情况下,Wi-Fi设备可能会使用半双工模式,尤其是在共享同一信道的情况下。
3、全双工通信(Full-Duplex Communication)
全双工通信是指通信双方可以在同一时间进行双向数据传输。发送方和接收方可以同时发送和接收数据,而不会相互干扰。
特点:
- 数据传输是双向的,并且可以同时进行。
- 无需等待对方完成发送,双方可以随时发送数据。
- 带宽利用率高,因为数据可以在两个方向上同时传输。
适用场景:
- 现代以太网(Ethernet):大多数现代以太网设备支持全双工通信,允许多个设备同时发送和接收数据。
- WebSocket:WebSocket协议允许服务器和客户端之间保持持久连接,并支持双向数据传输。
- 实时应用:如在线聊天、视频会议、在线游戏等,这些应用要求低延迟和高并发的数据传输。
4、准双工通信(Pseudo-Duplex Communication)
准双工通信是一种介于半双工和全双工之间的通信模式。它允许数据在两个方向上传输,但在某些情况下,数据传输并不是完全同时的。通常,准双工会在短时间内实现双向传输,但并不是严格意义上的全双工。
特点:
- 数据传输是双向的,但可能不是完全同时进行。
- 在某些时间段内,数据可以在两个方向上同时传输,但在其他时间段内,可能需要轮流传输。
- 准双工通信通常用于资源受限的环境,或者在网络带宽有限的情况下。
适用场景:
- 某些无线通信协议:如蓝牙(Bluetooth),在某些情况下可能会使用准双工模式来节省能源。(省点模式下是半双工模式,电量充足时为全双工模式)
- 一些低功耗设备:为了节省电力,某些设备可能会采用准双工模式,减少同时传输的需求。
5、通信类型的对比
6、通信类型总结
- 单工通信:数据只能在一个方向上传输,适用于只需要单向传输的场景。
- 半双工通信:数据可以在两个方向上传输,但不能同时进行,适用于需要双向通信但对延迟要求不高的场景。
- 全双工通信:数据可以在两个方向上同时传输,适用于实时性要求高的应用场景,如在线聊天、视频会议等。
- 准双工通信:介于半双工和全双工之间,适用于资源受限或带宽有限的环境。
乘风破浪!Dare to Be!!!