Introduction

What Socket.IO is

Socket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server. It consists of:

  • a Node.js server: Source | API
  • a Javascript client library for the browser (which can be also run from Node.js): Source | API

Diagram for bidirectional communication

There are also several client implementation in other languages, which are maintained by the community:

Socket.IO是一个库,可用于在客户端和服务器之间进行实时,双向和基于事件的通信。它包括:

Node.js服务器: API
浏览器环境下运行的Javascript客户端(也可以从Node.js运行): API

还有一些其他语言的客户端实现,由社区维护:

  • Java:https://github.com/socketio/socket.io-client-java
  • C ++:https://github.com/socketio/socket.io-client-cpp
  • 斯威夫特:https://github.com/socketio/socket.io-client-swift
  • 飞镖:https://github.com/rikulo/socket.io-client-dart
  • Python:https://github.com/miguelgrinberg/python-socketio
  • .Net:https://github.com/Quobject/SocketIoClientDotNet

How does that work?

The client will try to establish a WebSocket connection if possible, and will fall back on HTTP long polling if not.

WebSocket is a communication protocol which provides a full-duplex and low-latency channel between the server and the browser. More information can be found here.

So, in the best-case scenario, provided that:

  • the browser supports WebSocket (97% of all browsers in 2020)
  • there is no element (proxy, firewall, …) preventing WebSocket connections between the client and the server

you can consider the Socket.IO client as a “slight” wrapper around the WebSocket API. Instead of writing:

如果可能,客户端将尝试建立WebSocket连接,如果没有,客户端将使用HTTP长轮询。

WebSocket是一种通信协议,可在服务器和浏览器之间提供全双工和低延迟通道。更多信息可以在这里找到。

因此,在最佳情况下,应提供:

  • 浏览器支持WebSocket(2020年所有浏览器中的97%)

  • 没有任何元素(代理,防火墙等)阻止客户端和服务器之间的WebSocket连接

您可以将Socket.IO客户端视为WebSocket API的“轻量”包装器。而不是写:

1
2
3
4
5
6
7
8
9
const socket = new WebSocket('ws://localhost:3000');

socket.onopen(() => {
socket.send('Hello!');
});

socket.onmessage(data => {
console.log(data);
});

You will have, on the client-side:

在客户端,您将拥有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const socket = io('ws://localhost:3000');

socket.on('connect', () => {
// either with send()
socket.send('Hello!');

// or with emit() and custom event names
socket.emit('salutations', 'Hello!', { 'mr': 'john' }, Uint8Array.from([1, 2, 3, 4]));
});

// handle the event sent with socket.send()
socket.on('message', data => {
console.log(data);
});

// handle the event sent with socket.emit()
socket.on('greetings', (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});

The API on the server-side is similar, you also get an socket object which extends the Node.js EventEmitter class:

服务器端的API与此类似,您还获得了一个扩展Node.js EventEmitter类的套接字对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const io = require('socket.io')(3000);

io.on('connect', socket => {
// either with send()
socket.send('Hello!');

// or with emit() and custom event names
socket.emit('greetings', 'Hey!', { 'ms': 'jane' }, Buffer.from([4, 3, 3, 1]));

// handle the event sent with socket.send()
socket.on('message', (data) => {
console.log(data);
});

// handle the event sent with socket.emit()
socket.on('salutations', (elem1, elem2, elem3) => {
console.log(elem1, elem2, elem3);
});
});

Socket.IO provides additional features over a plain WebSocket object, which are listed below.

But first, let’s detail what the Socket.IO library is not.

Socket.IO在纯WebSocket对象上提供了其他功能,下面列出了这些功能。

但首先,让我们详细说明Socket.IO库不是什么。

What Socket.IO is not

Socket.IO is NOT a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds additional metadata to each packet. That is why a WebSocket client will not be able to successfully connect to a Socket.IO server, and a Socket.IO client will not be able to connect to a plain WebSocket server either.

Socket.IO不是WebSocket实现。 尽管Socket.IO确实确实在可能的情况下使用WebSocket作为传输方式,但它向每个数据包添加了其他元数据。 这就是为什么WebSocket客户端将无法成功连接到Socket.IO服务器,而Socket.IO客户端也将无法连接到普通WebSocket服务器的原因。

1
2
// WARNING: the client will NOT be able to connect!
const socket = io('ws://echo.websocket.org');

If you are looking for a plain WebSocket server, please take a look at ws or uWebSockets.js.

There are also talks to include a WebSocket server in the Node.js core.

On the client-side, you might be interested by the robust-websocket package.

如果您正在寻找普通的WebSocket服务器,请查看ws或uWebSockets.js。

也有讨论将WebSocket服务器包含在Node.js核心中。

在客户端,您可能会对robust-websocket软件包感兴趣。

Minimal working example

If you are new to the Node.js ecosystem, please take a look at the Get Started guide, which is ideal for beginners.

Else, let’s start right away! The server library can be installed from NPM:

如果您不熟悉Node.js生态系统,请查看《入门指南》,它是初学者的理想选择。

否则,我们马上开始! 可以从NPM安装服务器库:

1
npm install socket.io

More information about the installation can be found in the Server installation page.

