UDP/数据报套接字#

稳定性:2 - 稳定

源代码: lib/dgram.js

node:dgram 模块提供了 UDP 数据报套接字的实现。

import dgram from 'node:dgram';

const server = dgram.createSocket('udp4');

server.on('error', (err) => {
  console.error(`server error:\n${err.stack}`);
  server.close();
});

server.on('message', (msg, rinfo) => {
  console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});

server.on('listening', () => {
  const address = server.address();
  console.log(`server listening ${address.address}:${address.port}`);
});

server.bind(41234);
// Prints: server listening 0.0.0.0:41234const dgram = require('node:dgram');
const server = dgram.createSocket('udp4');

server.on('error', (err) => {
  console.error(`server error:\n${err.stack}`);
  server.close();
});

server.on('message', (msg, rinfo) => {
  console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});

server.on('listening', () => {
  const address = server.address();
  console.log(`server listening ${address.address}:${address.port}`);
});

server.bind(41234);
// Prints: server listening 0.0.0.0:41234

类:dgram.Socket#

封装了数据报功能。

dgram.Socket 的新实例使用 dgram.createSocket() 创建。 new 关键字不能用于创建 dgram.Socket 实例。

事件:'close'#

在使用 close() 关闭套接字后,会触发 'close' 事件。 触发后,不会在此套接字上触发新的 'message' 事件。

事件:'connect'#

在套接字由于成功的 connect() 调用而与远程地址关联后,会触发 'connect' 事件。

事件:'error'#

每当发生任何错误时,都会触发 'error' 事件。 事件处理函数会传递一个 Error 对象。

事件:'listening'#

一旦 dgram.Socket 可寻址并可以接收数据,就会触发 'listening' 事件。 这种情况可能是显式地通过 socket.bind() 发生,也可能是隐式地在首次使用 socket.send() 发送数据时发生。 在 dgram.Socket 处于监听状态之前,底层系统资源不存在,并且诸如 socket.address()socket.setTTL() 之类的调用将失败。

事件:'message'#

当新的数据报在套接字上可用时,会触发 'message' 事件。 事件处理函数会传递两个参数:msgrinfo

如果传入数据包的源地址是 IPv6 链路本地地址,则接口名称会添加到 address。 例如,在 en0 接口上接收到的数据包可能将地址字段设置为 'fe80::2618:1234:ab11:3b9c%en0',其中 '%en0' 是接口名称作为区域 ID 后缀。

socket.addMembership(multicastAddress[, multicastInterface])#

告知内核使用 IP_ADD_MEMBERSHIP 套接字选项加入给定 multicastAddressmulticastInterface 上的多播组。 如果未指定 multicastInterface 参数,则操作系统将选择一个接口并将其添加到成员资格。 要将成员资格添加到每个可用接口,请多次调用 addMembership,每个接口一次。

在未绑定的套接字上调用时,此方法将隐式绑定到随机端口,并在所有接口上进行侦听。

在多个 cluster 工作进程之间共享 UDP 套接字时,socket.addMembership() 函数必须只调用一次,否则会发生 EADDRINUSE 错误

import cluster from 'node:cluster';
import dgram from 'node:dgram';

if (cluster.isPrimary) {
  cluster.fork(); // Works ok.
  cluster.fork(); // Fails with EADDRINUSE.
} else {
  const s = dgram.createSocket('udp4');
  s.bind(1234, () => {
    s.addMembership('224.0.0.114');
  });
}const cluster = require('node:cluster');
const dgram = require('node:dgram');

if (cluster.isPrimary) {
  cluster.fork(); // Works ok.
  cluster.fork(); // Fails with EADDRINUSE.
} else {
  const s = dgram.createSocket('udp4');
  s.bind(1234, () => {
    s.addMembership('224.0.0.114');
  });
}

socket.addSourceSpecificMembership(sourceAddress, groupAddress[, multicastInterface])#

