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

WebSocket与Socket.IO:关系与区别

2022-09-296.8k 阅读

WebSocket 基础

WebSocket 协议概述

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它使得客户端和服务器之间能够进行实时、双向的通信,打破了传统 HTTP 协议请求 - 响应模式的限制。在 HTTP 协议中,通常是客户端发起请求,服务器返回响应,这种模式对于实时性要求高的应用场景(如在线聊天、实时游戏、股票行情更新等)并不适用。而 WebSocket 协议在建立连接后,双方都可以主动向对方发送数据,实现了真正意义上的实时通信。

WebSocket 协议的设计目标是为了在 web 浏览器和服务器之间提供一种高效、低延迟的双向通信方式。它基于 TCP 协议,通过 HTTP 协议进行握手,这使得它能够在现有的网络基础设施(如防火墙、代理服务器等)中顺利通行。一旦握手成功,后续的数据传输就不再依赖于 HTTP 协议,而是直接在 TCP 连接上进行,大大提高了通信效率。

WebSocket 握手过程

WebSocket 的握手过程是通过 HTTP 协议完成的。客户端首先发送一个带有特殊头部的 HTTP 请求,告知服务器它希望建立一个 WebSocket 连接。以下是一个典型的 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

在这个请求中,关键的头部信息有:

  1. Upgrade: websocketConnection: Upgrade:这两个头部表明客户端希望将当前的 HTTP 连接升级为 WebSocket 连接。
  2. Sec - WebSocket - Key:这是一个由客户端生成的随机密钥,用于验证服务器的响应。
  3. Sec - WebSocket - Version:指定客户端支持的 WebSocket 协议版本。

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

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

其中,Sec - WebSocket - Accept 头部的值是通过将客户端发送的 Sec - WebSocket - Key 加上一个固定的字符串 "258EAFA5 - E914 - 47DA - 95CA - C5AB0DC85B11",然后进行 SHA - 1 哈希计算,并将结果进行 Base64 编码得到的。如果客户端验证服务器返回的 Sec - WebSocket - Accept 头部的值正确,那么握手成功,后续就可以在这个 TCP 连接上进行 WebSocket 通信了。

WebSocket 数据帧格式

WebSocket 数据传输是以帧(frame)为单位的。每个帧由头部和负载数据组成。以下是 WebSocket 帧的基本格式:

描述
0 - 7 位Opcode,表示帧的类型,例如文本帧(0x1)、二进制帧(0x2)等。
8 - 15 位掩码位(Mask),如果设置为 1,则负载数据是经过掩码处理的。
16 - 23 位负载数据长度(Payload Length),如果长度小于 126,则直接表示长度;如果长度为 126,则接下来的 2 个字节表示长度;如果长度为 127,则接下来的 8 个字节表示长度。
24 - 31 位及之后掩码密钥(Masking Key),如果掩码位为 1,则存在 4 个字节的掩码密钥,用于对负载数据进行掩码和解掩码操作。
之后负载数据(Payload Data),实际传输的数据。

这种数据帧格式的设计使得 WebSocket 能够在不同的网络环境下高效地传输数据,同时也提供了一定的安全性,例如通过掩码机制防止恶意的中间人攻击。

WebSocket 示例代码(以 Node.js 为例)

在 Node.js 中,可以使用 ws 库来实现 WebSocket 服务器和客户端。

  1. WebSocket 服务器端代码
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    ws.on('message', function incoming(message) {
        console.log('Received: %s', message);
        ws.send('You sent: ' + message);
    });

    ws.send('Connected to the WebSocket server!');
});

在这段代码中,首先创建了一个 WebSocket 服务器实例,并监听在 8080 端口上。当有客户端连接时,会向客户端发送一条欢迎消息。并且当服务器接收到客户端发送的消息时,会将消息回显给客户端。 2. WebSocket 客户端代码

const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:8080');

ws.on('open', function open() {
    ws.send('Hello, server!');
});

ws.on('message', function incoming(data) {
    console.log('Received: %s', data);
});

