MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

WebSocket与长轮询的区别

2022-03-241.9k 阅读

WebSocket 与长轮询基础概念

WebSocket 基础

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它于2011年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

从协议层面看,WebSocket 协议本质上是一个基于 TCP 的协议。它通过 HTTP/1.1 协议的101状态码进行握手。例如,客户端发起 WebSocket 握手请求时,其请求头可能如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec - WebSocket - Version: 13

服务器如果接受该请求,会返回类似如下的响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

一旦握手成功,后续的数据传输就不再需要 HTTP 协议头,大大减少了数据传输量。

在代码实现上,以 Python 的 Flask - SocketIO 库为例,简单的 WebSocket 服务端代码如下:

from flask import Flask
from flask_socketio import SocketIO

app = Flask(__name__)
app.config['SECRET_KEY'] ='secret!'
socketio = SocketIO(app)

@socketio.on('connect')
def handle_connect():
    print('Client connected')

@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')

if __name__ == '__main__':
    socketio.run(app, debug=True)

客户端可以使用 JavaScript 的 WebSocket API 进行连接:

const socket = new WebSocket('ws://localhost:5000');
socket.onopen = function (event) {
    console.log('Connected to server');
};
socket.onmessage = function (event) {
    console.log('Received message:', event.data);
};
socket.onclose = function (event) {
    console.log('Connection closed');
};

长轮询基础

长轮询是一种 HTTP 协议下实现服务器推送的技术。它的基本原理是客户端向服务器发送请求,服务器如果没有新的数据,不会立即响应,而是保持这个请求打开,直到有新的数据或者达到一个超时时间才响应。客户端收到响应后,会立刻再次发起同样的请求,如此循环。

与传统轮询(客户端定时向服务器发送请求获取数据,不管服务器有无新数据都会立即响应)不同,长轮询减少了不必要的请求,因为只有当服务器有数据更新时才会响应。

例如,用 Node.js 实现一个简单的长轮询服务端:

const http = require('http');
const url = require('url');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
    const parsedUrl = url.parse(req.url);
    const query = querystring.parse(parsedUrl.query);

    // 模拟数据更新
    const newData = Math.random() > 0.5? 'New data available' : null;

    if (newData) {
        res.writeHead(200, { 'Content - Type': 'text/plain' });
        res.end(newData);
    } else {
        // 模拟等待新数据
        setTimeout(() => {
            res.writeHead(200, { 'Content - Type': 'text/plain' });
            res.end('No new data yet');
        }, 5000);
    }
});

server.listen(3000, () => {
    console.log('Server running on port 3000');
});

客户端可以使用 AJAX 来实现长轮询:

function longPolling() {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', '/long - poll', true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                console.log('Received:', xhr.responseText);
            } else {
                console.error('Error:', xhr.status);
            }
            longPolling();
        }
    };
    xhr.send();
}

longPolling();

协议层面区别

WebSocket 协议特点

  1. 全双工通信:WebSocket 协议允许客户端和服务器在同一时间互相发送数据,这是它最显著的特点。这种双向通信能力使得服务器可以主动推送数据给客户端,而不需要客户端不断发起请求。例如在实时聊天应用中,当一方发送消息后,服务器可以立即将消息推送给另一方,实现近乎实时的通信效果。
  2. 单一 TCP 连接:WebSocket 基于单个 TCP 连接进行通信,减少了连接建立和关闭的开销。与 HTTP 协议不同,HTTP 每次请求响应都需要建立新的连接(虽然 HTTP/1.1 引入了持久连接,但在实际应用中仍存在一定的连接管理开销),而 WebSocket 一旦握手成功,连接就会一直保持,直到双方主动关闭。
  3. 轻量级协议头:WebSocket 握手完成后,后续的数据传输协议头非常轻量级。相比 HTTP 协议,HTTP 每次请求都需要携带大量的头部信息(如 Cookie、User - Agent 等),而 WebSocket 数据帧头只有 2 - 14 字节,大大减少了数据传输量,提高了传输效率,特别适合在网络带宽有限的情况下进行实时数据传输。

