WebSocket 入门教程——从理论到实时通信实战

WebSocket 入门教程——从理论到实时通信实战

作者: CaoZH
日期: 2026-02-20
本文为原创教程


实时通信已经成为现代 Web 应用的标配。无论是聊天、通知、实时数据看板还是在线协作,都需要 WebSocket。

本文从 WebSocket 协议原理开始,带你用 Spring Boot + Vue 3 搭建完整的实时通信应用。

一、HTTP vs WebSocket

1
2
3
4
5
6
7
8
9
10
11
12
13
## HTTP(轮询)
客户端 → 服务器(请求)
客户端 ← 服务器(响应)
客户端 → 服务器(请求)
客户端 ← 服务器(响应)
...
实时性差,每次请求都带 HTTP 头部

## WebSocket(长连接)
客户端 → 服务器(握手升级)
客户端 ←→ 服务器(全双工通信)
...
一次握手,双向通信,头部开销极小
对比 HTTP 轮询 WebSocket
通信方向 单向(客户端发起) 双向
连接开销 每次请求都有 HTTP 头部 一次握手后持久连接
实时性 延迟 = 轮询间隔 毫秒级
服务端推送 ❌ 需要轮询 ✅ 原生支持
适用场景 REST API 实时消息、游戏、协作

二、后端实现(Spring Boot)

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

配置 WebSocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// WebSocketConfig.java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatHandler(), "/ws/chat")
.setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}

@Bean
public WebSocketHandler chatHandler() {
return new ChatWebSocketHandler();
}
}

消息处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// ChatWebSocketHandler.java
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {

// 在线用户列表
private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

@Override
public void afterConnectionEstablished(WebSocketSession session) {
String userId = getUserId(session);
sessions.put(userId, session);
log.info("用户 {} 已连接", userId);

// 广播在线人数
broadcastUserCount();
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
ChatMessage chatMessage = JSON.parseObject(payload, ChatMessage.class);

// 根据消息类型处理
switch (chatMessage.getType()) {
case "chat":
handleChat(chatMessage);
break;
case "typing":
handleTyping(chatMessage);
break;
case "read":
handleReadReceipt(chatMessage);
break;
}
}

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
String userId = getUserId(session);
sessions.remove(userId);
broadcastUserCount();
}

// 发送给指定用户
public void sendToUser(String userId, String message) {
WebSocketSession session = sessions.get(userId);
if (session != null && session.isOpen()) {
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
log.error("发送消息失败", e);
}
}
}

// 广播
public void broadcast(String message) {
sessions.values().forEach(session -> {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
} catch (IOException e) {
log.error("广播失败", e);
}
});
}
}

消息模型

1
2
3
4
5
6
7
8
9
// ChatMessage.java
@Data
public class ChatMessage {
private String type; // chat, typing, read, online_count
private String sender;
private String receiver;
private String content;
private Long timestamp;
}

三、前端实现(Vue 3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const ws = ref(null)
const messages = ref([])
const inputMessage = ref('')
const onlineCount = ref(0)
const userId = 'user_' + Date.now()

function connect() {
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
const url = `${protocol}//localhost:8080/ws/chat`

ws.value = new WebSocket(url)

ws.value.onopen = () => {
console.log('WebSocket 已连接')
}

ws.value.onmessage = (event) => {
const data = JSON.parse(event.data)

switch (data.type) {
case 'chat':
messages.value.push(data)
break
case 'online_count':
onlineCount.value = data.count
break
case 'typing':
showTypingIndicator(data.sender)
break
}
}

ws.value.onclose = () => {
console.log('WebSocket 断开,5秒后重连...')
setTimeout(connect, 5000)
}

ws.value.onerror = (error) => {
console.error('WebSocket 错误:', error)
}
}

function sendMessage() {
if (!inputMessage.value.trim()) return

const message = {
type: 'chat',
sender: userId,
content: inputMessage.value,
timestamp: Date.now()
}

ws.value.send(JSON.stringify(message))
inputMessage.value = ''
}

onMounted(() => {
connect()
})

onUnmounted(() => {
ws.value?.close()
})
</script>

<template>
<div class="chat-container">
<header>
<h3>实时聊天</h3>
<span class="online">👥 {{ onlineCount }} 人在线</span>
</header>

<div class="messages">
<div v-for="msg in messages" :key="msg.timestamp"
:class="msg.sender === userId ? 'own' : 'other'">
<div class="message-content">{{ msg.content }}</div>
<div class="time">{{ formatTime(msg.timestamp) }}</div>
</div>
</div>

<div class="input-area">
<input v-model="inputMessage"
@keyup.enter="sendMessage"
placeholder="输入消息...">
<button @click="sendMessage">发送</button>
</div>
</div>
</template>

四、Nginx 代理 WebSocket

1
2
3
4
5
6
7
8
location /ws/ {
proxy_pass http://backend:8080;
proxy_http_version 1.1; # WebSocket 需要 HTTP/1.1
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # WebSocket 长连接超时
}

五、测试

1
2
3
4
5
6
7
8
9
10
11
12
// 浏览器控制台测试
const ws = new WebSocket('ws://localhost:8080/ws/chat')

ws.onopen = () => console.log('已连接')
ws.onmessage = (e) => console.log('收到:', e.data)

ws.send(JSON.stringify({
type: 'chat',
content: 'Hello WebSocket!',
sender: 'test_user',
timestamp: Date.now()
}))

六、总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## WebSocket 核心要点

1. 基于 HTTP 升级握手(101 Switching Protocols)
2. 全双工通信,服务端可主动推送
3. 头部开销小(2-10 bytes vs HTTP 数百 bytes)
4. 支持文本和二进制帧
5. 需要处理心跳和重连

## 应用场景
✅ 即时聊天
✅ 实时数据看板
✅ 在线协作编辑
✅ 游戏实时同步
✅ 股票/行情推送
✅ 通知推送

首发于 CaoZH 的笔记