
Node.js ist ein beliebtes Tool zum Erstellen von Client-Server-Anwendungen. Bei korrekter Verwendung kann Node.js eine groĂe Anzahl von Netzwerkanforderungen mit nur einem Thread verarbeiten. Zweifellos ist Netzwerk-E / A eine der StĂ€rken dieser Plattform. Es scheint, dass Entwickler bei der Verwendung von Node.js zum Schreiben von serverseitigem Code fĂŒr eine Anwendung, die aktiv verschiedene Netzwerkprotokolle verwendet, wissen sollten, wie diese Protokolle funktionieren. Dies ist jedoch hĂ€ufig nicht der Fall. Dies liegt an einer weiteren StĂ€rke von Node.js, dem NPM-Paketmanager, in dem Sie fĂŒr fast jede Aufgabe eine vorgefertigte Lösung finden. Mit vorgefertigten Paketen vereinfachen wir unser Leben, verwenden den Code wieder (und das ist richtig), verstecken aber gleichzeitig vor dem Bildschirm der Bibliotheken die Essenz der Prozesse, die stattfinden.In diesem Artikel werden wir versuchen, das WebSocket-Protokoll zu verstehen, indem wir einen Teil der Spezifikation implementieren, ohne externe AbhĂ€ngigkeiten zu verwenden. Willkommen bei Katze.
, , WebSocket . , , http, , . http . Http request/reply â , . (, http 2.0). , . , , http, . RFC6202, , . WebSocket 2008 , . , WebSocket 2011 13 RFC6455. OSI http tcp. WebSocket http. WebSocket , , , . . , WebSocket 2009 , , Google Chrome 4 . , , . WebSocket :
- (handshake)
, , WebSocket, http . , GET . , , , , . http , . typescript ts-node.
import * as http from 'http';
import * as stream from 'stream';
export class SocketServer {
constructor(private port: number) {
http
.createServer()
.on('request', (request: http.IncomingMessage, socket: stream.Duplex) => {
console.log(request.headers);
})
.listen(this.port);
console.log('server start on port: ', this.port);
}
}
new SocketServer(8080);
8080. .
const socket = new WebSocket('ws://localhost:8080');
WebSocket, . readyState. :
- 0 â
- 1 â .
- 2 â
- 3 â
readyState, 0, 3. , . WebSocket API
:
{
host: 'localhost:8080',
connection: 'Upgrade',
pragma: 'no-cache',
'cache-control': 'no-cache',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
upgrade: 'websocket',
origin: 'chrome-search://local-ntp',
'sec-websocket-version': '13',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
'sec-websocket-key': 'h/k2aB+Gu3cbgq/GoSDOqQ==',
'sec-websocket-extensions': 'permessage-deflate; client_max_window_bits'
}
, http RFC2616. http GET, upgrade , . , 101, â . WebSocket , :
- sec-websocket-version . 13
- sec-websocket-extensions , . ,
- sec-websocket-protocol , . , , . â , .
- sec-websocket-key . . .
, , 101, sec-websocket-accept, , sec-websocket-key :
- sec-websocket-key 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
- sha-1
- base64
Upgrade: WebSocket Connection: Upgrade. , . sec-websocket-key node.js crypto. .
import * as crypto from 'crypto';
SocketServer
private HANDSHAKE_CONSTANT = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
constructor(private port: number) {
http
.createServer()
.on('upgrade', (request: http.IncomingMessage, socket: stream.Duplex) => {
const clientKey = request.headers['sec-websocket-key'];
const handshakeKey = crypto
.createHash('sha1')
.update(clientKey + this.HANDSHAKE_CONSTANT)
.digest('base64');
const responseHeaders = [
'HTTP/1.1 101',
'upgrade: websocket',
'connection: upgrade',
`sec-webSocket-accept: ${handshakeKey}`,
'\r\n',
];
socket.write(responseHeaders.join('\r\n'));
})
.listen(this.port);
console.log('server start on port: ', this.port);
}
http Node.js upgrade , . , , 1. . .
. â . . , , , .. . , , , ( ). , , , . .

