程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • WebSocket

    • WebSocket 基础篇
    • WebSocket 原理篇
      • 一、WebSocket 协议的原理详解
        • 1.1 WebSocket 与 HTTP 的关系
        • 1.2 WebSocket 握手流程
        • 1.3 WebSocket 协议工作流程
        • 1.4 WebSocket 的优缺点
      • 二、WebSocket 握手请求与响应详解
        • 2.1 客户端握手请求示例
        • 2.2 服务器握手响应示例
      • 三、WebSocket 的应用场景
        • 3.1 WebSocket 适用场景
        • 3.2 不适用 WebSocket 的场景
      • 四、WebSocket 在线/离线状态的判断与断线问题的解决
        • 4.1 如何判断客户端在线/离线?
        • 4.2 解决 WebSocket 断线问题
        • 方案一:修改 Nginx 配置
        • 方案二:客户端发送心跳包
    • J2EE WebSocket 实现方式
    • Spring WebSocket 实现方式
    • Spring WebSocket 高级实现方式
    • 在线聊天(单聊)案例
  • WebSocket
  • WebSocket
scholar
2024-09-18
目录

WebSocket 原理篇

# WebSocket 原理篇

# 一、WebSocket 协议的原理详解

WebSocket 协议与 HTTP 协议类似,都是基于 TCP 连接传输数据的。然而,WebSocket 协议通过 HTTP 协议建立初始连接,然后在此基础上进行 WebSocket 协议通信,因此这两者在握手阶段有一定的交集。

# 1.1 WebSocket 与 HTTP 的关系

  • HTTP 协议 是一种非持久化协议,通信只能由客户端发起,每次请求对应一次响应。即使在 HTTP/1.1 中引入了 keep-alive,允许复用 TCP 连接,但仍然保持“请求-响应”模式。
  • WebSocket 协议 是一种持久化协议,可以在一次连接建立后,持续进行双向通信。服务器可以主动向客户端推送数据,而无需等待客户端请求。

# 1.2 WebSocket 握手流程

WebSocket 的建立过程主要分为两个阶段:HTTP 握手和协议升级。

  1. 客户端发起 HTTP 请求:使用标准的 HTTP 请求建立初始连接,并表明要升级为 WebSocket 协议。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
1
2
3
4
5
6
7
8

解析这段请求:

  • Upgrade: websocket:告诉服务器请求协议升级为 WebSocket。
  • Connection: Upgrade:指示当前连接要进行协议升级。
  • Sec-WebSocket-Key:客户端随机生成的 Base64 编码值,服务器需要用此值验证 WebSocket 请求的合法性。
  • Sec-WebSocket-Protocol:指定客户端支持的子协议,用于区分不同服务。
  • Sec-WebSocket-Version:WebSocket 协议版本,这里为 13,表示使用的是最新的 WebSocket 协议版本。
  1. 服务器响应并确认协议升级:

服务器解析客户端的请求后,返回如下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
1
2
3
4
5

解析服务器响应:

  • HTTP/1.1 101 Switching Protocols:状态码 101 表示协议切换成功。
  • Upgrade: websocket:确认升级协议为 WebSocket。
  • Connection: Upgrade:保持连接升级。
  • Sec-WebSocket-Accept:由服务器生成的验证信息,通过对 Sec-WebSocket-Key 进行加密计算得到。
  • Sec-WebSocket-Protocol:确认使用的子协议。

至此,WebSocket 连接建立成功,接下来所有通信将按照 WebSocket 协议进行,而不再使用 HTTP。

# 1.3 WebSocket 协议工作流程

  1. 握手阶段:客户端通过 HTTP 请求发起握手,服务器返回 101 状态码表示协议切换成功。
  2. 数据传输阶段:握手成功后,WebSocket 连接进入数据传输阶段,双方可以随时发送数据。通信是双向的,且无需重新建立连接。
  3. 连接关闭阶段:当客户端或服务器希望关闭连接时,可以发送关闭帧,另一方收到后也应发送关闭帧确认,随后连接断开。

# 1.4 WebSocket 的优缺点

优点:

  • 持久化连接:建立连接后,通信可以持续进行,避免了频繁的握手过程。
  • 双向通信:服务器可以主动推送消息,不需要等待客户端请求。
  • 轻量化:相比 HTTP 请求的头部信息,WebSocket 消耗更少的带宽。
  • 实时性:适用于需要低延迟、高实时性的应用场景。

