ws: a Node.js WebSocket library

ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and server implementation.ws是一个简单易用、快速且经过全面测试的WebSocket客户端和服务器实现。

Passes the quite extensive Autobahn test suite: server, client.通过了相当广泛的高速公路测试套件:serverclient

Note: This module does not work in the browser. :此模块在浏览器中不起作用。The client in the docs is a reference to a back end with the role of a client in the WebSocket communication. 文档中的客户端是对后端的引用,该后端在WebSocket通信中扮演客户端的角色。Browser clients must use the native WebSocket object. 浏览器客户端必须使用本机WebSocket对象。To make the same code work seamlessly on Node.js and the browser, you can use one of the many wrappers available on npm, like isomorphic-ws.为了使相同的代码在Node.js和浏览器上无缝工作,您可以使用npm上可用的许多包装器中的一个,比如isomorphic-ws

Table of Contents目录

Protocol support协议支持

Installing安装

npm install ws

Opt-in for performance选择加入以获得性能

There are 2 optional modules that can be installed along side with the ws module. 有2个可选模块可以与ws模块一起安装。These modules are binary addons that improve the performance of certain operations. 这些模块是二进制插件,可以提高某些操作的性能。Prebuilt binaries are available for the most popular platforms so you don't necessarily need to have a C++ compiler installed on your machine.预构建的二进制文件可用于最流行的平台,因此您不一定需要在机器上安装C++编译器。

To not even try to require and use these modules, use the WS_NO_BUFFER_UTIL and WS_NO_UTF_8_VALIDATE environment variables. 为了甚至不尝试要求和使用这些模块,请使用WS_NO_BUFFER_UTILWS_NO_UTF_8_VALIDATE环境变量。These might be useful to enhance security in systems where a user can put a package in the package search path of an application of another user, due to how the Node.js resolver algorithm works.由于Node.js解析器算法的工作原理,在用户可以将包放入另一个用户的应用程序的包搜索路径的系统中,这些可能有助于增强安全性。

The utf-8-validate module is not needed and is not required, even if it is already installed, regardless of the value of the WS_NO_UTF_8_VALIDATE environment variable, if buffer.isUtf8() is available.如果buffer.isUtf8()可用,则无论WS_NO_UTF_8_VALIDATE环境变量的值如何,utf-8-validate模块都不需要也不需要,即使它已经安装。

API docs

See /doc/ws.md for Node.js-like documentation of ws classes and utility functions.有关类似Node.js的ws类和实用程序函数的文档,请参阅/doc/ws.md

WebSocket compression压缩

ws supports the permessage-deflate extension which enables the client and server to negotiate a compression algorithm and its parameters, and then selectively apply it to the data payloads of each WebSocket message.ws支持permessage-delate扩展,该扩展使客户端和服务器能够协商压缩算法及其参数,然后选择性地将其应用于每个WebSocket消息的数据有效负载。

The extension is disabled by default on the server and enabled by default on the client. 默认情况下,该扩展在服务器上处于禁用状态,在客户端上处于启用状态。It adds a significant overhead in terms of performance and memory consumption so we suggest to enable it only if it is really needed.它在性能和内存消耗方面增加了大量开销,因此我们建议只有在真正需要时才启用它。

Note that Node.js has a variety of issues with high-performance compression, where increased concurrency, especially on Linux, can lead to catastrophic memory fragmentation and slow performance. 请注意,Node.js在高性能压缩方面存在各种问题,其中并发性的增加,尤其是在Linux上,可能会导致灾难性的内存碎片和性能下降。If you intend to use permessage-deflate in production, it is worthwhile to set up a test representative of your workload and ensure Node.js/zlib will handle it with acceptable performance and memory usage.如果您打算在生产中使用permesage-deflate,那么值得为您的工作负载设置一个测试代表,并确保Node.js/zlib能够以可接受的性能和内存使用率来处理它。

Tuning of permessage-deflate can be done via the options defined below. 可以通过以下定义的选项来调整渗透率放气。You can also use zlibDeflateOptions and zlibInflateOptions, which is passed directly into the creation of raw deflate/inflate streams.您还可以使用zlibDeflateOptionszlibInflateOptions,它们直接传递到原始放气/充气流的创建中。

