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

深入理解WebSocket的全双工通信模式

2023-12-066.8k 阅读

WebSocket全双工通信模式的概念

传统HTTP通信模式的局限性

在探讨WebSocket的全双工通信模式之前,先来回顾一下传统HTTP通信模式的特点。HTTP协议是基于请求 - 响应模型的。客户端发起一个请求,服务器接收到请求后进行处理,并返回相应的响应。这种模式在很多场景下工作良好,例如网页的加载,用户请求一个HTML页面,服务器返回该页面的内容。

然而,随着互联网应用的发展,这种模式暴露出了一些局限性。以实时性要求较高的场景为例,比如在线聊天、实时股票行情展示等。在传统HTTP模式下,如果要实现实时数据更新,通常会采用轮询(Polling)或者长轮询(Long Polling)的方式。

轮询是指客户端定时向服务器发送请求,询问是否有新的数据。这种方式的缺点很明显,会产生大量不必要的请求,浪费网络带宽和服务器资源,因为在大部分情况下,服务器可能并没有新的数据。长轮询则是客户端发起一个请求,服务器如果没有新数据,会保持这个连接,直到有新数据或者连接超时才返回响应。客户端接收到响应后,会立即发起下一个请求。虽然长轮询相比轮询减少了不必要的请求,但依然存在资源浪费的问题,而且连接的维护也增加了服务器的负担。

WebSocket全双工通信模式的定义

WebSocket是一种在单个TCP连接上进行全双工通信的协议。全双工通信意味着通信的双方可以同时发送和接收数据。与传统HTTP的请求 - 响应模式不同,WebSocket建立连接后,客户端和服务器可以随时向对方发送消息,无需像HTTP那样等待请求和响应的交替。

WebSocket协议在客户端和服务器之间建立了一个持久的连接。一旦连接建立,双方就可以通过这个连接进行双向数据传输。这种特性使得WebSocket非常适合实时性要求高的应用场景,如在线游戏、实时协作工具等。

WebSocket协议的优势

  1. 实时性:由于可以双向实时通信,服务器端一旦有新的数据,能够立即推送给客户端,无需客户端不断请求询问。这极大地提高了数据传输的及时性,提升了用户体验。
  2. 高效性:相比轮询和长轮询,WebSocket减少了不必要的请求 - 响应开销。一旦连接建立,双方可以直接进行数据传输,节省了网络带宽和服务器资源。
  3. 建立持久连接:WebSocket建立的是持久连接,不像HTTP每次请求 - 响应完成后连接就关闭。这使得通信更加稳定,对于需要频繁交互的应用场景非常有利。

WebSocket协议的工作原理

WebSocket握手过程

WebSocket的通信是从握手过程开始的。客户端通过HTTP协议发起一个特殊的HTTP请求,这个请求中包含了升级协议的信息,告诉服务器想要将HTTP协议升级为WebSocket协议。

以下是一个简单的WebSocket握手请求示例:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Key: dGhlIHNhbXBsZSBub25jZQ==
Sec - WebSocket - Version: 13
  1. Upgrade和Connection头字段Upgrade: websocketConnection: Upgrade 这两个头字段表明客户端希望将当前连接升级为WebSocket连接。
  2. Sec - WebSocket - Key:这个字段是一个Base64编码的随机字符串,用于验证服务器是否支持WebSocket协议。服务器会使用这个密钥进行一些计算,然后在响应中返回。
  3. Sec - WebSocket - Version:指定客户端支持的WebSocket协议版本。目前常用的版本是13。

服务器接收到这个请求后,如果支持WebSocket协议,会返回一个响应,示例如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec - WebSocket - Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  1. 101 Switching Protocols状态码:表示服务器同意将协议升级为WebSocket协议。
  2. Sec - WebSocket - Accept:服务器会将客户端发送的 Sec - WebSocket - Key 加上一个固定的字符串 258EAFA5 - E914 - 47DA - 95CA - C5AB0DC85B11,然后对这个新的字符串进行SHA - 1哈希计算,最后将结果进行Base64编码,作为 Sec - WebSocket - Accept 的值返回给客户端。客户端接收到这个值后,会进行同样的计算并验证,如果验证通过,则握手成功,WebSocket连接建立。

WebSocket数据帧格式

WebSocket通过数据帧来传输数据。数据帧的格式定义了如何在连接上发送和接收数据。