缺点:

  • 浏览器兼容性:部分旧版浏览器(如 IE 10 以下版本)不支持 WebSocket。
  • 连接维护:持久化连接需要服务器进行更多资源管理,如连接超时、心跳检测等。

# 二、WebSocket 握手请求与响应详解

# 2.1 客户端握手请求示例

以下是客户端发送的握手请求:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
1
2
3
4
5
6
7
8
  • GET /chat HTTP/1.1:发起对 /chat 路径的 HTTP GET 请求。
  • Upgrade: websocket 和 Connection: Upgrade:指示服务器切换协议。
  • Sec-WebSocket-Key:客户端生成的随机字符串,用于校验服务器响应。
  • Sec-WebSocket-Protocol:指定客户端支持的子协议。
  • Sec-WebSocket-Version:WebSocket 协议版本,通常为 13。

# 2.2 服务器握手响应示例

服务器响应如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
1
2
3
4
5
  • HTTP/1.1 101 Switching Protocols:确认协议切换。
  • Sec-WebSocket-Accept:服务器返回的验证信息,是对客户端 Sec-WebSocket-Key 的加密处理结果。
  • Sec-WebSocket-Protocol:确认最终采用的子协议。

总结

WebSocket 协议连接过程概述:

  1. 客户端发起 HTTP 请求,并携带 WebSocket 协议相关信息(如 Upgrade、Sec-WebSocket-Key 等)。
  2. 服务器接收请求,验证后返回协议切换确认信息(如 Sec-WebSocket-Accept),建立 WebSocket 连接。
  3. 连接建立后,客户端和服务器可以进行双向通信,直到一方主动关闭连接。

# 三、WebSocket 的应用场景

WebSocket 作为一种高效的双向通信协议,适用于多个实时性要求较高的场景。以下是 WebSocket 的典型应用场景及一些不能使用的场景:

# 3.1 WebSocket 适用场景

  1. 即时聊天通信

    • 实现类似微信、Slack 的实时聊天应用,用户间消息可以立即发送和接收,避免传统 HTTP 长轮询带来的延迟和资源浪费。
  2. 多人在线游戏

    • 在多人在线游戏中,服务器需要频繁向所有玩家广播游戏状态的更新,WebSocket 能实现低延迟的实时数据同步。
  3. 在线协同编辑

    • 在文档或代码的协同编辑中,多个用户可以实时查看和编辑内容,通过 WebSocket 可以确保每个用户的界面保持同步。
  4. 实时数据流

    • 比如股票行情、加密货币价格、体育比赛直播等需要频繁更新的场景,WebSocket 可以实时推送最新的数据到客户端。
  5. 地图位置共享

    • 实时更新和共享用户或车辆的位置数据,适用于打车服务、物流跟踪等应用。
  6. 即时 Web 应用

    • 比如在交易网站中,价格波动数据需要实时显示,WebSocket 能够连续推送更新的数据,提高用户体验。

# 3.2 不适用 WebSocket 的场景

  1. 静态或不频繁更新的数据

    • 当数据变化较慢,且只需一次获取即可满足需求时,使用 HTTP 请求即可,比如静态页面或不需要频繁刷新数据的页面。
  2. 旧数据查询

    • 如果仅需获取历史数据或一次性的查询,HTTP 协议更合适。
  3. 无需双向实时通信

    • 如果只需单向请求并响应,且通信频率较低,HTTP 的请求-响应模式已经足够。

# 四、WebSocket 在线/离线状态的判断与断线问题的解决

在 WebSocket 通信中,判断客户端是否在线以及处理断线重连是两个重要的问题。特别是在 Nginx 代理 WebSocket 的场景下,如果连接长时间无消息,可能会被自动断开。下面详细总结这部分内容。

# 4.1 如何判断客户端在线/离线?

为了判断客户端是否在线,通常采用“心跳检测”的方式,结合数据库或缓存系统来记录客户端的状态。