See the docs for more options.有关更多选项,请参阅文档

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({
  port: 8080,
  perMessageDeflate: {
    zlibDeflateOptions: {
      // See zlib defaults.请参阅zlib默认值。
      chunkSize: 1024,
      memLevel: 7,
      level: 3
    },
    zlibInflateOptions: {
      chunkSize: 10 * 1024
    },
    // Other options settable:其他可设置选项:
    clientNoContextTakeover: true, // Defaults to negotiated value.默认为协商值。
    serverNoContextTakeover: true, // Defaults to negotiated value.默认为协商值。
    serverMaxWindowBits: 10, // Defaults to negotiated value.默认为协商值。
    // Below options specified as default values.以下选项被指定为默认值。
    concurrencyLimit: 10, // Limits zlib concurrency for perf.限制性能的zlib并发性。
    threshold: 1024 // Size (in bytes) below which messages低于消息大小(以字节为单位)
    // should not be compressed if context takeover is disabled.如果禁用了上下文接管,则不应进行压缩。
  }
});

The client will only use the extension if it is supported and enabled on the server. 只有在服务器上支持并启用该扩展时,客户端才会使用该扩展。To always disable the extension on the client set the perMessageDeflate option to false.要始终禁用客户端上的扩展,请将perMessageDeflate选项设置为false

import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path', {
  perMessageDeflate: false
});

Usage examples用法示例

Sending and receiving text data发送和接收文本数据

import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path');

ws.on('error', console.error);

ws.on('open', function open() {
  ws.send('something');
});

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

Sending binary data发送二进制数据

import WebSocket from 'ws';

const ws = new WebSocket('ws://www.host.com/path');

ws.on('error', console.error);

ws.on('open', function open() {
  const array = new Float32Array(5);

  for (var i = 0; i < array.length; ++i) {
    array[i] = i / 2;
  }

  ws.send(array);
});

Simple server简单服务器

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

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

  ws.send('something');
});

External HTTP/S server外部HTTP/S服务器

import { createServer } from 'https';
import { readFileSync } from 'fs';
import { WebSocketServer } from 'ws';

const server = createServer({
  cert: readFileSync('/path/to/cert.pem'),
  key: readFileSync('/path/to/key.pem')
});
const wss = new WebSocketServer({ server });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

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

  ws.send('something');
});

server.listen(8080);

Multiple servers sharing a single HTTP/S server多个服务器共享一个HTTP/S服务器

import { createServer } from 'http';
import { parse } from 'url';
import { WebSocketServer } from 'ws';

const server = createServer();
const wss1 = new WebSocketServer({ noServer: true });
const wss2 = new WebSocketServer({ noServer: true });

wss1.on('connection', function connection(ws) {
  ws.on('error', console.error);

  // ...
});

wss2.on('connection', function connection(ws) {
  ws.on('error', console.error);

  // ...
});

server.on('upgrade', function upgrade(request, socket, head) {
  const { pathname } = parse(request.url);

  if (pathname === '/foo') {
    wss1.handleUpgrade(request, socket, head, function done(ws) {
      wss1.emit('connection', ws, request);
    });
  } else if (pathname === '/bar') {
    wss2.handleUpgrade(request, socket, head, function done(ws) {
      wss2.emit('connection', ws, request);
    });
  } else {
    socket.destroy();
  }
});

server.listen(8080);

Client authentication客户端身份验证

import { createServer } from 'http';
import { WebSocketServer } from 'ws';

function onSocketError(err) {
  console.error(err);
}

const server = createServer();
const wss = new WebSocketServer({ noServer: true });

wss.on('connection', function connection(ws, request, client) {
  ws.on('error', console.error);

  ws.on('message', function message(data) {
    console.log(`Received message ${data} from user ${client}`);
  });
});

server.on('upgrade', function upgrade(request, socket, head) {
  socket.on('error', onSocketError);

  // This function is not defined on purpose. 此功能不是故意定义的。Implement it with your own logic.用你自己的逻辑来实现它。
  authenticate(request, function next(err, client) {
    if (err || !client) {
      socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
      socket.destroy();
      return;
    }

    socket.removeListener('error', onSocketError);

    wss.handleUpgrade(request, socket, head, function done(ws) {
      wss.emit('connection', ws, request, client);
    });
  });
});

server.listen(8080);

Also see the provided example using express-session.另请参阅所提供的使用express-session示例

Server broadcast服务器广播

A client WebSocket broadcasting to all connected WebSocket clients, including itself.客户端WebSocket广播到所有连接的WebSocket客户端,包括其自身。

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

  ws.on('message', function message(data, isBinary) {
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: isBinary });
      }
    });
  });
});