在客户端代码中,创建了一个 WebSocket 连接到本地服务器的 8080 端口。当连接建立成功后,会向服务器发送一条消息,并在接收到服务器返回的消息时,将其打印到控制台。

Socket.IO 基础

Socket.IO 概述

Socket.IO 是一个基于 WebSocket 协议的实时双向通信库,但它不仅仅依赖于 WebSocket。Socket.IO 的设计目标是提供一个跨平台、跨浏览器的实时通信解决方案,它能够自动检测客户端和服务器环境,并选择最合适的传输方式进行通信。这意味着即使在不支持 WebSocket 的环境中,Socket.IO 也能通过其他方式(如长轮询、Server - Sent Events 等)来实现实时通信。

Socket.IO 主要由两部分组成:客户端库(适用于浏览器、Node.js 等环境)和服务器端库(主要用于 Node.js 服务器)。它提供了一个简单易用的 API,使得开发者可以轻松地实现实时应用,如聊天应用、实时协作工具等。

Socket.IO 传输机制

  1. WebSocket:在支持 WebSocket 的环境中,Socket.IO 优先使用 WebSocket 进行通信,因为它具有高效、低延迟的特点。
  2. 长轮询:当 WebSocket 不可用时,Socket.IO 会退化为长轮询方式。长轮询是一种模拟实时通信的技术,客户端向服务器发送一个请求,服务器在有新数据时才返回响应,否则保持连接打开。客户端在接收到响应后,立即再次发送请求,以此来实现实时数据传输。这种方式虽然不如 WebSocket 高效,但兼容性更好。
  3. Server - Sent Events (SSE):Socket.IO 也支持使用 SSE 作为传输方式。SSE 是一种单向的服务器向客户端推送数据的技术,适用于一些只需要服务器向客户端发送数据的场景。Socket.IO 可以在某些情况下结合 SSE 与其他传输方式来实现双向通信。

Socket.IO 事件机制

Socket.IO 提供了丰富的事件机制,使得开发者可以方便地处理连接、消息接收、断开连接等各种情况。

  1. 连接事件:当客户端成功连接到服务器时,会触发 connect 事件。在服务器端,可以通过以下方式监听这个事件:
const io = require('socket.io')(server);

io.on('connection', function(socket) {
    console.log('A user connected');
});

在客户端,可以这样监听连接成功事件:

const socket = io();

socket.on('connect', function() {
    console.log('Connected to the server');
});
  1. 消息事件:可以自定义各种消息事件来传输数据。例如,在服务器端发送一个名为 chat message 的消息事件:
io.on('connection', function(socket) {
    socket.on('chat message', function(msg) {
        io.emit('chat message', msg);
    });
});

在客户端发送和接收这个消息事件:

socket.on('connect', function() {
    socket.send('Hello, server!');
});

socket.on('chat message', function(msg) {
    console.log('Received chat message: ', msg);
});
  1. 断开连接事件:当客户端断开与服务器的连接时,会触发 disconnect 事件。在服务器端监听这个事件:
io.on('connection', function(socket) {
    socket.on('disconnect', function() {
        console.log('A user disconnected');
    });
});

Socket.IO 示例代码(以 Node.js 为例)

  1. Socket.IO 服务器端代码
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

io.on('connection', function(socket) {
    socket.on('chat message', function(msg) {
        io.emit('chat message', msg);
    });

    socket.on('disconnect', function() {
        console.log('A user disconnected');
    });
});

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

在这段代码中,使用 Express 和 Node.js 的 http 模块创建了一个服务器,并使用 Socket.IO 库来处理实时通信。当有客户端连接时,监听 chat message 事件,并将接收到的消息广播给所有连接的客户端。同时监听 disconnect 事件,在客户端断开连接时打印日志。 2. Socket.IO 客户端代码(在浏览器环境中)

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>Socket.IO Chat</title>
</head>