告知内核使用带有 IP_ADD_SOURCE_MEMBERSHIP 套接字选项的 multicastInterface 加入给定 sourceAddressgroupAddress 上的特定于源的多播通道。 如果未指定 multicastInterface 参数,则操作系统将选择一个接口并将其添加到成员资格。 要将成员资格添加到每个可用接口,请多次调用 socket.addSourceSpecificMembership(),每个接口一次。

在未绑定的套接字上调用时,此方法将隐式绑定到随机端口,并在所有接口上进行侦听。

socket.address()#

返回一个包含套接字的地址信息的对象。 对于 UDP 套接字,此对象将包含 addressfamilyport 属性。

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

socket.bind([port][, address][, callback])#

对于 UDP 套接字,导致 dgram.Socket 在指定的 port 和可选的 address 上侦听数据报消息。 如果未指定 portport0,则操作系统将尝试绑定到随机端口。 如果未指定 address,则操作系统将尝试在所有地址上进行侦听。 绑定完成后,会触发 'listening' 事件,并调用可选的 callback 函数。

指定 'listening' 事件侦听器并将 callback 传递给 socket.bind() 方法虽然没有害处,但用处不大。

绑定的数据报套接字使 Node.js 进程保持运行以接收数据报消息。

如果绑定失败,则会生成 'error' 事件。 在极少数情况下(例如,尝试使用已关闭的套接字进行绑定),可能会抛出 Error

在端口 41234 上侦听的 UDP 服务器的示例

import dgram from 'node:dgram';

const server = dgram.createSocket('udp4');

server.on('error', (err) => {
  console.error(`server error:\n${err.stack}`);
  server.close();
});

server.on('message', (msg, rinfo) => {
  console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});

server.on('listening', () => {
  const address = server.address();
  console.log(`server listening ${address.address}:${address.port}`);
});

server.bind(41234);
// Prints: server listening 0.0.0.0:41234const dgram = require('node:dgram');
const server = dgram.createSocket('udp4');

server.on('error', (err) => {
  console.error(`server error:\n${err.stack}`);
  server.close();
});

server.on('message', (msg, rinfo) => {
  console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});

server.on('listening', () => {
  const address = server.address();
  console.log(`server listening ${address.address}:${address.port}`);
});

server.bind(41234);
// Prints: server listening 0.0.0.0:41234

socket.bind(options[, callback])#

对于 UDP 套接字,此方法使 dgram.Socket 监听指定 port 和可选的 address 上的数据报消息,这些属性作为 options 对象的一部分传递,并作为第一个参数传入。 如果未指定 portport0,操作系统将尝试绑定到一个随机端口。 如果未指定 address,操作系统将尝试监听所有地址。 绑定完成后,会触发 'listening' 事件,并调用可选的 callback 函数。

options 对象可能包含 fd 属性。 当设置大于 0fd 时,它将包装具有给定文件描述符的现有套接字。 在这种情况下,portaddress 属性将被忽略。

指定 'listening' 事件侦听器并将 callback 传递给 socket.bind() 方法虽然没有害处,但用处不大。

options 对象可能包含一个额外的 exclusive 属性,该属性在使用带有 cluster 模块的 dgram.Socket 对象时使用。 当 exclusive 设置为 false(默认值)时,集群工作进程将使用相同的底层套接字句柄,从而可以共享连接处理任务。 但是,当 exclusivetrue 时,句柄不共享,并且尝试进行端口共享会导致错误。 创建 reusePort 选项设置为 truedgram.Socket 会导致在调用 socket.bind()exclusive 始终为 true

绑定的数据报套接字使 Node.js 进程保持运行以接收数据报消息。

如果绑定失败,则会生成 'error' 事件。 在极少数情况下(例如,尝试使用已关闭的套接字进行绑定),可能会抛出 Error

下面显示了一个监听专用端口的套接字示例。

socket.bind({
  address: 'localhost',
  port: 8000,
  exclusive: true,
}); 

socket.close([callback])#