长轮询基于 HTTP 协议的局限

  1. 半双工通信本质:长轮询是基于 HTTP 协议实现的,而 HTTP 协议本身是半双工的,即客户端发起请求,服务器响应后,在这个连接上只能由客户端再次发起请求才能进行数据传输。长轮询通过不断地由客户端发起请求来模拟服务器推送的效果,但它并不能真正实现服务器主动推送数据,这与 WebSocket 的全双工通信有着本质的区别。
  2. 非持久连接:HTTP 协议默认是非持久连接,虽然可以通过设置 Connection: keep - alive 来实现一定程度的持久连接,但在长轮询场景下,每次请求响应后,客户端需要重新发起请求,这就导致了连接的频繁建立和关闭(即使使用持久连接,也存在连接管理的开销)。相比之下,WebSocket 的单一持久连接在性能上具有明显优势。
  3. 重量级协议头:每次长轮询请求都需要携带完整的 HTTP 协议头,包括 Cookie、各种 HTTP 头字段等,这些头部信息会占用一定的带宽。尤其在频繁请求的情况下,协议头带来的数据传输开销会变得比较显著,降低了数据传输的效率,特别是在移动网络等带宽受限的环境中,这种影响更为明显。

性能表现差异

WebSocket 性能优势

  1. 低延迟:由于 WebSocket 的全双工通信和持久连接特性,服务器可以立即将数据推送给客户端,无需等待客户端下一次请求。在实时应用如在线游戏、金融行情实时显示等场景中,低延迟至关重要。例如,在一个多人在线游戏中,玩家的操作需要及时反馈给其他玩家,WebSocket 能够快速地将玩家的操作数据推送给相关客户端,保证游戏的流畅性和实时性。
  2. 减少网络流量:WebSocket 轻量级的协议头以及持久连接减少了不必要的请求和响应,从而降低了网络流量。在移动应用中,节省网络流量不仅可以降低用户的流量费用,还可以提高应用在弱网环境下的稳定性。例如,一个实时更新的移动新闻应用,如果使用 WebSocket 进行数据推送,相比长轮询,它可以在相同的数据量下减少网络传输的数据量,提高数据传输效率。
  3. 高效的资源利用:单一的 TCP 连接使得服务器和客户端在资源管理上更加高效。服务器不需要为每个请求创建和管理新的连接,减少了服务器的资源消耗。同时,客户端也可以更有效地管理网络连接资源,在处理多个实时数据推送任务时,不会因为过多的连接而导致资源耗尽。例如,在一个物联网应用中,大量的设备通过 WebSocket 与服务器进行通信,服务器可以通过较少的资源开销来管理这些连接,实现高效的数据交互。

长轮询性能劣势

  1. 高延迟:长轮询需要客户端不断发起请求,即使服务器没有新数据,客户端也需要等待服务器响应超时后再次发起请求。这种机制导致了数据传输的延迟相对较高,特别是在服务器数据更新不频繁的情况下,客户端可能需要等待较长时间才能获取到新数据。例如,在一个监控系统中,如果使用长轮询获取设备状态更新,当设备状态长时间不变时,客户端可能需要多次发起无意义的请求,并且在服务器有新状态更新时,也需要等待下一次请求才能获取到,这就造成了延迟。
  2. 高网络流量:频繁的请求和响应以及重量级的 HTTP 协议头导致长轮询的网络流量较大。每次请求都需要携带完整的 HTTP 协议头,即使服务器返回的数据量很小,协议头的开销也不可忽视。在大量客户端同时进行长轮询的情况下,网络带宽会被大量占用,可能导致网络拥塞,影响整个系统的性能。例如,在一个有大量用户的在线投票系统中,如果使用长轮询实时获取投票结果,每个用户的频繁请求会使得网络流量急剧增加,可能导致服务器和网络负载过高。
  3. 资源消耗大:长轮询需要服务器长时间保持请求连接,直到有数据或者超时,这会占用服务器的大量资源。同时,客户端频繁发起请求也会消耗客户端的 CPU 和网络资源。在大规模应用中,这种资源消耗可能成为系统扩展的瓶颈。例如,一个支持大量用户的实时聊天系统,如果使用长轮询实现消息推送,服务器需要为每个用户的长轮询请求分配资源,随着用户数量的增加,服务器的资源压力会越来越大。

适用场景分析