<body>
    <ul id="messages"></ul>
    <form id="form" autocomplete="off">
        <input id="input" autocomplete="off" /><button>Send</button>
    </form>
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io();

        const form = document.getElementById('form');
        const input = document.getElementById('input');
        const messages = document.getElementById('messages');

        form.addEventListener('submit', function (e) {
            e.preventDefault();
            if (input.value) {
                socket.emit('chat message', input.value);
                input.value = '';
            }
        });

        socket.on('chat message', function (msg) {
            const item = document.createElement('li');
            item.textContent = msg;
            messages.appendChild(item);
            window.scrollTo(0, document.body.scrollHeight);
        });
    </script>
</body>

</html>

在这个 HTML 页面中,引入了 Socket.IO 的客户端库。当用户在输入框中输入内容并提交表单时,会向服务器发送 chat message 事件。当接收到服务器广播的 chat message 事件时,会将消息显示在页面上。

WebSocket 与 Socket.IO 的关系

Socket.IO 基于 WebSocket 进行扩展

Socket.IO 是在 WebSocket 协议的基础上进行了封装和扩展。它利用了 WebSocket 提供的全双工通信能力,为开发者提供了更便捷、更强大的实时通信解决方案。WebSocket 本身只是一个协议,定义了客户端和服务器之间如何建立连接、传输数据等基本规则。而 Socket.IO 则在这个基础上,增加了很多实用的功能,如自动重连、跨域支持、多种传输方式的自动切换等。

例如,在一个复杂的网络环境中,WebSocket 连接可能会因为网络波动等原因而断开。如果使用原生的 WebSocket,开发者需要自己编写代码来处理连接断开后的重连逻辑。而 Socket.IO 内置了自动重连机制,当连接断开时,它会自动尝试重新连接服务器,大大减轻了开发者的负担。

目标与应用场景的相似性

WebSocket 和 Socket.IO 的主要目标都是为了实现客户端和服务器之间的实时通信,因此它们在应用场景上有很多相似之处。两者都适用于在线聊天、实时游戏、股票行情更新、实时监控等需要实时数据交互的场景。

然而,由于 Socket.IO 具有更好的兼容性和更多的功能特性,对于一些需要支持多种浏览器和网络环境,并且对实时通信的稳定性和易用性要求较高的应用,Socket.IO 可能是更好的选择。而对于一些对性能要求极高,且运行环境相对稳定,对兼容性要求不那么苛刻的场景,原生的 WebSocket 可能更合适。

WebSocket 与 Socket.IO 的区别

协议层面

  1. WebSocket:是一种独立的网络协议,有自己明确的协议规范(RFC 6455)。它基于 TCP 协议,通过 HTTP 协议进行握手,握手成功后就直接在 TCP 连接上进行数据传输。WebSocket 协议定义了数据帧格式、连接管理、错误处理等方面的细节。
  2. Socket.IO:并不是一个真正的协议,而是一个基于多种传输方式(包括 WebSocket)的实时通信库。它没有自己独立的协议规范,而是在不同的传输方式上进行封装,提供统一的 API 给开发者使用。Socket.IO 会根据客户端和服务器的环境自动选择最合适的传输方式,如 WebSocket、长轮询等。

兼容性

  1. WebSocket:WebSocket 协议在现代浏览器中得到了广泛支持,但在一些旧版本的浏览器(如 Internet Explorer 10 及以下)中不被支持。此外,在一些网络环境中,如某些代理服务器、防火墙可能会对 WebSocket 连接进行限制。
  2. Socket.IO:Socket.IO 具有更好的兼容性,它可以自动检测客户端的环境,并选择合适的传输方式。即使在不支持 WebSocket 的浏览器或网络环境中,Socket.IO 也能通过长轮询等方式实现实时通信。这使得 Socket.IO 可以应用于更广泛的场景,尤其是需要支持多种浏览器和网络环境的项目。

功能特性

  1. WebSocket:WebSocket 提供了基本的全双工通信功能,开发者需要自己处理很多细节,如连接管理、错误处理、消息的可靠传输等。例如,如果需要实现自动重连功能,就需要在代码中手动编写逻辑来检测连接断开并重新建立连接。
  2. Socket.IO:Socket.IO 提供了丰富的功能特性,除了自动重连外,还包括跨域支持、房间(room)机制、消息广播等。房间机制允许开发者将不同的客户端划分到不同的组中,方便进行针对性的消息发送。例如,在一个多人在线游戏中,可以将同一局游戏的玩家划分到同一个房间,服务器可以向该房间内的所有玩家发送游戏相关的消息,而不会影响其他房间的玩家。

