WebSocket
WebSocket是一种基于TCP的网络通信协议,专为实时双向通信 设计。它允许客户端和服务器在单个持久连接 上双向传输数据,克服了传统HTTP协议单向请求-响应模式的局限性,适用于需要低延迟、高实时性的场景(如聊天、游戏、股票行情、实时数据推送等)。
1. WebSocket是什么
- 全双工通信协议: 允许客户端(如浏览器)和服务器同时双向传输数据,类似电话通话,双方可随时发送信息。
- **持久连接:**一次握手建立连接后,连接保持开放,数据传输无需重复建立连接,大幅降低延迟。
- 基于TCP: 运行在传输层TCP协议之上,端口与HTTP一致(80/443),但使用独立协议
ws://
或加密的wss://
。
2. WebSocket与HTTP对比
| 特性 | HTTP | WebSocket |
| 通信模式 | 半双工(请求-响应) | 全双工(双向实时通信) |
| 连接生命周期 | 短连接(每次请求后关闭) | 长连接(持久连接,可复用) |
| 数据开销 | 每次请求需携带完整Header | 首次握手后,数据帧轻量(2~10字节) |
| 实时性 | 依赖轮询(如AJAX轮询) | 支持服务端主动推送 |
适用场景 | 静态资源获取、表单提交 | 实时聊天、在线协作、金融行情等 |
---|
3. 为什么需要WebSocket
传统HTTP的问题:
- **单向通信:**只能客户端发起请求,服务器被动响应。
- **高开销:**每次请求需携带头部信息(如Cookie、User-Agent),浪费带宽。
- **实时性差:**依赖轮询(定时请求)或长轮询(挂起等待响应),导致延迟高、资源浪费。
WebSocket的优势:
- **低延迟:**消息即时到达,适合实时应用。
- **高效传输:**连接建立后,数据帧头部极小(仅2~10字节)。
- **节省资源:**避免频繁建立连接和冗余头部。
4. WebSocket工作原理
4.1. 握手阶段(HTTP升级)
WebSocket连接通过HTTP协议发起,客户端发送一个包含Upgrade头的请求,服务器返回101 Switching Protocols 确认升级。
客户端请求示例:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务器响应示例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
4.2 数据传输阶段
握手成功后,双方通过**数据帧(Frame)**通信,格式轻量且支持分片传输。数据帧包含以下字段:
- Opcode :标识数据类型(如文本
0x1
、二进制0x2
)。 - Payload:实际传输的数据。
- FIN:标记是否为消息的最后一帧。
5. 使用 Java WebSocket API 编写 WebSocket 服务端
5.1 依赖配置
添加 javax.websocket-api 依赖:
XML
<!-- Maven 依赖 -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
5.2 服务端代码
java
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ServerEndpoint("/chat") // 定义 WebSocket 端点路径
public class ChatServer {
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("客户端连接: " + session.getId());
}
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到消息: " + message);
broadcast("用户 " + session.getId() + ": " + message);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
System.out.println("客户端断开: " + session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
private void broadcast(String message) {
sessions.forEach(session -> {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
5.3 部署
将应用部署到支持Java WebSocket的服务器(如Tomcat 8+、Jetty 9+)。
6. 使用 Java WebSocket API 编写 WebSocket 客户端
6.1 客户端代码
java
import javax.websocket.*;
import java.net.URI;
@ClientEndpoint
public class ChatClient {
private Session session;
public ChatClient(String endpoint) throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, new URI(endpoint));
}
@OnOpen
public void onOpen(Session session) {
this.session = session;
System.out.println("已连接到服务器");
}
@OnMessage
public void onMessage(String message) {
System.out.println("收到服务器消息: " + message);
}
@OnClose
public void onClose() {
System.out.println("连接已关闭");
}
public void sendMessage(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
public static void main(String[] args) throws Exception {
ChatClient client = new ChatClient("ws://localhost:8080/your-app/chat");
client.sendMessage("Hello Server!");
}
}
7. 使用 Spring Boot 编写 WebSocket 服务端
7.1 添加依赖
XML
<!-- Spring Boot WebSocket 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
7.2 启用WebSocket支持
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/chat") // 注册处理器和路径
.setAllowedOrigins("*"); // 允许跨域
}
@Bean
public WebSocketHandler myHandler() {
return new ChatWebSocketHandler();
}
}
7.3 实现消息处理器
java
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.CopyOnWriteArrayList;
public class ChatWebSocketHandler extends TextWebSocketHandler {
private final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
System.out.println("新连接: " + session.getId());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
System.out.println("收到消息: " + payload);
broadcast("用户 " + session.getId() + ": " + payload);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
System.out.println("连接关闭: " + session.getId());
}
private void broadcast(String message) {
sessions.forEach(s -> {
try {
s.sendMessage(new TextMessage(message));
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
7.4 客户端连接(javascript)
javascript
<script>
const socket = new WebSocket('ws://localhost:8080/chat');
socket.onopen = () => {
socket.send('Hello Spring WebSocket!');
};
socket.onmessage = (event) => {
console.log('收到消息:', event.data);
};
</script>