一个WebSocket数据帧由以下部分组成:

  1. Opcode:操作码,用于表示数据帧的类型。常见的操作码有:
    • 0x0:表示这是一个延续帧(Continuation Frame),用于传输一个较大消息的后续部分。
    • 0x1:表示文本帧(Text Frame),数据是UTF - 8编码的文本。
    • 0x2:表示二进制帧(Binary Frame),数据是二进制格式。
    • 0x8:表示关闭连接帧(Close Frame),用于关闭WebSocket连接。
    • 0x9:表示ping帧(Ping Frame),用于检查连接是否存活。
    • 0xA:表示pong帧(Pong Frame),是对ping帧的响应。
  2. Mask:掩码位,用于标识数据是否被掩码处理。在客户端发送的数据帧中,这个位总是设置为1,并且会提供一个掩码密钥。服务器接收到数据帧后,需要根据掩码密钥对数据进行解掩码操作。在服务器发送的数据帧中,这个位总是0。
  3. Payload Length:有效载荷长度,即数据部分的长度。如果长度小于126,那么这个字段直接表示长度值。如果长度为126,那么后续2个字节表示长度值。如果长度为127,那么后续8个字节表示长度值。
  4. Masking - Key(可选):掩码密钥,用于对数据进行掩码和解掩码操作。只有当Mask位为1时才存在。
  5. Payload Data:有效载荷数据,即实际要传输的数据。

了解数据帧格式对于深入理解WebSocket的工作原理非常重要,因为在编写WebSocket服务器和客户端代码时,需要正确地处理数据帧的解析和构建。

WebSocket连接的生命周期

  1. 连接建立:通过前面介绍的握手过程,客户端和服务器成功建立WebSocket连接。此时,双方可以开始进行双向通信。
  2. 数据传输:在连接建立后,客户端和服务器可以随时向对方发送数据帧。根据业务需求,可以发送文本帧、二进制帧等不同类型的数据帧。
  3. 连接关闭:WebSocket连接可以由客户端或服务器发起关闭。当一方发送关闭连接帧(Opcode为0x8)时,另一方收到后会回复一个关闭连接帧,然后双方关闭连接。关闭连接帧中可以包含一个状态码和关闭原因,用于告知对方关闭的原因。

WebSocket在后端开发中的应用

使用Node.js搭建WebSocket服务器

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,它提供了丰富的网络编程能力,非常适合用于搭建WebSocket服务器。在Node.js中,可以使用 ws 库来实现WebSocket服务器。

首先,通过npm安装 ws 库:

npm install ws

以下是一个简单的Node.js WebSocket服务器示例代码:

const WebSocket = require('ws');

// 创建WebSocket服务器实例
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    // 当有客户端连接时
    console.log('A client has connected');

    ws.on('message', function incoming(message) {
        // 接收到客户端发送的消息
        console.log('Received message from client:', message);

        // 向客户端发送消息
        ws.send('Server received your message: ' + message);
    });

    ws.on('close', function close() {
        // 客户端关闭连接时
        console.log('A client has disconnected');
    });
});

在上述代码中:

  1. 首先引入了 ws 库,并创建了一个WebSocket服务器实例,监听在8080端口。
  2. 当有客户端连接到服务器时,会触发 connection 事件,在这个事件处理函数中,可以对新连接的客户端进行一些初始化操作,如记录连接信息等。
  3. 当服务器接收到客户端发送的消息时,会触发 message 事件,在这个事件处理函数中,先打印接收到的消息,然后向客户端回发一条包含客户端发送内容的消息。
  4. 当客户端关闭连接时,会触发 close 事件,在这个事件处理函数中,打印客户端断开连接的信息。

使用Python搭建WebSocket服务器

Python也有多种库可以用于搭建WebSocket服务器,其中 websockets 库是一个简单易用的选择。

首先,通过pip安装 websockets 库:

pip install websockets

以下是一个Python WebSocket服务器示例代码:

import asyncio
import websockets

async def handle_connection(websocket, path):
    try:
        # 接收到客户端发送的消息
        message = await websocket.recv()
        print('Received message from client:', message)

        # 向客户端发送消息
        response = 'Server received your message: ' + message
        await websocket.send(response)
    except websockets.exceptions.ConnectionClosedOK:
        # 客户端正常关闭连接
        print('A client has disconnected')

start_server = websockets.serve(handle_connection, 'localhost', 8080)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

在上述代码中:

  1. 引入了 asyncio 库用于异步编程,以及 websockets 库。
  2. 定义了一个 handle_connection 异步函数,当有客户端连接时,这个函数会被调用。在函数中,首先使用 await websocket.recv() 接收客户端发送的消息,然后打印消息,并构造一个响应消息发送回客户端。如果客户端正常关闭连接,会捕获 websockets.exceptions.ConnectionClosedOK 异常并打印客户端断开连接的信息。
  3. 使用 websockets.serve 创建一个WebSocket服务器,绑定到 localhost 的8080端口,并使用 asyncio 的事件循环来启动和运行服务器。