关闭底层套接字并停止监听其上的数据。 如果提供了回调,则将其添加为 'close' 事件的监听器。

socket[Symbol.asyncDispose]()#

稳定性: 1 - 实验性的

调用 socket.close() 并返回一个 promise,该 promise 在套接字关闭时完成。

socket.connect(port[, address][, callback])#

dgram.Socket 与远程地址和端口相关联。 此句柄发送的每个消息都将自动发送到该目标。 此外,套接字将仅接收来自该远程对等方的消息。 尝试在已连接的套接字上调用 connect() 将导致 ERR_SOCKET_DGRAM_IS_CONNECTED 异常。 如果未提供 address,则默认情况下将使用 '127.0.0.1'(对于 udp4 套接字)或 '::1'(对于 udp6 套接字)。 连接完成后,会触发 'connect' 事件,并调用可选的 callback 函数。 如果失败,则调用 callback,否则,会触发 'error' 事件。

socket.disconnect()#

一个同步函数,用于将已连接的 dgram.Socket 与其远程地址分离。 尝试在未绑定或已断开连接的套接字上调用 disconnect() 将导致 ERR_SOCKET_DGRAM_NOT_CONNECTED 异常。

socket.dropMembership(multicastAddress[, multicastInterface])#

指示内核使用 IP_DROP_MEMBERSHIP 套接字选项离开 multicastAddress 上的多播组。 当套接字关闭或进程终止时,内核会自动调用此方法,因此大多数应用程序永远没有理由调用此方法。

如果未指定 multicastInterface,操作系统将尝试在所有有效接口上删除成员资格。

socket.dropSourceSpecificMembership(sourceAddress, groupAddress[, multicastInterface])#

指示内核使用 IP_DROP_SOURCE_MEMBERSHIP 套接字选项在给定的 sourceAddressgroupAddress 处离开特定于源的多播通道。 当套接字关闭或进程终止时,内核会自动调用此方法,因此大多数应用程序永远没有理由调用此方法。

如果未指定 multicastInterface,操作系统将尝试在所有有效接口上删除成员资格。

socket.getRecvBufferSize()#

  • 返回: <number> SO_RCVBUF 套接字接收缓冲区大小(以字节为单位)。

如果在未绑定的套接字上调用此方法,则会引发 ERR_SOCKET_BUFFER_SIZE

socket.getSendBufferSize()#

  • 返回: <number> SO_SNDBUF 套接字发送缓冲区大小(以字节为单位)。

如果在未绑定的套接字上调用此方法,则会引发 ERR_SOCKET_BUFFER_SIZE

socket.getSendQueueSize()#

  • 返回: <number> 排队等待发送的字节数。

socket.getSendQueueCount()#

  • 返回: <number> 当前队列中等待处理的发送请求数。

socket.ref()#

默认情况下,绑定套接字会导致它阻止 Node.js 进程退出,只要套接字处于打开状态。 socket.unref() 方法可用于将套接字从引用计数中排除,该引用计数使 Node.js 进程保持活动状态。 socket.ref() 方法将套接字添加回引用计数并恢复默认行为。

多次调用 socket.ref() 不会产生额外的效果。

socket.ref() 方法返回对套接字的引用,因此可以链接调用。

socket.remoteAddress()#

返回一个对象,其中包含远程端点的 addressfamilyport。 如果套接字未连接,则此方法会引发 ERR_SOCKET_DGRAM_NOT_CONNECTED 异常。

socket.send(msg[, offset, length][, port][, address][, callback])#

在套接字上广播数据报。 对于无连接套接字,必须指定目标 portaddress。 另一方面,连接的套接字将使用其关联的远程端点,因此不得设置 portaddress 参数。

msg 参数包含要发送的消息。 根据其类型,可以应用不同的行为。 如果 msgBuffer、任何 TypedArrayDataView,则 offsetlength 分别指定消息开始的 Buffer 中的偏移量以及消息中的字节数。 如果 msgString,则会自动将其转换为具有 'utf8' 编码的 Buffer。 对于包含多字节字符的消息,将根据 字节长度(而不是字符位置)计算 offsetlength。 如果 msg 是一个数组,则不得指定 offsetlength