A client WebSocket broadcasting to every other connected WebSocket clients, excluding itself.客户端WebSocket广播到除自身之外的所有其他连接的WebSocket客户端。

import WebSocket, { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

  ws.on('message', function message(data, isBinary) {
    wss.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(data, { binary: isBinary });
      }
    });
  });
});

Round-trip time往返时间

import WebSocket from 'ws';

const ws = new WebSocket('wss://websocket-echo.com/');

ws.on('error', console.error);

ws.on('open', function open() {
  console.log('connected');
  ws.send(Date.now());
});

ws.on('close', function close() {
  console.log('disconnected');
});

ws.on('message', function message(data) {
  console.log(`Round-trip time: ${Date.now() - data} ms`);

  setTimeout(function timeout() {
    ws.send(Date.now());
  }, 500);
});

Use the Node.js streams API使用Node.js流API

import WebSocket, { createWebSocketStream } from 'ws';

const ws = new WebSocket('wss://websocket-echo.com/');

const duplex = createWebSocketStream(ws, { encoding: 'utf8' });

duplex.on('error', console.error);

duplex.pipe(process.stdout);
process.stdin.pipe(duplex);

Other examples其他示例

For a full example with a browser client communicating with a ws server, see the examples folder.有关浏览器客户端与ws服务器通信的完整示例,请参阅examples文件夹。

Otherwise, see the test cases.否则,请参阅测试用例。

FAQ

How to get the IP address of the client?如何获取客户端的IP地址?

The remote IP address can be obtained from the raw socket.远程IP地址可以从原始套接字中获取。

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws, req) {
  const ip = req.socket.remoteAddress;

  ws.on('error', console.error);
});

When the server runs behind a proxy like NGINX, the de-facto standard is to use the X-Forwarded-For header.当服务器运行在像NGINX这样的代理之后时,事实上的标准是使用X-Forwarded-For标头。

wss.on('connection', function connection(ws, req) {
  const ip = req.headers['x-forwarded-for'].split(',')[0].trim();

  ws.on('error', console.error);
});

How to detect and close broken connections?如何检测和关闭断开的连接?

Sometimes the link between the server and the client can be interrupted in a way that keeps both the server and the client unaware of the broken state of the connection (e.g. when pulling the cord).有时,服务器和客户端之间的链接可能会被中断,从而使服务器和客户端都不知道连接的断开状态(例如,在拔线时)。

In these cases ping messages can be used as a means to verify that the remote endpoint is still responsive.在这些情况下,可以使用ping消息来验证远程端点是否仍有响应。

import { WebSocketServer } from 'ws';

function heartbeat() {
  this.isAlive = true;
}

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.isAlive = true;
  ws.on('error', console.error);
  ws.on('pong', heartbeat);
});

const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate();

    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('close', function close() {
  clearInterval(interval);
});

Pong messages are automatically sent in response to ping messages as required by the spec.Pong消息是根据规范的要求自动发送的,以响应ping消息。

Just like the server example above your clients might as well lose connection without knowing it. 就像上面的服务器示例一样,您的客户端可能会在不知情的情况下失去连接。You might want to add a ping listener on your clients to prevent that. 您可能需要在客户端上添加一个ping侦听器来防止这种情况发生。A simple implementation would be:一个简单的实现方式是:

import WebSocket from 'ws';

function heartbeat() {
  clearTimeout(this.pingTimeout);

  // Use `WebSocket#terminate()`, which immediately destroys the connection,使用`WebSocket#terminate()`,它会立即破坏连接,
  // instead of `WebSocket#close()`, which waits for the close timer.而不是等待关闭计时器的`WebSocket#close()`。
  // Delay should be equal to the interval at which your server sends out pings plus a conservative assumption of the latency.延迟应该等于服务器发送ping的间隔加上保守的延迟假设。
  this.pingTimeout = setTimeout(() => {
    this.terminate();
  }, 30000 + 1000);
}

const client = new WebSocket('wss://websocket-echo.com/');

client.on('error', console.error);
client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
  clearTimeout(this.pingTimeout);
});

How to connect via a proxy?如何通过代理进行连接?

Use a custom http.Agent implementation like https-proxy-agent or socks-proxy-agent.使用自定义http.Agent实现,如https-proxy-agentsocks-proxy-agent

Changelog变更日志

We're using the GitHub releases for changelog entries.我们正在使用GitHub版本的变更日志条目。

License许可证

MIT