性能

  1. WebSocket:由于 WebSocket 是直接基于 TCP 协议进行通信,没有额外的封装和中间层,因此在性能上具有一定优势。特别是在数据传输量较大、实时性要求极高的场景下,WebSocket 能够提供更低的延迟和更高的吞吐量。
  2. Socket.IO:Socket.IO 虽然在兼容性和功能上表现出色,但由于它需要在不同的传输方式之间进行切换,并且为了实现各种功能特性,会增加一些额外的开销。例如,在使用长轮询传输方式时,会频繁地建立和关闭 HTTP 连接,这会带来一定的性能损耗。因此,在性能要求极高的场景下,Socket.IO 可能不如原生的 WebSocket。

部署与维护

  1. WebSocket:部署 WebSocket 服务器相对简单,只需要一个支持 WebSocket 协议的服务器框架即可。在维护方面,由于 WebSocket 协议相对简单,开发者只需要关注基本的连接管理和数据传输逻辑,维护成本相对较低。
  2. Socket.IO:Socket.IO 服务器的部署也不复杂,但由于它涉及多种传输方式和复杂的功能特性,在维护过程中可能会遇到更多的问题。例如,在处理不同传输方式之间的切换时,可能会出现兼容性问题或性能问题,需要开发者花费更多的精力去排查和解决。

选择 WebSocket 还是 Socket.IO

根据项目需求选择

  1. 兼容性要求:如果项目需要支持较旧的浏览器或复杂的网络环境,Socket.IO 是更好的选择。例如,一个面向大众用户的在线应用,无法保证所有用户都使用最新版本的浏览器,此时 Socket.IO 的自动切换传输方式和更好的兼容性就能发挥重要作用。
  2. 性能要求:对于对性能要求极高,如实时游戏、金融交易系统等场景,原生的 WebSocket 更为合适。这些场景对延迟和吞吐量非常敏感,WebSocket 的直接基于 TCP 协议的通信方式能够满足其高性能需求。
  3. 功能需求:如果项目需要一些高级功能,如自动重连、房间机制、消息广播等,Socket.IO 提供的丰富功能特性可以大大简化开发过程。例如,在开发一个多人在线协作工具时,Socket.IO 的房间机制可以方便地管理不同协作组的用户。

开发团队技术栈

  1. 熟悉程度:如果开发团队对原生的 WebSocket 协议和相关的网络编程有深入的了解,并且项目对性能要求较高,那么使用 WebSocket 可能会更高效。因为团队可以更好地控制和优化底层的通信逻辑。
  2. 学习成本:如果开发团队对实时通信开发经验较少,Socket.IO 的简单易用的 API 和丰富的文档可能更容易上手。它提供了一个统一的接口,不需要开发者过多关注不同传输方式的细节,降低了学习成本。

项目规模与维护成本

  1. 小型项目:对于小型项目,开发周期较短,对兼容性和功能特性有一定要求,Socket.IO 可以快速实现所需的实时通信功能,并且其维护成本相对可控。例如,一个简单的在线聊天应用,使用 Socket.IO 可以在短时间内完成开发,并且不需要花费太多精力去处理兼容性和连接管理等问题。
  2. 大型项目:在大型项目中,如果对性能和稳定性有极高的要求,并且开发团队有足够的技术实力,WebSocket 可能更适合。虽然其开发和维护的难度相对较高,但通过优化可以提供更稳定、高效的实时通信服务。同时,对于大型项目中复杂的功能需求,也可以在 WebSocket 的基础上进行定制开发。

在实际项目中,需要综合考虑以上因素,权衡利弊,选择最适合项目的实时通信方案。无论是 WebSocket 还是 Socket.IO,都为后端开发中的实时通信提供了强大的工具,开发者应根据具体情况灵活运用,以实现高效、稳定的实时应用。