address 参数是一个字符串。 如果 address 的值是主机名,则将使用 DNS 来解析主机的地址。 如果未提供 address 或以其他方式为 nullish,则默认情况下将使用 '127.0.0.1'(对于 udp4 套接字)或 '::1'(对于 udp6 套接字)。

如果套接字之前未通过调用 bind 绑定,则套接字将分配一个随机端口号,并绑定到“所有接口”地址(对于 udp4 套接字为 '0.0.0.0',对于 udp6 套接字为 '::0')。

可以指定一个可选的 callback 函数,作为报告 DNS 错误或确定何时可以安全地重用 buf 对象的方法。 DNS 查找会将发送时间延迟至少一个 Node.js 事件循环周期。

唯一能确定数据报已发送的方法是使用 callback。如果发生错误并且提供了 callback,错误将作为第一个参数传递给 callback。如果未提供 callback,则错误将作为 socket 对象上的 'error' 事件发出。

Offset 和 length 是可选的,但如果使用了其中任何一个,必须 同时设置两者。它们仅在第一个参数是 BufferTypedArrayDataView 时才受支持。

如果在未绑定的套接字上调用此方法,则会抛出 ERR_SOCKET_BAD_PORT 错误。

以下是将 UDP 数据包发送到 localhost 上的端口的示例:

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';

const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.send(message, 41234, 'localhost', (err) => {
  client.close();
});const dgram = require('node:dgram');
const { Buffer } = require('node:buffer');

const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.send(message, 41234, 'localhost', (err) => {
  client.close();
});

以下是将由多个缓冲区组成的 UDP 数据包发送到 127.0.0.1 上的端口的示例:

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';

const buf1 = Buffer.from('Some ');
const buf2 = Buffer.from('bytes');
const client = dgram.createSocket('udp4');
client.send([buf1, buf2], 41234, (err) => {
  client.close();
});const dgram = require('node:dgram');
const { Buffer } = require('node:buffer');

const buf1 = Buffer.from('Some ');
const buf2 = Buffer.from('bytes');
const client = dgram.createSocket('udp4');
client.send([buf1, buf2], 41234, (err) => {
  client.close();
});

根据应用程序和操作系统,发送多个缓冲区可能会更快或更慢。运行基准测试以确定在具体情况下的最佳策略。但总的来说,发送多个缓冲区通常更快。

以下是使用连接到 localhost 上的端口的套接字发送 UDP 数据包的示例

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';

const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.connect(41234, 'localhost', (err) => {
  client.send(message, (err) => {
    client.close();
  });
});const dgram = require('node:dgram');
const { Buffer } = require('node:buffer');

const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.connect(41234, 'localhost', (err) => {
  client.send(message, (err) => {
    client.close();
  });
});
关于 UDP 数据报大小的说明#

IPv4/v6 数据报的最大大小取决于 MTU(最大传输单元)和 Payload Length(有效负载长度)字段大小。

  • Payload Length 字段宽度为 16 位,这意味着正常有效负载不能超过 64K 字节,包括互联网标头和数据(65,507 字节 = 65,535 - 8 字节 UDP 标头 - 20 字节 IP 标头);这通常适用于环回接口,但对于大多数主机和网络来说,如此长的数据报消息是不切实际的。

  • MTU 是给定的链路层技术可以支持的数据报消息的最大大小。对于任何链路,IPv4 强制要求最小 MTU 为 68 字节,而 IPv4 建议的 MTU 为 576(通常建议作为拨号类型应用程序的 MTU),无论它们是完整到达还是分片到达。

    对于 IPv6,最小 MTU 为 1280 字节。但是,强制的最小片段重组缓冲区大小为 1500 字节。68 字节的值非常小,因为大多数当前的链路层技术,如以太网,都具有最小 MTU 为 1500。