Then, let’s create an index.js file, with the following content:

有关安装的更多信息,请参见“服务器安装”页面。

然后,我们创建一个index.js文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const content = require('fs').readFileSync(__dirname + '/index.html', 'utf8');

const httpServer = require('http').createServer((req, res) => {
// serve the index.html file
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(content));
res.end(content);
});

const io = require('socket.io')(httpServer);

io.on('connect', socket => {
console.log('connect');
});

httpServer.listen(3000, () => {
console.log('go to http://localhost:3000');
});

Here, a classic Node.js HTTP server is started to serve the index.html file, and the Socket.IO server is attached to it. Please see the Server initialization page for the various ways to create a server.

Let’s create the index.html file next to it:

在这里,启动了经典的Node.js HTTP服务器来提供index.html文件,并将Socket.IO服务器连接到该服务器。 有关创建服务器的各种方法,请参见服务器初始化页面。

让我们在它旁边创建index.html文件:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Minimal working example</title>
</head>
<body>
<ul id="events"></ul>

<script src="/socket.io/socket.io.js"></script>
<script>
const $events = document.getElementById('events');

const newItem = (content) => {
const item = document.createElement('li');
item.innerText = content;
return item;
};

const socket = io();

socket.on('connect', () => {
$events.appendChild(newItem('connect'));
});
</script>
</body>
</html>

Finally, let’s start our server:

1
node index.js

![image-20200830095840462](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200830095840462.png)

The socket object on both sides extends the EventEmitter class, so:

  • sending an event is done with: socket.emit()
  • receiving an event is done by registering a listener: socket.on(, )

两侧的套接字对象都扩展了EventEmitter类,因此:

发送事件的方法是:socket.emit()
接收事件是通过注册侦听器完成的:socket.on(<事件名称>,<监听器>)

To send an event from the server to the client

Let’s update the index.js file (server-side):

让我们更新index.js文件(服务器端):

1
2
3
4
5
6
io.on('connect', socket => {
let counter = 0;
setInterval(() => {
socket.emit('hello', ++counter);
}, 1000);
});

And the index.html file (client-side):

1
2
3
4
5
6
7
8
9
const socket = io();

socket.on('connect', () => {
$events.appendChild(newItem('connect'));
});

socket.on('hello', (counter) => {
$events.appendChild(newItem(`hello - ${counter}`));
});

![image-20200830100611650](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200830100611650.png)

To send a message from the client to the server

Let’s update the index.js file (server-side):

1
2
3
4
5
io.on('connect', socket => {
socket.on('hey', data => {
console.log('hey', data);
});
});

And the index.html file (client-side):

1
2
3
4
5
6
7
8
9
10
11
const socket = io();

socket.on('connect', () => {
$events.appendChild(newItem('connect'));
});

let counter = 0;
setInterval(() => {
++counter;
socket.emit('hey', { counter }); // the object will be serialized for you
}, 1000);

![image-20200830101114485](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200830101114485.png)

Now, let’s detail the features provided by Socket.IO.

现在,让我们详细介绍Socket.IO提供的功能。

Features

Its main features are:

Reliability(可靠性)

Connections are established even in the presence of:

  • proxies and load balancers.
  • personal firewall and antivirus software.

For this purpose, it relies on Engine.IO, which first establishes a long-polling connection, then tries to upgrade to better transports that are “tested” on the side, like WebSocket. Please see the Goals section for more information.

即使存在以下情况也会建立连接:

  • 代理和负载均衡。
  • 个人防火墙和防病毒软件。

为此,它依赖Engine.IO,该引擎首先建立长轮询连接,然后尝试升级到在侧面进行“测试”的更好传输,例如WebSocket。 请参阅“目标”部分以获取更多信息。

Auto-reconnection support(自动重新连接支持)

Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options here.

除非另有指示,否则断开连接的客户端将尝试永久重新连接,直到服务器再次可用为止。 请在此处查看可用的重新连接选项。

Disconnection detection(断线检测)

A heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore.

That functionality is achieved with timers set on both the server and the client, with timeout values (the pingInterval and pingTimeout parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the sticky-session requirement when using multiples nodes.

心跳机制在Engine.IO级别上实现,使服务器和客户端都可以知道另一方何时不再响应。

通过在服务器和客户端上设置计时器,并在连接握手期间共享超时值(pingInterval和pingTimeout参数),可以实现该功能。 这些计时器要求将任何后续客户端调用都定向到同一服务器,因此使用多个节点时需要执行粘性会话。

Binary support(二进制支持)

Any serializable data structures can be emitted, including:

可以发出任何可序列化的数据结构,包括:

  • 浏览器中的ArrayBuffer和Blob

  • Node.js中的ArrayBuffer和Buffer

Multiplexing support(多路传输支持)

In order to create separation of concerns within your application (for example per module, or based on permissions), Socket.IO allows you to create several Namespaces, which will act as separate communication channels but will share the same underlying connection.

为了在应用程序内创建关注点分离(例如,每个模块或基于权限),Socket.IO允许您创建多个命名空间,这些命名空间将充当单独的通信通道,但将共享相同的基础连接。

Caught a mistake? Edit this page on GitHub