WebSocket实现网站点赞通知

WebSocket 详解

什么是 WebSocket?

WebSocket 是一种在单个 TCP 连接 上进行全双工通信 的网络协议,实现了浏览器与服务器之间的实时双向通信

与 HTTP 的根本区别

HTTP(请求-响应模式)

复制代码
客户端: 请求 → 服务器
客户端: ← 响应 服务器
(连接关闭)

WebSocket(全双工模式)

复制代码
客户端: ↔ 服务器
客户端: ↔ 服务器  
客户端: ↔ 服务器
(持续连接,双向实时通信)

WebSocket 协议特点

1. 一次握手,持久连接

javascript 复制代码
// 握手过程
客户端 → 服务器: HTTP Upgrade 请求
客户端 ← 服务器: HTTP 101 Switching Protocols
// 之后就是 WebSocket 协议通信

2. 极小的协议开销

java 复制代码
// 建立连接后,数据帧头很小
// HTTP 每次请求都有完整的头部,WebSocket 只有 2-14 字节的帧头

3. 真正的实时性

  • 服务器可以主动推送 数据给客户端
  • 无需客户端轮询询问
  • 毫秒级延迟

WebSocket 核心技术原理

连接建立过程

java 复制代码
// 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                   3
 0 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. 依赖配置

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2. 配置类

java 复制代码
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 {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3. 端点类(注解方式)

java 复制代码
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());  // 修正日志语法
    }

    /**
     * 收到消息
     */
    @OnMessage
    public void onMessage(String message, Session session) { 
        LOG.info("收到消息: {},内容: {}", token, message);  // 修正日志语法
    }

    /**
     * 连接错误
     */
    @OnError
    public 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();
    }
}
相关推荐
大白的编程日记.3 分钟前
【计算网络学习笔记】MySql的多版本控制MVCC和Read View
网络·笔记·学习·mysql
踏浪无痕2 小时前
线上偶发 502 排查:用 Netty 成功复现 KeepAlive 时间窗口案例实战(附完整源码)
运维·网络协议
shmexon2 小时前
上海兆越亮相无锡新能源盛会,以硬核通信科技赋能“能碳未来”
网络·人工智能
javaの历练之路2 小时前
基于 SpringBoot+Vue2 的前后端分离博客管理系统(含 WebSocket+ECharts)
spring boot·websocket·echarts
北京耐用通信2 小时前
告别“牵一发而动全身”:耐达讯自动化Profibus PA分线器为石化流量计网络构筑安全屏障
人工智能·网络协议·安全·自动化·信息与通信
Sinowintop2 小时前
易连EDI-EasyLink无缝集成之消息队列Kafka
分布式·网络协议·kafka·集成·国产化·as2·国产edi
Lay_鑫辰3 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
车载测试工程师3 小时前
CAPL学习-IP API函数-2
网络·学习·tcp/ip·capl·canoe
Xの哲學4 小时前
Linux 指针工作原理深入解析
linux·服务器·网络·架构·边缘计算
Pocker_Spades_A5 小时前
在家搭个私人网盘?用 Nextcloud+cpolar 突破局域网限制
网络