无法提前知道数据包可能经过的每个链路的 MTU。发送大于接收方 MTU 的数据报将不起作用,因为数据包将被静默丢弃,而不会通知源数据未到达其预期的接收者。

socket.setBroadcast(flag)#

设置或清除 SO_BROADCAST 套接字选项。当设置为 true 时,UDP 数据包可以发送到本地接口的广播地址。

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

socket.setMulticastInterface(multicastInterface)#

本节中所有对范围的引用都指的是 IPv6 区域索引,它由 RFC 4007 定义。以字符串形式,带有范围索引的 IP 写作 'IP%scope',其中 scope 是接口名称或接口号。

将套接字的默认传出多播接口设置为选定的接口或返回到系统接口选择。multicastInterface 必须是套接字族中的 IP 的有效字符串表示形式。

对于 IPv4 套接字,这应该是为所需物理接口配置的 IP。发送到套接字上的多播的所有数据包都将通过最近一次成功使用此调用确定的接口发送。

对于 IPv6 套接字,multicastInterface 应包含一个范围以指示接口,如以下示例所示。在 IPv6 中,单个 send 调用也可以在地址中使用显式范围,因此只有发送到未指定显式范围的多播地址的数据包才会受到最近一次成功使用此调用的影响。

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

示例:IPv6 传出多播接口#

在大多数系统中,范围格式使用接口名称

const socket = dgram.createSocket('udp6');

socket.bind(1234, () => {
  socket.setMulticastInterface('::%eth1');
}); 

在 Windows 中,范围格式使用接口号

const socket = dgram.createSocket('udp6');

socket.bind(1234, () => {
  socket.setMulticastInterface('::%2');
}); 
示例:IPv4 传出多播接口#

所有系统都在所需的物理接口上使用主机的 IP

const socket = dgram.createSocket('udp4');

socket.bind(1234, () => {
  socket.setMulticastInterface('10.0.0.2');
}); 
调用结果#

在未准备好发送或不再打开的套接字上调用可能会抛出 *Not running* Error

如果无法将 multicastInterface 解析为 IP,则会抛出 *EINVAL* System Error

在 IPv4 上,如果 multicastInterface 是有效地址,但不匹配任何接口,或者地址与族不匹配,则会抛出 System Error,例如 EADDRNOTAVAILEPROTONOSUP

在 IPv6 上,指定或省略范围的大多数错误将导致套接字继续使用(或返回到)系统的默认接口选择。

套接字的地址族的 ANY 地址(IPv4 '0.0.0.0' 或 IPv6 '::')可用于将套接字默认传出接口的控制权返回给系统,以供将来的多播数据包使用。

socket.setMulticastLoopback(flag)#

设置或清除 IP_MULTICAST_LOOP 套接字选项。当设置为 true 时,多播数据包也将在本地接口上接收。

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

socket.setMulticastTTL(ttl)#

设置 IP_MULTICAST_TTL 套接字选项。虽然 TTL 通常代表“生存时间”,但在此上下文中,它指定数据包允许通过的 IP 跳数,特别是对于多播流量。转发数据包的每个路由器或网关都会递减 TTL。如果 TTL 被路由器递减到 0,则不会转发它。

ttl 参数的取值范围为 0 到 255。大多数系统上的默认值为 1

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

socket.setRecvBufferSize(size)#

设置 SO_RCVBUF 套接字选项。设置套接字接收缓冲区的最大字节数。

如果在未绑定的套接字上调用此方法,则会引发 ERR_SOCKET_BUFFER_SIZE

socket.setSendBufferSize(size)#

设置 SO_SNDBUF 套接字选项。设置套接字发送缓冲区的最大字节数。

如果在未绑定的套接字上调用此方法,则会引发 ERR_SOCKET_BUFFER_SIZE

socket.setTTL(ttl)#