工作流程:

  1. 第一次连接与状态记录:

    • 当客户端第一次与服务器建立 WebSocket 连接时,会发送一个包含唯一标识(如用户 ID)和时间戳的请求。
    • 服务器接收该请求后,将客户端的唯一标识和时间戳存入数据库或缓存中,标记为“在线”。
  2. 定时心跳检测:

    • 客户端每隔一段时间(如 30 秒)发送一个心跳包,请求中仍包含唯一标识和时间戳。
    • 服务器接收到心跳包后,查询数据库或缓存中该唯一标识对应的上一次时间戳,计算与当前时间戳的差值。
    • 如果时间差在预定的范围内(如 60 秒内),则认为客户端仍然在线;如果超出范围,则标记为“离线”。

示例代码:

// 客户端定时发送心跳包
function sendHeartbeat() {
  const uniqueId = "client_123"; // 客户端唯一标识
  const timestamp = Date.now(); // 当前时间戳

  ws.send(JSON.stringify({ id: uniqueId, timestamp: timestamp }));
}

// 每隔 30 秒发送一次心跳包
setInterval(sendHeartbeat, 30000);
1
2
3
4
5
6
7
8
9
10

服务器端逻辑:

// 伪代码:服务器端处理心跳请求
function handleHeartbeat(request) {
    String clientId = request.getParameter("id");
    long currentTimestamp = request.getParameter("timestamp");

    // 从缓存中查询该客户端的上一次心跳时间
    long lastTimestamp = cache.get(clientId);

    if (lastTimestamp == null || (currentTimestamp - lastTimestamp > 60000)) {
        // 超过 60 秒没有更新,标记为离线
        markAsOffline(clientId);
    } else {
        // 更新客户端的最新心跳时间
        cache.put(clientId, currentTimestamp);
        markAsOnline(clientId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4.2 解决 WebSocket 断线问题

在实际应用中,WebSocket 可能因为网络不稳定、Nginx 超时等原因断开连接。为了确保连接的持久性,可以采用以下两种方案:

# 方案一:修改 Nginx 配置

Nginx 作为反向代理时,如果 WebSocket 连接在一段时间内没有消息传输,可能会被自动断开。可以通过调整 Nginx 的配置来增加连接的持久性。

Nginx 配置示例:

http {
    # 增加 WebSocket 的超时时间
    upstream websocket {
        server localhost:8080;
    }

    server {
        location /ws {
            proxy_pass http://websocket;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";

            # 设置较长的超时时间,防止无消息时被断开
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_connect_timeout 3600s;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 方案二:客户端发送心跳包

定期发送心跳包可以防止连接因长时间无消息而被 Nginx 或其他代理服务器断开。心跳包通常是一个简单的 ping 消息,服务器接收后返回一个 pong 响应。

心跳包实现示例:

// 心跳检测,防止连接超时断开
var heartCheck = {
  timeout: 30000, // 每 30 秒发送一次心跳
  timeoutObj: null,
  serverTimeoutObj: null,

  reset: function () {
    clearTimeout(this.timeoutObj);
    clearTimeout(this.serverTimeoutObj);
    return this;
  },

  start: function () {
    var self = this;
    this.timeoutObj = setTimeout(function () {
      ws.send('ping'); // 发送心跳包
      console.log('发送心跳包: ping');

      self.serverTimeoutObj = setTimeout(function () {
        ws.close(); // 如果服务器未响应,主动断开连接
      }, self.timeout);
    }, this.timeout);
  }
};

// 在 WebSocket 连接建立后启动心跳检测
ws.onopen = function () {
  heartCheck.reset().start(); // 连接成功后启动心跳检测
};

// 当接收到服务器的 pong 消息时,重置心跳检测
ws.onmessage = function (event) {
  if (event.data === 'pong') {
    heartCheck.reset().start(); // 重置并重新启动心跳检测
  } else {
    console.log('收到消息:', event.data);
  }
};
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

总结

  1. 判断在线/离线的关键在于心跳检测,通过记录时间戳和唯一标识来确定客户端是否在线。
  2. WebSocket 断线问题主要由网络不稳定或代理超时引起,可以通过 Nginx 配置调整超时时间或定期发送心跳包来解决。
  3. 综合使用心跳包和重连机制,可以最大限度地确保 WebSocket 连接的稳定性和持续性。
编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
WebSocket 基础篇
J2EE WebSocket 实现方式

← WebSocket 基础篇 J2EE WebSocket 实现方式→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式