WebSocket 适用场景

  1. 实时聊天应用:无论是即时通讯软件还是网页版的聊天工具,WebSocket 的全双工通信和低延迟特性使得消息可以实时传递。用户发送消息后,服务器能够立即将消息推送给接收方,实现近乎面对面交流的实时效果。例如微信网页版、QQ 网页版等,都可以利用 WebSocket 来实现高效的实时聊天功能。
  2. 在线游戏:在多人在线游戏中,玩家的操作、游戏状态的更新等都需要实时传递给其他玩家。WebSocket 的低延迟和高效的数据传输能力可以保证游戏的流畅性和实时性,为玩家提供良好的游戏体验。像一些网页版的休闲游戏、多人在线竞技游戏等,都适合使用 WebSocket 进行数据交互。
  3. 金融行情实时显示:金融市场的行情数据瞬息万变,投资者需要实时获取最新的股票价格、汇率等信息。WebSocket 可以将最新的行情数据及时推送给客户端,投资者无需手动刷新页面就能看到最新数据。例如各大金融网站的实时行情显示,以及一些金融交易软件的行情推送功能,都可以借助 WebSocket 来实现。
  4. 物联网数据传输:物联网设备通常需要实时将采集到的数据上传到服务器,同时服务器也可能需要向设备发送控制指令。WebSocket 的持久连接和双向通信特性非常适合这种场景,能够实现设备与服务器之间高效、稳定的数据交互。例如智能家居系统中,智能设备通过 WebSocket 与服务器连接,服务器可以实时获取设备状态并发送控制命令,实现远程控制和监控。

长轮询适用场景

  1. 对实时性要求不是极高的场景:在一些应用中,虽然需要获取服务器的最新数据,但对实时性的要求不是特别严格,允许有一定的延迟。例如一些企业内部的通知系统,员工可能不需要在通知发布的瞬间就收到,几分钟的延迟是可以接受的。这种情况下,长轮询可以满足需求,并且相对于 WebSocket 来说,实现成本较低。
  2. 兼容性要求高的场景:在一些老旧的浏览器或者网络环境中,WebSocket 的支持可能存在问题。而长轮询基于 HTTP 协议,几乎所有的浏览器和网络环境都能很好地支持。例如在一些政府、企业的内部系统中,可能存在部分老旧设备或者浏览器,为了保证系统的兼容性,长轮询可能是一个更合适的选择。
  3. 简单数据获取场景:当应用只需要定期获取一些简单的数据,并且数据量不大时,长轮询可以满足需求。例如一个简单的天气预报查询页面,用户可能每隔一段时间获取一次最新的天气信息,这种情况下使用长轮询实现起来简单方便,不需要引入 WebSocket 这种相对复杂的技术。

安全性对比

WebSocket 安全性

  1. 协议层面安全:WebSocket 协议在设计上考虑了安全性。它通过 HTTP 协议进行握手,并且在握手过程中,服务器会对客户端发送的 Sec - WebSocket - Key 进行验证,生成 Sec - WebSocket - Accept 响应头返回给客户端,只有当客户端验证通过这个响应头,才会建立起 WebSocket 连接。这在一定程度上防止了非法连接的建立。
  2. 传输加密:WebSocket 可以通过 HTTPS 协议进行传输加密,保证数据在传输过程中的保密性和完整性。在实际应用中,特别是涉及到用户敏感信息的场景,如实时金融交易、在线支付等,使用 HTTPS 与 WebSocket 结合可以有效防止数据被窃取或篡改。
  3. 安全漏洞防范:虽然 WebSocket 本身相对安全,但在实际应用中,也可能存在一些安全漏洞,如跨站 WebSocket 劫持(CSWSH)等。开发者需要采取一些防范措施,如使用 CSRF 令牌、验证来源等,来确保 WebSocket 连接的安全性。

长轮询安全性

  1. 基于 HTTP 安全机制:长轮询基于 HTTP 协议,因此它可以利用 HTTP 协议的安全机制,如 HTTPS 加密传输。通过使用 HTTPS,长轮询请求和响应的数据在传输过程中同样可以得到加密保护,防止数据被窃听和篡改。
  2. 安全风险:然而,长轮询也存在一些特定的安全风险。由于它是通过不断发起 HTTP 请求来实现的,可能会遭受 DDoS 攻击,大量的恶意请求可能会耗尽服务器资源,导致服务器无法正常工作。另外,在处理长轮询请求时,如果服务器端没有正确验证和过滤输入数据,也可能存在 SQL 注入、XSS 等安全漏洞。

实现复杂度比较