设置 IP_TTL 套接字选项。虽然 TTL 通常代表“生存时间”,但在此上下文中,它指定数据包允许通过的 IP 跳数。转发数据包的每个路由器或网关都会递减 TTL。如果 TTL 被路由器递减到 0,则不会转发它。更改 TTL 值通常用于网络探测或多播。

ttl 参数的取值范围为 1 到 255。大多数系统上的默认值为 64。

如果在未绑定的套接字上调用此方法,则会抛出 EBADF

socket.unref()#

默认情况下,绑定套接字将导致它阻止 Node.js 进程退出,只要套接字处于打开状态。socket.unref() 方法可用于从保持 Node.js 进程处于活动状态的引用计数中排除套接字,从而允许进程退出,即使套接字仍在侦听。

多次调用 socket.unref() 不会产生任何其他影响。

socket.unref() 方法返回对套接字的引用,因此可以链接调用。

node:dgram 模块函数#

dgram.createSocket(options[, callback])#

  • options <Object> 可用选项包括
    • type <string> 套接字的族。必须是 'udp4''udp6'。必需。
    • reuseAddr <boolean>true 时,即使另一个进程已经在其上绑定了套接字,socket.bind() 也会重用该地址,但只有一个套接字可以接收数据。默认: false
    • reusePort <boolean>true 时,即使另一个进程已经在其上绑定了套接字,socket.bind() 也会重用该端口。传入的数据报将分发到侦听套接字。该选项仅在某些平台上可用,例如 Linux 3.9+、DragonFlyBSD 3.6+、FreeBSD 12.0+、Solaris 11.4 和 AIX 7.2.5+。在不支持的平台上,绑定套接字时,此选项会引发错误。默认: false
    • ipv6Only <boolean>ipv6Only 设置为 true 将禁用双栈支持,即,绑定到地址 :: 不会使 0.0.0.0 被绑定。默认: false
    • recvBufferSize <number> 设置 SO_RCVBUF 套接字值。
    • sendBufferSize <number> 设置 SO_SNDBUF 套接字值。
    • lookup <Function> 自定义查找函数。默认值: dns.lookup()
    • signal <AbortSignal> 一个 AbortSignal,可用于关闭套接字。
    • receiveBlockList <net.BlockList> receiveBlockList 可用于丢弃发往特定 IP 地址、IP 范围或 IP 子网的入站数据报。如果服务器位于反向代理、NAT 等之后,则此方法不起作用,因为针对阻止列表检查的地址是代理的地址,或由 NAT 指定的地址。
    • sendBlockList <net.BlockList> sendBlockList 可用于禁用对特定 IP 地址、IP 范围或 IP 子网的出站访问。
  • callback <Function> 作为 'message' 事件的监听器附加。 可选。
  • 返回: <dgram.Socket>

创建一个 dgram.Socket 对象。创建套接字后,调用 socket.bind() 将指示套接字开始侦听数据报消息。 当 addressport 没有传递给 socket.bind() 时,该方法会将套接字绑定到随机端口上的 “所有接口” 地址(它对 udp4udp6 套接字都做了正确的事情)。 可以使用 socket.address().addresssocket.address().port 检索绑定的地址和端口。

如果启用 signal 选项,则在相应的 AbortController 上调用 .abort() 类似于在套接字上调用 .close()

const controller = new AbortController();
const { signal } = controller;
const server = dgram.createSocket({ type: 'udp4', signal });
server.on('message', (msg, rinfo) => {
  console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
// Later, when you want to close the server.
controller.abort(); 

dgram.createSocket(type[, callback])#

创建一个指定 typedgram.Socket 对象。

创建套接字后,调用 socket.bind() 将指示套接字开始侦听数据报消息。 当 addressport 没有传递给 socket.bind() 时,该方法会将套接字绑定到随机端口上的 “所有接口” 地址(它对 udp4udp6 套接字都做了正确的事情)。 可以使用 socket.address().addresssocket.address().port 检索绑定的地址和端口。