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
| @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
| @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
| @Data public class ChatMessage { private String type; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 3600s; }
|
五、测试
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 的笔记