WebSocket 实现复杂度

  1. 服务端实现:WebSocket 的服务端实现相对复杂,需要专门的库来处理 WebSocket 协议的握手、数据帧的解析和组装等。不同的编程语言都有相应的 WebSocket 库,如 Python 的 Flask - SocketIO、Java 的 Spring WebSocket 等。这些库虽然提供了方便的接口,但开发者需要了解 WebSocket 协议的细节,以便正确配置和使用。例如,在使用 Flask - SocketIO 时,开发者需要处理连接、断开连接、消息接收等各种事件,并且要确保服务器能够高效地处理大量的 WebSocket 连接。
  2. 客户端实现:客户端使用 JavaScript 的 WebSocket API 相对简单,只需要创建一个 WebSocket 对象,然后监听 openmessageclose 等事件即可。但在实际应用中,可能需要处理连接重试、错误处理等复杂逻辑,以确保在网络不稳定的情况下,客户端能够保持与服务器的连接。例如,当网络中断后,客户端需要能够自动尝试重新连接服务器,并且要处理在重新连接过程中可能出现的各种错误。

长轮询实现复杂度

  1. 服务端实现:长轮询的服务端实现相对简单,因为它基于 HTTP 协议,开发者可以使用现有的 HTTP 服务器框架来实现。只需要在服务器端处理请求时,根据是否有新数据来决定是立即响应还是等待一段时间后响应。例如在 Node.js 中,使用内置的 http 模块就可以很容易地实现长轮询服务端,不需要引入额外复杂的库。
  2. 客户端实现:客户端使用 AJAX 来实现长轮询也比较简单,通过创建 XMLHttpRequest 对象,设置请求方法、URL 等参数,然后监听 readystatechange 事件来处理响应。与 WebSocket 客户端相比,长轮询客户端不需要处理复杂的连接管理逻辑,只需要在每次响应后重新发起请求即可。但在实际应用中,也需要考虑一些细节,如请求超时处理、错误处理等,以保证长轮询的稳定性。

可扩展性分析

WebSocket 可扩展性

  1. 连接管理:随着客户端数量的增加,WebSocket 服务器需要管理大量的持久连接。这对服务器的资源(如内存、CPU 等)提出了较高的要求。为了提高可扩展性,通常需要采用分布式架构,将连接分散到多个服务器节点上进行管理。例如,可以使用负载均衡器将 WebSocket 连接分发到多个应用服务器上,每个应用服务器负责处理一部分连接。同时,还可以使用缓存技术来存储连接状态等信息,提高服务器的处理效率。
  2. 消息处理:在大规模应用中,WebSocket 服务器需要高效地处理大量的消息推送。可以采用消息队列来解耦消息的发送和接收,将消息先发送到消息队列中,然后由多个消费者从队列中获取消息并推送给相应的客户端。这样可以提高消息处理的并行度,增强系统的可扩展性。例如,使用 RabbitMQ、Kafka 等消息队列系统来处理 WebSocket 消息推送。
  3. 集群部署:为了应对高并发的 WebSocket 连接,集群部署是必不可少的。通过将多个 WebSocket 服务器组成集群,可以实现负载均衡和故障转移。当某个服务器节点出现故障时,其他节点可以继续提供服务,保证系统的可用性。例如,使用 Docker 和 Kubernetes 等容器编排技术来实现 WebSocket 服务器的集群部署,提高系统的可扩展性和稳定性。

长轮询可扩展性

  1. 请求处理:长轮询由于是基于 HTTP 请求,服务器在处理大量客户端的长轮询请求时,会面临较大的压力。每个请求都需要占用服务器的资源,包括线程、内存等。随着客户端数量的增加,服务器的性能会逐渐下降。为了提高可扩展性,可以采用缓存技术来减少重复请求的处理,例如将一些常用的数据缓存起来,当客户端发起长轮询请求时,直接从缓存中获取数据,而不需要进行复杂的查询和处理。
  2. 负载均衡:与 WebSocket 类似,长轮询服务也可以采用负载均衡技术来将请求分发到多个服务器节点上。通过负载均衡器,可以根据服务器的负载情况,将长轮询请求合理地分配到各个服务器上,提高系统的整体处理能力。但由于长轮询请求的特性,负载均衡器在处理时可能需要考虑更多的因素,如请求的超时时间、连接保持等。
  3. 资源优化:在长轮询场景下,为了提高可扩展性,还需要对服务器资源进行优化。例如,合理设置请求的超时时间,避免过长的请求占用过多的资源。同时,对服务器的内存、CPU 等资源进行监控和调优,确保服务器在高负载情况下能够稳定运行。但总体来说,由于长轮询的特性,其可扩展性相对 WebSocket 较差,在大规模应用中可能面临更多的挑战。