websocket

发布于:2025-07-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

引用:万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)写的不错

本文为笔者阅读上面博主文章的笔记,大家可以直接去阅读原文

黑马苍穹外卖的websocket代码

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching //开启缓存注解
@EnableScheduling //开启任务调度注解
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

一、 简介

1.1 什么是 WebSocket

WebSocket 是一种网络通信协议,设计用于在 Web 应用程序(客户端,通常是浏览器)和服务器之间建立全双工、低延迟、持久化的通信通道。与传统基于请求/响应模式的 HTTP 不同,WebSocket 在单个 TCP 连接上实现了双向实时数据流。该协议最初由 W3C 和 IETF 共同开发,其核心标准 RFC 6455 于 2011 年正式发布。

1.2 WebSocket 的优势和劣势

优势:

  1. 实时性: 建立连接后,数据可以在客户端和服务器之间即时、主动推送,无需客户端轮询(Polling),非常适合需要低延迟的应用(如聊天、实时游戏、金融行情)。

  2. 全双工通信: 支持同时双向数据传输。服务器可以主动向客户端推送消息,客户端也可以随时发送消息给服务器,通信效率更高。

  3. 降低网络开销:

    • 减少 HTTP 请求头: 连接建立后,后续数据传输仅需携带少量协议开销(通常仅 2-14 字节的帧头),避免了 HTTP 请求/响应中庞大的头部信息。

    • 避免轮询开销: 消除了客户端为获取更新而不断发送 HTTP 请求(及其头部)的需要。

  4. 持久化连接: 单个 TCP 连接在整个会话期间保持打开状态,避免了为每个消息建立新连接的开销(如 HTTP 短连接)。

  5. 支持二进制和文本数据: 原生支持高效传输二进制数据(如图片、音频、视频帧)和文本数据。

劣势:

  1. 兼容性要求: 需要浏览器和服务器同时支持 WebSocket 协议。虽然现代浏览器和主流后端框架/语言都普遍支持,但一些非常旧的浏览器或特定环境(如受限的嵌入式设备)可能不支持或支持不完善。

  2. 服务器资源消耗: 维护大量长时间存活的连接会消耗服务器的内存和 CPU 资源(连接状态管理、心跳保活等),对服务器架构(如连接管理、水平扩展)提出了更高要求。

  3. 复杂性增加: 相比简单的 HTTP 请求/响应,实现健壮、安全的 WebSocket 应用需要处理更多方面,如连接管理、错误处理、心跳机制、消息分帧、重连逻辑等。

  4. 代理和防火墙穿透: 某些配置严格的网络代理或防火墙可能阻止非 HTTP 流量(WebSocket 握手后升级为独立协议),需要特殊处理(如 WSS - WebSocket Secure 通常更容易穿透)。

  5. 安全考量:

    • 未经请求的数据推送: 服务器主动推送的能力需要谨慎设计,确保只向授权的客户端发送数据,防止信息泄露或被恶意利用。

    • 协议升级攻击: WebSocket 握手是基于 HTTP 的升级机制,需要防范相关的协议升级攻击。

    • 跨域问题: 同源策略 (Same-Origin Policy) 同样适用于 WebSocket 连接,建立跨域连接需要服务器显式配置 CORS (通过 Origin 头验证) 或使用代理。

  6. 无状态协议上的状态管理: WebSocket 连接本身是有状态的(持久的),但构建在其上的应用协议通常需要自行管理应用层的会话状态。

二、 WebSocket 的基本概念

