对websocket的理解
websocket,也是应用层的网络通信协议,要理解它是什么,就要问为什么会出现这个协议。
在没有websocket以前,我们都是用http协议做数据收发,渐渐的,我们发现有一些场景http的特性无法很好地处理。例如,服务端想主动发送给客户端信息的时候怎么办?
例如,我们的在线游戏,音视频通讯,都需要即时地和服务器做信息交互。
如果用http来处理这个场景,就涉及到客户端方面的轮询。
但是轮询的逻辑是按照一定的频率,客户端不断地发送请求包,去看服务端是否“有话对我们说”。
这带来了两个主要的问题:
第一,服务端何时发送数据是不确定的,长时间持续地发送请求查询,其中大量的请求是无效的,网络利用率低下。
第二,对于需要实时发送的信息,轮询只能周期性地获取,如果服务端想要发送信息,需要等到下一个轮询周期才可以,会造成信息的延迟。如果是在线游戏,视频聊天什么的,用户当天就会打个差评再卸载掉。
所以,关键是要做到当服务端想发送的时候,就立刻主动地发送信息。
而websocket就能达成这一功能,它的特性是全双工通信,此外还有低延迟,数据包传输效率高的特性。这些特性都是相较于http协议来说的,你可以这么认为,想要专门应对需要高效全双工交互的应用场景,但http表示臣妾做不到啊,所以你新纳一个嫔妃websocket,专门帮你干这些活儿。
如果之前除了http协议以外没有接触过应用层协议,那么一开始是难以接受websocket概念的。为了更好地理解,需要跟http做一些关键的对比。
- http是请求-响应模式,必须要有请求,才有回复(且服务端不能主动请求)。websocket是全双工通信的,有信息就主动传达给对方。
- http短连接(一次来回后就默认关闭连接)。websocket是长连接。
- http是无状态。websocket是有状态的。
websocket是如何工作的
- 发出连接请求,客户端发送的第一个请求,是一个http请求,与普通http不同的是
Upgrade: websocket // upgrade是一个http标准头部,指定希望升级到的协议
Connection: Upgrade // connection设定完成协议升级后是否保持连接
2. 服务器同意握手的响应
Upgrade: websocket // 确认升级到 WebSocket
Connection: Upgrade // 核心:确认连接已升级
3. 通信阶段
核心报文数据:
- FIN:是否是结束帧,FIN=0表示还有后续,FIN=1表示是最后一个数据帧
- Opcode:帧类型,数据类型(即这个数据包是干什么的)
- Mask,是否掩码,1表示是掩码,掩码是将数据报文内容转换,而非是加密
- 连接关闭
一端发送关闭帧,另一端接受到关闭帧后,发送响应关闭帧。对话之后TCP断连。
代码实现一下websocket
现在通过java的方式来写一个websocket案例。
纯websocket的实现:
<--依赖包-->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.4</version>
</dependency>
public class WebsocketChatRoom extends WebSocketServer {
public WebsocketChatRoom(int port) {
super(new InetSocketAddress(port));
}
@Override
public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
System.out.println("新连接到达:"+ webSocket.getRemoteSocketAddress());
}
@Override
public void onClose(WebSocket webSocket, int i, String s, boolean b) {
System.out.println("连接关闭:" + webSocket.getRemoteSocketAddress());
}
@Override
public void onMessage(WebSocket webSocket, String s) {
System.out.println("收到信息:" + s);
// websocketServer自带的广播实现
broadcast(s);
}
@Override
public void onError(WebSocket webSocket, Exception e) {
System.out.println("连接错误: "+e.getMessage());
}
@Override
public void onStart() {
}
public static void main(String[] args) {
int port = 8888;
WebsocketChatRoom server = new WebsocketChatRoom(port);
server.start();
System.out.println("聊天室,启动!: " + port);
}
}
上述的重点是继承WebSocketServer,它定义多个接口,每个接口望文生义,即各自连接阶段做的事情。最重要的是onMessage,参数为连接信息和报文文本。
作为后端开发,我们再请出deepseek大神帮我们写一个前端聊天窗口。
重要的是前端连接逻辑:
// 创建连接
socket = new WebSocket(serverUrl);
// websocket各阶段的处理器
socket.onopen = (event) => {
console.log('WebSocket connection opened:', event);
statusIndicator.className = 'status-indicator connected';
statusText.textContent = 'Connected to server';
sendButton.disabled = false;
// Add connection message to chat
addMessage('System', 'Connected to the WebSocket server', 'received');
};
socket.onmessage = (event) => {
console.log('Message from server:', event.data);
addMessage('Server', event.data, 'received');
// Auto-scroll to bottom
chatBox.scrollTop = chatBox.scrollHeight;
};
socket.onclose = (event) => {
console.log('WebSocket connection closed:', event);
statusIndicator.className = 'status-indicator';
statusText.textContent = 'Disconnected from server';
sendButton.disabled = true;
// Add disconnection message to chat
addMessage('System', 'Disconnected from the server', 'received');
// Try to reconnect after 5 seconds
setTimeout(() => {
if (!socket || socket.readyState === WebSocket.CLOSED) {
statusText.textContent = 'Reconnecting...';
initWebSocket();
}
}, 5000);
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
statusText.textContent = 'Connection error';
sendButton.disabled = true;
};
打开两个窗口,打开时会创建自己的用户名,然后在聊天室发送信息,即可同步信息了。
剖析一下websocket是如何实现的
什么是网络协议?为什么会有不同的网络协议?websocket和tcp的关系是什么呢?
满足好奇心,观物观本质才能举一反三。咱们来探讨一下websocket到底是如何实现的。
想要理解协议,不妨先从基础的http实现开始。
http是应用层协议,其底层逻辑是tcp层面的实现。tcp如何编写呢,即我们最开始学习高级语言时的“网络编程部分”。socket连接,输入流输出流,连接关闭等核心操作。
// 服务器监听端口
int port = 8888;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器启动,监听端口: " + port);
// 等待客户端连接(阻塞式)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接成功: " +
clientSocket.getInetAddress().getHostAddress() + ":" +
clientSocket.getPort());
// 获取输入流,读取客户端数据
InputStream input = clientSocket.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(input, "UTF-8"));
String message;
while ((message = reader.readLine()) != null) {
System.out.println("收到消息: " + message);
// 简单的回应
OutputStream output = clientSocket.getOutputStream();
PrintWriter writer = new PrintWriter(
new OutputStreamWriter(output, "UTF-8"), true);
writer.println("服务器已收到: " + message);
}
clientSocket.close();
} catch (IOException e) {
System.err.println("服务器异常: " + e.getMessage());
}
网络的底层就是socket编码的信息传输。socket编程即计算机网络的tcp层面的抽象。信息沟通的底层就是这样的。那么怎么实现http协议呢?
http协议的编码层面,就是特定规范的文本,根据特定的规则去解析。
你可能已经想到了,我们学过的请求头,请求体,请求行。当http传输到socket的时候,就是这样的文本形式。
POST /api/users HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 45
Authorization: Bearer token123
User-Agent: Mozilla/5.0
{"name":"张三","age":25,"email":"zhangsan@example.com"}
读取到文本数据后,只需要用一行一行读取的逻辑,就可以解析请求行中的请求方式,url,请求头等具体信息了。应用层协议即网络编程 + 协议规范中定义的逻辑处理。
类比一下websocket,也是先读取报文,再用特定的逻辑解析即可。
总思路有了,那么细节上是如何的呢,这里跟http做对比更加直观一些。
- http一般经历一次对话后就关闭连接;而websocket会保持socket连接不关闭,这样就实现了长连接。
- http接收方只是读一次输入流,写一次输出流就完成这次连接;而websocket,服务端保持长连接,想要主动发送信息的时候,忘输出流中写数据发送即可。
- 判断是否是websocket连接,有一个检测逻辑,查看请求头的中是否有 Upgrade: websocket,那么就是我们的websocket了,随即返还升级响应,开始websocket连接。
- websocket的数据帧格式是不同的,在写入的时候,需要按照要求处理成对应的数据格式。
- http服务器接收请求后,创建新线程,专门处理请求。websocket服务器则是长期保持线程(会话),时刻准备响应信息。
现代前端环境中的websocket包,或者后端的实现,本质大致是这样。其实要跟深一步,想要优化网络,需要使用NIO来替代我们的BIO网络编程。但是NIO就放到后面的专题专门来讲吧。