小工具知识网

当前位置: 首页 > 行业知识 > WebSocket协议及其在实时通信中的重要性

行业知识

WebSocket协议及其在实时通信中的重要性

2026-11-03 17:55:26 来源:互联网转载

���文深入介绍了WebSocket协议及其在实时通信中的重要性。从HTTP的限制到WebSocket的优势,再到连接建立和实际应用,全面阐述了WebSocket的工作原理及其在实际业务中的应用场景。

()

一、引言

在生产中,有时需要服务端向客户端发送消息,但是在传统的 HTTP 协议中,是请求-响应模式,也就是说每个请求都是独立的,是由客户端向服务器发送请求,服务器处理请求并返回响应,然后连接就会关闭。

这种请求-响应模式并不能支持后端发起请求,为了解决传统 HTTP 协议在实时通信中的限制,WebSocket协议被引入。

()

二、WebSocket 介绍

WebSocket 是一种网络传输协议。它允许客户端和服务器之间进行双向通信,而不需要每次请求都重新建立连接。可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。

WebSocket 通信始于 HTTP 握手,之后升级到WebSocket协议。具有以下优势:

  1. 双向通信:WebSocket支持全双工通信,允许服务器主动向客户端推送数据,而不需要客户端发送请求。
  2. 较低的延迟:WebSocket 通过在建立连接后保持持久连接的方式,避免了重复建立和关闭连接的开销。可以减少延迟,实现更快的数据传输和实时更新。
  3. 减少网络流量:与轮询方式相比,WebSocket 采用事件驱动的方式,只在有新数据时才发送更新,避免了不必要的网络流量和服务器负载。
  4. 兼容性:WebSocket 协议已经得到了广泛的支持。

三、其他主动推送

短轮询长轮询iframe流SSEWebSocketSocket.IO
实时性较差较差较好较好较好较好
网络开销较大较大较小较小较小较小
协议支持HTTP 协议HTTP 协议HTTP 协议HTTP 协议WebSocket 协议自适应
跨域支持较差较差较差支持支持支持
兼容性较好较好较好较好较好较好
双向通信单向通信单向通信单向通信单向通信双向通信双向通信
实现复杂度相对简单相对简单相对简单相对复杂相对复杂相对复杂
延迟较高相对较低相对较低较低最低较低

1.短轮询