, , . .
. 2
| 0 | 1 | 2 | 3 | 4 5 6 7 | 0 | 1 2 3 4 5 6 7 |
|---|---|---|---|---|---|---|
| FIN | RSV1 | RSV2 | RSV3 | OPCODE | MASK |
- FIN . 1, , 0, . .
- RSV1, RSV2, RSV3 .
- OPCODE 4 . : . . , UTF8, . 3 ping, pong, close. .
- 0 , â
- 1
- 2
- 8
- 9 Ping
- xA Pong
- MASK â . 0, , 1, . , , . , , .
- 7 , .
. 0 12
- <= 125, , , . ,
- = 126 2
- = 127 8
| 0, 2, 8 | 0, 4 |
|---|---|
, , . . â 4 , . , XOR. , , XOR.
, WebSocket .
, . WebSocket , . Ping. , . Ping, , . , Pong , Ping. ,
private MASK_LENGTH = 4; // .
private OPCODE = {
PING: 0x89, // Ping
SHORT_TEXT_MESSAGE: 0x81, // , 125
};
private DATA_LENGTH = {
MIDDLE: 128, // ,
SHORT: 125, //
LONG: 126, // , 2
VERY_LONG: 127, // , 8
};
Ping
private ping(message?: string) {
const payload = Buffer.from(message || '');
const meta = Buffer.alloc(2);
meta[0] = this.OPCODE.PING;
meta[1] = payload.length;
return Buffer.concat([meta, payload]);
}
, , . - Ping. , . , , . .
private CONTROL_MESSAGES = {
PING: Buffer.from([this.OPCODE.PING, 0x0]),
};
private connections: Set<stream.Duplex> = new Set();
, Ping 5 , .
setInterval(() => socket.write(this.CONTROL_MESSAGES.PING), heartbeatTimeout);
this.connections.add(socket);
. . , , . , , , , , .
private decryptMessage(message: Buffer) {
const length = message[1] ^ this.DATA_LENGTH.MIDDLE; // 1
if (length <= this.DATA_LENGTH.SHORT) {
return {
length,
mask: message.slice(2, 6), // 2
data: message.slice(6),
};
}
if (length === this.DATA_LENGTH.LONG) {
return {
length: message.slice(2, 4).readInt16BE(), // 3
mask: message.slice(4, 8),
data: message.slice(8),
};
}
if (length === this.DATA_LENGTH.VERY_LONG) {
return {
payloadLength: message.slice(2, 10).readBigInt64BE(), // 4
mask: message.slice(10, 14),
data: message.slice(14),
};
}
throw new Error('Wrong message format');
}
- . XOR , 128 , 10000000. , , , 1.
- 126,
- 127,
. ,
private unmasked(mask: Buffer, data: Buffer) {
return Buffer.from(data.map((byte, i) => byte ^ mask[i % this.MASK_LENGTH]));
}
XOR . 4 . .
public sendShortMessage(message: Buffer, socket: stream.Duplex) {
const meta = Buffer.alloc(2);
meta[0] = this.OPCODE.SHORT_TEXT_MESSAGE;
meta[1] = message.length;
socket.write(Buffer.concat([meta, message]));
}
. , .
socket.on('data', (data: Buffer) => {
if (data[0] === this.OPCODE.SHORT_TEXT_MESSAGE) { //
const meta = this.decryptMessage(data);
const message = this.unmasked(meta.mask, meta.data);
this.connections.forEach(socket => {
this.sendShortMessage(message, socket);
});
}
});
this.connections.forEach(socket => {
this.sendShortMessage(
Buffer.from(` . ${this.connections.size}`),
socket,
);
});
. .
const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = ({ data }) => console.log(data);
socket.send('Hello world!');

Wenn Ihre Anwendung WebSockets benötigt und Sie diese höchstwahrscheinlich benötigen, sollten Sie das Protokoll natĂŒrlich nicht selbst implementieren, es sei denn, dies ist unbedingt erforderlich. Sie können jederzeit eine geeignete Lösung aus der Vielzahl der Bibliotheken in npm auswĂ€hlen. Besser bereits geschriebenen und getesteten Code wiederverwenden. Wenn Sie jedoch verstehen, wie es "unter der Haube" funktioniert, können Sie immer viel mehr als nur den Code eines anderen verwenden. Das obige Beispiel ist auf Github verfĂŒgbar