2.1 WebSocket 的协议

  • 基础: WebSocket 是一个独立的、基于 TCP 的应用层协议(协议标识符 ws:// 或加密的 wss://)。

  • 握手: 连接始于一个标准的 HTTP 升级请求。客户端发送一个包含 Upgrade: websocket 和 Connection: Upgrade 等特定头部的 HTTP 请求。服务器响应一个状态码 101 Switching Protocols 和相应的确认头部,完成协议升级。握手阶段协商协议版本、子协议 (可选)、扩展 (可选) 等。

  • 数据帧: 握手成功后,通信切换到 WebSocket 协议帧格式。数据被封装在帧 (Frames) 中传输,帧头较小(最小 2 字节),包含操作码 (Opcode - 标识帧类型:文本、二进制、控制帧等)、掩码 (客户端到服务器必须掩码)、负载长度等信息。这种轻量级帧结构是高效传输的关键。

  • 双向通信: 连接建立后,客户端和服务器可以随时、独立地发送数据帧给对方,无需等待请求。支持文本帧 (UTF-8 编码) 和二进制帧

  • 控制帧: 除了承载应用数据的帧,协议定义了控制帧用于管理连接,如 Ping (探测连接活性/心跳)、Pong (响应 Ping)、Close (优雅关闭连接)。

2.2 WebSocket 的生命周期

一个 WebSocket 连接的生命周期包含以下关键阶段:

  1. 连接建立阶段 (Connection Establishment / Opening Handshake):

    • 客户端发起一个带有特定 WebSocket 头部的 HTTP GET 请求(升级请求)。

    • 服务器验证请求,如果接受,则发送 HTTP 101 Switching Protocols 响应。

    • TCP 连接成功升级为 WebSocket 连接。此阶段完成。

  2. 连接开放阶段 (Connection Open / Data Exchange):

    • 连接已成功建立并处于活动状态。

    • 客户端和服务器可以随时、双向地通过发送 WebSocket 数据帧(文本或二进制)进行应用数据的交换。

    • 控制帧(如 Ping/Pong)也在此阶段用于连接保活和管理。

  3. 连接关闭阶段 (Connection Closing / Closing Handshake):

    • 连接的关闭可以由任一方(客户端或服务器)主动发起,或由于底层网络故障被动触发。

    • 主动关闭方发送一个 Close 控制帧,包含一个可选的关闭状态码和原因短语。

    • 接收方收到 Close 帧后,必须回应一个 Close 帧作为确认(如果它尚未发送自己的 Close 帧)。

    • 双方在发送并接收到 Close 帧后,会认为 WebSocket 连接已进入关闭流程,并准备关闭底层的 TCP 连接。

  4. 连接关闭完成阶段 (Connection Closed):

    • 底层 TCP 连接被终止

    • 此时,WebSocket 连接已完全结束。任何尝试通过此连接发送或接收数据的操作都将失败或产生错误。

    • 资源(如内存中的连接状态对象)应被释放。

    Client->>Server: HTTP Upgrade 请求 (WebSocket 握手)
    Server-->>Client: 101 Switching Protocols (握手响应)
    Client->>Server: 双向实时数据传输 (文本/二进制)
    Server->>Client: 主动推送数据

在这个示意图中,客户端向服务器发送一个 WebSocket 握手请求,服务器响应一个握手响应,连接就被建立了。一旦连接建立,客户端和服务器就可以在连接上互相发送数据,直到其中一方发送一个关闭帧来关闭连接。在关闭帧被接收后,连接就会被关闭,WebSocket 连接关闭完成。

2.3 WebSocket的消息格式

字段 说明
FIN (1bit) 标记是否为完整消息
Opcode (4bit) 消息类型(1=文本,2=二进制,8=关闭,9=Ping,10=Pong)
Mask (1bit) 是否加密(客户端→服务端必须为1)
Payload Len 数据长度(7bit/16bit/64bit)
Masking Key 加密密钥(当 Mask=1 时存在)
Payload Data 实际数据

2.4 核心 API

// 创建连接
const ws = new WebSocket("wss://api.example.com/ws");

// 事件监听
ws.onopen = () => console.log("Connected!");
ws.onmessage = (e) => console.log("Received:", e.data);
ws.onclose = () => console.log("Connection closed");

// 发送数据
ws.send("Hello Server!");

// 关闭连接
ws.close(1000, "Normal closure");

三、Java 中的 WebSocket 实践

3.1 Java WebSocket 服务端

依赖

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

 服务端代码

@ServerEndpoint("/echo")
public class EchoServer {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("Client connected: " + session.getId());
    }

    @OnMessage
    public void onMessage(String msg, Session session) throws IOException {
        System.out.println("Received: " + msg);
        session.getBasicRemote().sendText("Echo: " + msg);
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("Connection closed");
    }
}

3.2 Java WebSocket 客户端

@ClientEndpoint
public class EchoClient {
    private Session session;

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
    }

    public void connect(String url) throws Exception {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        container.connectToServer(this, new URI(url));
    }

    public void send(String msg) throws IOException {
        session.getBasicRemote().sendText(msg);
    }
}

3.3 Spring Boot 集成 WebSocket

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

配置类

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

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

处理器

public class WebSocketHandler implements WebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        System.out.println("New connection: " + session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        session.sendMessage(new TextMessage("Server: " + message.getPayload()));
    }
}

四、消息格式详解

4.1 文本 vs 二进制消息

// 发送文本消息
session.getBasicRemote().sendText("Hello World!");

// 发送二进制消息
byte[] data = Files.readAllBytes(Paths.get("image.png"));
ByteBuffer buffer = ByteBuffer.wrap(data);
session.getBasicRemote().sendBinary(buffer);

4.2 Ping/Pong 心跳机制

// 服务端发送 Ping
ByteBuffer pingData = ByteBuffer.wrap(new byte[]{1, 2, 3});
session.getBasicRemote().sendPing(pingData);

// 客户端响应 Pong
@OnMessage
public void onPong(PongMessage pong) {
    System.out.println("Received Pong: " + pong.getApplicationData());
}

4.3 关闭连接

// 正常关闭(状态码 1000)
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye!"));

// 异常关闭处理
@OnClose
public void onClose(CloseReason reason) {
    System.out.println("Closed: " + reason.getReasonPhrase());
}

五、性能优化策略

5.1 WebSocket vs HTTP 性能对比

指标 WebSocket HTTP 轮询
连接开销 1 个 TCP 连接 N 个 TCP 连接
头部开销 首次握手后无头部 每次请求携带头部
延迟 毫秒级 秒级
服务器压力 低(长连接) 高(频繁建连)

5.2 优化建议

  1. 压缩消息:对文本/二进制数据启用压缩

  2. 批处理:合并小消息为批量传输

  3. CDN 加速:静态资源就近分发

  4. 负载均衡:Nginx 反向代理多节点

  5. 异步 I/O:避免阻塞线程(如 Netty 实现)

WebSocket 已成为实时通信领域的核心技术。本文从协议原理到代码实践,从性能优化到未来演进,系统性地解析了其完整技术栈。建议收藏本文作为工具手册,随时查阅关键知识点。如有疑问欢迎评论区交流!