优势:

  • 实现简单。简单场景的快速的解决方案。

    劣势:

  • 请求频繁。频繁的请求和响应会导致较高的网络开销和延迟。
  • 资源占用高。对服务器资源的占用较高,每次请求需要建立连接和断开连接。

    2.长轮询

    优势:

    • 相比短轮询,能够减少一部分请求的频繁性。客户端在收到响应后会立即发起新的请求。
    • 相比短轮询速度相较快。服务器在有数据时才会响应,能够更快地将数据推送给客户端。

      劣势:

    • 延迟高。存在一定程度的延迟,客户端需要等待服务器有数据时才能收到响应。
    • 资源占用高。服务器需要维护大量的长连接,可能会影响服务器的性能。

      3.iframe流

      优势:

      • 通过在 iframe 中加载长连接资源来实现数据推送,相对于传统的轮询方式,可以降低一定程度的延迟。

        劣势:

      • 受到浏览器同源策略的限制。
      • 复杂应用场景难以管理和维护。

        4.SSE(Server-Sent Events)

        优势:

        • 基于 HTTP,在不需要额外的握手过程的情况下实现服务器向客户端的数据推送,降低了通信的开销。
        • 相对于传统的轮询方式,能够实现较低的延迟。

          劣势:

        • 仅支持单向通信,无法实现客户端到服务器的双向通信。

          5.Socket.IO

          优势:

          • 提供了跨浏览器的双向通信能力,可以自动选择最佳的通信方式,包括 WebSocket、轮询等,从而实现较低的延迟。
          • 支持实时双向通信。

            劣势:

          • 需要额外的库支持,增加代码的复杂性。

            6.WebSocket

            优势:

            • 提供了实时的双向通信能力,实现最低的延迟。
            • 与 HTTP 不同,WebSocket 在建立连接后能够直接实现服务器和客户端之间的双向通信,而不需要频繁地发起新的连接。

              劣势:

            • 部署和维护复杂。

              7.如何选择

              双向通讯:WebSocket、Socket.IO

              实时性:WebSocket >= Socket.IO > SSE ≈ iframe流 ≈ 长轮询 > 短轮询

              仅单向通讯:SSE

              场景简单且不复杂:长轮询、短轮询

              四、WebSocket 应用

              1.传统 HTTP 的限制

              HTTP 是请求-响应模式,也就是说每个请求都是独立的。

              WebSocket 是全双工通信。全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行。

              用一个例子来解释一下两个的区别:

              WebSocket(ws)就像你在餐厅里用餐,旁边有一位专门跟着你的服务员。你点菜,服务员会立刻把你的要求传达给厨房,菜做好后立刻送到你桌上,甚至如果厨房需要你的建议,服务员也能立即传达给你。

              而 HTTP 就像整个餐厅共用一批服务员。可能 A 服务员会给你送第一道菜,但送第二道菜的时候会换成 B 服务员。而且服务员之间不共享信息。也就是说,如果你向 A 服务员点了菜,当你问 B 服务员的时候,B 会回答你“很快就上了”,但实际上 B 并不知道这个事情,只是在安慰你的情绪。

              从资源使用上讲,相比于 HTTP,WebSocket 更具优势。相比于每次通讯需要建立连接,WebSocket只需要维持 TCP 即可。

              ![[Pasted image 20231209204843.png]]

              2.WebSocket 连接的建立

              WebSocket 连接流程
              1. 建立 TCP 连接:WebSocket 连接首先需要建立一个基础的 TCP 连接,这是因为WebSocket 是基于 TCP 的。
              2. 发送特殊的 HTTP 请求(WebSocket 握手):客户端会发送一个特殊的 HTTP 请求,这个请求被称为 WebSocket 握手请求。这个请求包含一些特殊的头部信息,其中包括 Upgrade 和 Connection 字段,告诉服务器希望升级到 WebSocket 协议。服务器收到这个请求后,如果支持 WebSocket,就会进行协议升级。
              3. 服务器确认协议升级:如果服务器支持 WebSocket,它会发送一个类似的响应,这个响应也包含特殊的头部信息,告诉客户端协议已经升级到 WebSocket。这样,从此之后,客户端和服务器之间的通信就不再遵循传统的 HTTP 协议,而是遵循 WebSocket 协议。
              4. 使用 WebSocket 协议进行通讯:一旦协议升级完成,客户端和服务器就可以使用 WebSocket 协议进行实时的双向通信,发送和接收数据帧而无需频繁地建立和断开连接。

                ![[Pasted image 20231209204917.png]]

              WebSocket 握手

              创建HTTP请求,对连接进行升级参数如下:

              # 请求头Request Headers# websocket版本Sec-WebSocket-Version: 13# 唯一口令,base64编码Sec-WebSocket-Key: 2icxlyJOYxKXJpCXa8T14Q==# 连接升级为websocket协议Connection: UpgradeUpgrade: websocketSec-WebSocket-Extensions: permessage-deflate; client_max_window_bitsHost: 127.0.0.1:9095# 返回值Response Headers# 升级为websocket协议Upgrade: websocketConnection: upgrade# 口令Sec-WebSocket-Accept: stCq5oQ38vohTpeBYUTMb0IU8Fo=Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15

              ![[Pasted image 20231209202336.png]]

              ![[Pasted image 20231209202607.png]]

              3.分布式环境中的 Session 共享

              分布式问题

              在生产中,程序往往是以分布式进行部署的,即启动多份程序,通过代理提高并发能力。

              如下图,用户A 通过负载在节点1上建立了 WebSocket 连接,连接的 Session 也存储在节点1。当 用户A 的数据通过其他方式到达系统时,通过负载代理,最终到达节点4,当节点4对数据进行发送时,发现此节点没有用户A的Session,这样就导致了分布式情况下出现发送不了的情况。

              WebSocket 的 Session 是继承自 Closeable,不能像 HttpSession 一样,把内容序列化到Redis中,每个客户端都会与服务器之间建立独立的 WebSocket 连接,Session 存储在建立连接的服务中,那么就引申出 WebSocket 的分布式问题。

              ![[Pasted image 20231129215436.png]]

              解决方法

              解决方法大概有三种,分别为中间件广播处理、服务间建立WebSocket连接、请求代理哈希转发。

              1. 中间件广播处理:利用中间件广播的能力,当出现需要发送的消息时,广播所有节点,至少有一个节点能够处理此消息,发送给客户端。
              2. 服务间建立WebSocket连接:所有服务间建立WebSocket连接,分内外两部分连接,外部为用户发起的连接,内部为节点间的连接。通过记录用户连接关系,即可知消息需发送的客户端节点。
              3. 请求代理哈希转发:通过重写代理的方式,获取请求头中用户信息,哈希分配到指定节点处理操作。

              下面是不同方法间的对比:

              中间件广播处理服务间建立WebSocket连接请求代理哈希转发
              优势简单易实现点对点通讯,灵活性较高定向请求,保证数据一致性
              劣势依赖于中间件,广播效率低系统复杂,需维护大量连接可能负载不均衡,需自定义负载策略
              扩展性扩展性好随节点增加复杂节点增加减少需重新计算哈希值
              分析

              第二种、第三种方法是可以解决分布式的问题,但在实现难易程度、扩展性方面相比第一种不具有优势。

              • 第二种,需要维护众多WebSocket连接,需要节点间长连接,自动重连等功能,维护调试成本高。
              • 第三种,需要对集群节点可用数量精确把控,若节点已经down掉,需要重新计算哈希值,以免代理到此节点上,维护成本高。

                综上所述,选择第一种方案相比来讲,在开发、维护方面更加具有优势。

                ![[Pasted image 20231129215455.png]]

                五、WebSocket案例

                1.业务场景和需求

                消息数据通过三方接口接入到系统中,服务端主动发送消息到小程序,小程序进行页面跳转,展示消息(由于小程序不支持SSE,选择WebSocket解决主动推送问题)。

                要求有较高实时性。由于消息不知什么时候发送到系统中,所以连接在跳转前一直维持。

                2.分析

                由于消息会不定时接入到系统中,请求通过负载到不同节点,需要保持连接,且解决分布式问题。分析结果如下:

                1. 分布式问题。消息接收节点 与 用户建立 WebSocket 连接节点,两节点不一定一致。
                2. 连接空挡。连接到达最大时间后自动断开,与下次连接间存在时间差。所以需要重复连接,这样会导致多个连接存在,消息多次发送问题。
                3. 恶意连接。恶意使用不存在的 key 进行恶意连接。

                针对上述问题方案如下:

                1. 使用MQ广播解决分布式问题。接收消息广播的方式分发。
                2. 在 WebSocket 连接成功后也进行广播,目的是断开此人其他的连接,保证连接有且仅有一个。
                3. 在连接建立前重写钩子函数,对用户进行校验。

                ![[Pasted image 20231129220451.png]]

                3.关键代码

                对Session进行封装,扩展Session数据,添加Session管理类,封装相应的方法

                public class SessionExt {      private WebSocketSession session;      private String uniqueId;    ... 省略 get set 方法}
                @Component  public class SessionContainer {      private static final ConcurrentHashMap SESSION_MAP =              new ConcurrentHashMap(WebSocketConstants.INT_16);        /**       * 获取 session 数据       *       * @param key key       * @return session       */         public SessionExt getSessionExt(String key) {          return SESSION_MAP.getOrDefault(key, null);      }        /**       * 添加 session 数据       *       * @param key        key       * @param sessionExt sessionExt       */         public void addSessionExt(String key, SessionExt sessionExt) {          SESSION_MAP.put(key, sessionExt);      }        /**       * 添加 session 数据       *       * @param key        key       * @param sessionExt sessionExt       */         public void addSessionExtAndClose(String key, SessionExt sessionExt) {          SessionExt sessionExtOld = SESSION_MAP.get(key);          if (sessionExtOld != null) {              sessionExtOld.closeSession();          }          SESSION_MAP.put(key, sessionExt);      }        /**       * 删除 session 数据       *       * @param key key       */         public void delSessionExt(String key) {          SESSION_MAP.remove(key);      }  }

                连接创建、关闭后的广播回调,关闭多余连接

                 public void handleOpenProcessing(String key, String uniqueId) {      SessionExt sessionExt = SESSION_CONTAINER.getSessionExt(key);      if (sessionExt != null) {          String uniqueIdOld = sessionExt.getUniqueId();          if (!uniqueIdOld.equals(uniqueId)) {              log.debug("[websocket]广播,连接后置处理,关闭用户之前连接");              sessionExt.closeSession();              SESSION_CONTAINER.delSessionExt(key);          }      }      log.info("[websocket]广播,连接后置处理,完成");  }    public void handleCloseProcessing(String key, String uniqueId) {      log.debug("[websocket]广播处理 关闭连接,key:" + key);      SessionExt sessionExt = SESSION_CONTAINER.getSessionExt(key);      if (sessionExt != null) {          String uniqueIdOld = sessionExt.getUniqueId();          if (uniqueIdOld.equals(uniqueId)) {              sessionExt.closeSession();              SESSION_CONTAINER.delSessionExt(key);          }      }      log.info("[websocket]广播,关闭后置处理,完成");  }

                六、总结

                本文介绍了 WebSocket 协议及其在实时通信中的重要性。因为传统 HTTP 协议的限制,引出了 WebSocket 协议,展现了 WebSocket 相对于传统HTTP的优越性。

                对WebSocket连接的建立过程进行了详细解析,包括TCP连接的建立、特殊HTTP请求的发送以及协议升级的过程。

                最后通过具体案例 WebSocket 在分布式环境中的 Session 共享问题,并提出了解决方案。

上一篇:服务器上的内存怎么用?

下一篇:域名mx记录怎么设置方法?