WebSocket在实时应用中的应用场景

  1. 在线聊天应用:WebSocket可以实现实时的消息推送,用户发送的消息能够立即被对方接收到。服务器可以维护一个用户连接列表,当有新消息时,将消息推送给对应的接收方。
  2. 实时数据监控:例如服务器性能监控、网络流量监控等场景。监控系统可以通过WebSocket将实时数据推送给前端页面,前端页面能够实时展示最新的数据变化,无需手动刷新。
  3. 在线游戏:WebSocket提供的低延迟、双向通信能力非常适合在线游戏。游戏服务器可以实时向玩家推送游戏状态变化,如其他玩家的位置移动、游戏事件等,同时接收玩家的操作指令。

WebSocket与其他技术的对比

WebSocket与HTTP/2 Server - Sent Events

  1. HTTP/2 Server - Sent Events(SSE):SSE是一种允许Web服务器向Web浏览器发送实时更新的技术。它基于HTTP协议,通过单向连接从服务器向客户端推送数据。客户端使用 EventSource 对象来接收服务器发送的事件流。

  2. 对比

    • 通信方向:WebSocket是全双工通信,客户端和服务器可以双向通信;而SSE是单向通信,只能由服务器向客户端推送数据。
    • 应用场景:如果应用场景只需要服务器向客户端推送数据,如新闻推送、日志监控等,SSE可能是一个不错的选择,因为它相对简单。但如果需要双向实时交互,如在线聊天、实时协作等,WebSocket则更为合适。
    • 连接管理:WebSocket建立的是持久连接,需要更复杂的连接管理和错误处理机制。SSE虽然也是基于持久连接,但由于是单向通信,连接管理相对简单。

WebSocket与MQTT

  1. MQTT(Message Queuing Telemetry Transport):MQTT是一种轻量级的发布/订阅消息传输协议,主要用于物联网(IoT)设备之间的通信。它基于TCP/IP协议,具有低带宽、低功耗的特点。

  2. 对比

    • 设计目标:WebSocket主要设计用于Web应用的实时双向通信,而MQTT更侧重于物联网设备之间的消息传递,适用于资源受限的设备和网络环境。
    • 通信模型:WebSocket采用的是客户端 - 服务器直接通信模型,而MQTT采用发布/订阅模型。在MQTT中,消息发布者将消息发布到主题(Topic),多个订阅者可以订阅感兴趣的主题来接收消息。
    • 协议开销:MQTT协议的设计非常轻量级,协议开销小,适合在低带宽、不稳定网络环境下工作。WebSocket虽然也高效,但相比MQTT,在极端环境下可能稍显不足。

WebSocket开发中的注意事项

安全问题

  1. 跨站WebSocket劫持(CSWSH):类似于跨站请求伪造(CSRF),攻击者可能利用用户已登录的会话,通过伪造WebSocket请求来执行恶意操作。为了防止CSWSH,可以在WebSocket握手过程中使用额外的认证机制,如在请求中添加自定义的认证头字段,并在服务器端进行验证。
  2. 数据加密:如果传输的数据包含敏感信息,如用户密码、金融数据等,应该对数据进行加密。可以在WebSocket之上使用TLS/SSL协议来加密传输的数据,确保数据在传输过程中的安全性。

性能优化

  1. 连接管理:在服务器端,合理管理WebSocket连接资源非常重要。可以使用连接池技术来复用连接,减少连接创建和销毁的开销。同时,对于长时间不活跃的连接,应该及时关闭,以释放资源。
  2. 数据压缩:如果传输的数据量较大,可以考虑使用数据压缩技术,如gzip。在WebSocket数据帧级别,可以对数据进行压缩后再发送,这样可以减少网络传输的数据量,提高传输效率。

兼容性问题

  1. 浏览器兼容性:虽然大多数现代浏览器都支持WebSocket协议,但在一些旧版本浏览器中可能存在兼容性问题。在开发Web应用时,需要考虑对旧版本浏览器的支持,可以使用一些Polyfill库来提供WebSocket的兼容实现。
  2. 协议版本兼容性:WebSocket协议有多个版本,不同版本之间可能存在一些差异。在与其他系统进行集成时,需要确保双方使用的WebSocket协议版本兼容,避免出现通信问题。

通过深入理解WebSocket的全双工通信模式及其相关知识,开发者可以在后端开发中更好地利用WebSocket技术,构建出高效、实时的应用程序。无论是实时数据交互的Web应用,还是物联网设备之间的通信,WebSocket都提供了强大的技术支持。在实际开发过程中,要充分考虑安全、性能和兼容性等问题,以确保应用的稳定运行和良好用户体验。