WebSocket实现网站点赞通知
WebSocket 详解
什么是 WebSocket?
WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议,实现了浏览器与服务器之间的实时双向通信。
与 HTTP 的根本区别
HTTP(请求-响应模式)
客户端: 请求 → 服务器
客户端: ← 响应 服务器
(连接关闭)
WebSocket(全双工模式)
客户端: ↔ 服务器
客户端: ↔ 服务器
客户端: ↔ 服务器
(持续连接,双向实时通信)
WebSocket 协议特点
1. 一次握手,持久连接
// 握手过程
客户端 → 服务器: HTTP Upgrade 请求
客户端 ← 服务器: HTTP 101 Switching Protocols
// 之后就是 WebSocket 协议通信
2. 极小的协议开销
// 建立连接后,数据帧头很小
// HTTP 每次请求都有完整的头部,WebSocket 只有 2-14 字节的帧头
3. 真正的实时性
- 服务器可以主动推送 数据给客户端
- 无需客户端轮询询问
- 毫秒级延迟
WebSocket 核心技术原理
连接建立过程
// 1. 客户端发起握手
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13// 2. 服务器响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
数据帧格式
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
Spring Boot 中的 WebSocket 实现
1. 依赖配置
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 配置类
package com.panda.wiki.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
3. 端点类(注解方式)
package com.panda.wiki.websocket;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint("/ws/{token}")
public class WebSocketServer {private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class); // 修正 Logger/*** 每个客户端一个token*/private String token = "";/*** 使用线程安全的 ConcurrentHashMap*/public static ConcurrentHashMap<String, Session> map = new ConcurrentHashMap<>();/*** 连接成功*/@OnOpen // 修正注解大小写public void onOpen(Session session, @PathParam("token") String token) { // 修正参数语法map.put(token, session);this.token = token;LOG.info("有新连接:token:{},session id:{},当前连接数:{}", token, session.getId(), map.size()); // 修正日志语法}/*** 连接关闭*/@OnClose // 修正注解大小写public void onClose(Session session) {map.remove(this.token);LOG.info("连接关闭,token:{},session id:{},当前连接数:{}", this.token, session.getId(), map.size()); // 修正日志语法}/*** 收到消息*/@OnMessagepublic void onMessage(String message, Session session) { LOG.info("收到消息: {},内容: {}", token, message); // 修正日志语法}/*** 连接错误*/@OnErrorpublic void onError(Session session, Throwable error) { LOG.error("发生错误", error); }/*** 群发消息*/public static void sendInfo(String message) { // 改为静态方法for (String token : map.keySet()) {Session session = map.get(token);try {if (session.isOpen()) { // 检查会话是否仍然打开session.getBasicRemote().sendText(message);LOG.info("推送消息: {}, 内容: {}", token, message); // 修正日志文字} else {// 如果会话已关闭,从map中移除map.remove(token);}} catch (IOException e) {LOG.error("推送消息失败: {}, 内容: {}", token, message, e); // 修正日志文字// 发送失败,从map中移除无效连接map.remove(token);}}}/*** 向指定用户发送消息*/public static void sendToUser(String token, String message) {Session session = map.get(token);if (session != null && session.isOpen()) {try {session.getBasicRemote().sendText(message);LOG.info("向用户 {} 发送消息: {}", token, message);} catch (IOException e) {LOG.error("向用户 {} 发送消息失败", token, e);map.remove(token); // 移除无效连接}} else {LOG.warn("用户 {} 不在线或连接已关闭", token);if (session != null) {map.remove(token); // 清理无效连接}}}/*** 获取当前在线连接数*/public static int getOnlineCount() {return map.size();}/*** 检查用户是否在线*/public static boolean isOnline(String token) {Session session = map.get(token);return session != null && session.isOpen();}
}