Node UDP服务器:使用Node.js建立高效的UDP服务器 (node udp服务器)

随着计算机网络技术的不断发展,UDP(用户数据报协议)已经成为了重要的网络传输协议之一,在传输数据时具有快速、简单的特点。而Node.js作为一种开发高效的网络应用程序的技术,通过利用Node.js的高效特点可以轻松地构建一个高效的UDP服务器。

本文将介绍如何使用Node.js构建一个高效的UDP服务器。读者将了解如何编写Node.js的服务器代码和客户端代码以及如何创建UDP套接字和监听服务器上的套接字。

Node UDP服务器的核心技术

Node.js是一种高性能、可扩展的JavaScript运行时环境,非常适合开发高性能网络应用程序。Node.js使用事件驱动、非阻塞I/O模式,可以轻松处理高并发。Node.js的高性能和可伸缩性使其非常适合用作UDP服务器的开发平台。

Node.js的UDP库提供了非常丰富的API来创建UDP套接字、监听服务器上的套接字(IP和端口),并编写处理UDP套接字的事件处理程序。这种事件驱动的编程方式可以使它们实现高性能。

使用Node.js编写UDP服务器的步骤

之一步:导入依赖包和创建套接字对象

为了开始使用Node.js开发UDP服务器,您需要安装Node.js。尝试以下命令以验证安装是否正确:

“`

$ node -v

“`

将以下代码片段添加到您的Node.js脚本中,以导入套接字相关的库文件:

“`

const dgram = require(‘dgram’);

“`

接下来,创建一个UDP服务器的套接字对象:

“`

const server = dgram.createSocket(‘udp4’);

“`

此代码片段使用Node.js的UDP库中的createSocket方法创建了一个IPv4套接字对象。我们可以使用IPv6协议创建一个IPv6套接字对象。

第二步:绑定UDP服务器

通过创建服务器套接字对象,我们设置了一系列套接字属性。但是,要让这些属性生效,我们需要将服务器绑定到IP地址和端口。

使用以下代码设置服务器绑定:

“`

const PORT = 3000;

const HOST = ‘127.0.0.1’;

server.bind(PORT, HOST);

“`

此代码片段将UDP服务器绑定到本地IP地址“127.0.0.1”和端口“3000”。这允许允许通过该端口和IP地址访问服务器。

第三步:使用服务器监听事件

事件监听是Node.js编程的核心特性。UDP服务器套接字上的事件可以监听是否有客户端请求数据服务。Node.js的UDP库为此提供了以下事件:’listening’, ‘message’和’close’。

将以下代码添加到我们的脚本中以监听客户端请求:

“`

server.on(‘listening’, () => {

const address = server.address();

console.log(`UDP Server listening on ${address.address}:${address.port}`);

});

“`

此代码片段监听服务器的“listening”事件。当UDP服务器绑定到指定的IP地址和端口时,它将触发“listening”事件。我们可以在“listening”事件处理程序中记录关于绑定的详细信息。

第四步:处理客户端消息

当客户端通过UDP连接到服务器时,它们将发送数据到服务器套接字。通过在UDP套接字上监听“message”事件,我们可以获取客户端发送的数据。

将以下代码添加到我们的脚本中以处理客户端消息:

“`

server.on(‘message’, (msg, remoteInfo) => {

console.log(`Message Received: ${msg} from ${remoteInfo.address}:${remoteInfo.port}`);

});

“`

此代码片段在UDP服务器套接字上监听“message”事件。当服务器接收到消息时,它会调用“message”事件处理程序。此处理程序将接收消息内容和客户端的IP地址和端口信息作为输入。

第五步:发送数据到客户端

Node.js的UDP库也可以让我们像服务器一样,向客户端发送响应消息。以下代码显示如何使用UDP套接字向客户端发送响应消息:

“`

const responseMessage = ‘Hello, UDP Client!’

const clientPort = remoteInfo.port;

const clientHost = remoteInfo.address;

server.send(responseMessage, clientPort, clientHost, (err) => {

if (err) console.log(err);

console.log(`Response sent to ${clientHost}:${clientPort}`);

});

“`

此代码片段将数据发送回客户端。“server.send”方法接受响应消息,客户端的端口和IP地址,以及可选的回调函数。当数据成功发送到客户端时,回调函数将被触发。如果由于任何原因无法发送数据,则会出现错误。

Node.js的UDP服务器的特点

– 快速:Node.js采用单线程模型,并使用非阻塞I/O模型,从而可以创建高性能、低延迟的服务器。

– 可伸缩:由于Node.js的非阻塞I/O模型,它可以同时处理大量连接。

– 易于编写:使用Node.js编写UDP服务器代码不需要任何特殊的技能,开发人员可以轻松实现高性能的UDP服务器。

本文介绍了如何使用Node.js编写简单高效的UDP服务器。我们了解了Node.js的UDP库,使用其创建UDP套接字、将服务器绑定到IP地址和端口、监听客户端请求,处理客户端消息和发送响应消息。Node.js的事件驱动编程方式使得Node.js编写高性能的网络应用变得非常容易。

相关问题拓展阅读:

linux下的服务器双机热备份,如何做?

Linux上的heartbeat双机热备服务架设

【一】 安装前环境设定

两台主机硬件环境(不必完全一致):庆哗

CPU: Xeon 3G *2 (EM64T)

MEM: 2G

NIC: Intel 1G *2

eth0: 对外IP

eth1: 对内IP(HA专用)

两台主机的eth1使用双机对联线直接连接。

分区方式:

Filesystem 容量 挂载点

/dev/sda2 9.7G /

/dev/sda6 45G /Datas

/dev/sda1 99M /boot

none2.0G /dev/shm

/dev/sda3 9.7G /opt

另外每台主机应预留500M的raw空间或者更多来作为共用空间被HA使用。

操作系统:

RedHat Enterprise 4 Update2 (2.6.9-22 EL)

预安装软件:

@ X Window System

@ GNOME Desktop Environment

@ KDE Desktop Environment

@ Editors

@ Engineering and Scientific

@ Graphical Internet

@ Text-based Internet

@ Authoring and Publishing

@ Server Configuration Tools

@ Development Tools

@ Kernel Development

@ X Software Development

@ GNOME Software Development

@ KDE Software Development

@ Administration Tools

@ System Tools

【二】安装前亮滑网络环境设定:

node1: 主机名:servers201 ( HA01 )

eth0: 192.168.10.201 //对外IP地址

eth1: 10.0.0.201 //HA心誉键行跳使用地址

node2: 主机名:servers202 ( HA02 )

eth0: 192.168.10.202 //对外IP地址

eth1: 10.0.0.202 //HA心跳使用地址

特别注意要检查以下几个文件:

/etc/hosts

/etc/host.conf

/etc/resolv.conf

/etc/sysconfig/network

/etc/sysconfig/network-scripts/ifcfg-eth0

/etc/sysconfig/network-scripts/ifcfg-eth1

/etc/nsswitch.conf

#vi /etc/hosts

node1的hosts内容如下:

127.0.0.1 localhost.localdomain localhost

192.168.10.201 servers201 HA01

10.0.0.201 HA01

10.0.0.202 HA02

192.168.10.202 server202

node2的hosts内容如下:

127.0.0.1 localhost.localdomain localhost

192.168.10.202 servers202 HA02

10.0.0.202 HA02

10.0.0.201 HA01

192.168.10.201 server201

#cat /etc/host.conf

order hosts,bind

#cat /etc/resolv.conf

nameserver 61.139.2.69 //DNS地址

#cat /etc/sysconfig/network

NETWORKING=yes

HOSTNAME=servers201 //主机名

GATEWAY=”192.168.10.1″ //网关

GATEWAY=”eth0″ //网关使用网卡

ONBOOT=YES //启动时加载

FORWARD_IPV4=”yes” //只允许IPV4

#cat /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE=eth0

ONBOOT=yes

BOOTPROTO=static

IPADDR=192.168.10.201

NETMASK=255.255.255.0

GATEWAY=192.168.10.1

TYPE=Ethernet

IPV6INIT=no

#cat /etc/sysconfig/network-scripts/ifcfg-eth1

DEVICE=eth1

ONBOOT=yes

BOOTPROTO=none

IPADDR=10.0.0.201

NETMASK=255.255.255.0

TYPE=Ethernet

与 在上面的配置中,除了

/etc/hosts

/etc/sysconfig/network

/etc/sysconfig/network-scripts/ifcfg-eth0

/etc/sysconfig/network-scripts/ifcfg-eth1

要各自修改外,其他一致。

配置完成后,试试在各自主机上ping对方的主机名,应该可以ping通:

/root#ping HA02

PING HA02 (10.0.0.202) 56(84) bytes of data.

64 bytes from HA02 (10.0.0.202): icmp_seq=0 ttl=64 time=0.198 ms

64 bytes from HA02 (10.0.0.202): icmp_seq=1 ttl=64 time=0.266 ms

64 bytes from HA02 (10.0.0.202): icmp_seq=2 ttl=64 time=0.148 ms

— HA02 ping statistics —

3 packets tranitted, 3 received, 0% packet loss, time 2023ms

rtt min/avg/max/mdev = 0.148/0.204/0.266/0.048 ms, pipe 2

【三】安装HA 与HA依赖包

rpm -Uvh libnet-1.1.2.1-1.rh.el.um.1.i386.rpm //可以不装

rpm -Uvh heartbeat-pils-2.0.4-1.el4.i386.rpm

rpm -Uvh heartbeat-stonith-2.0.4-1.el4.i386.rpm

rpm -Uvh heartbeat-2.0.4-1.el4.i386.rpm

rpm -Uvh ipvsadm-1.24-5.i386.rpm

【四】 配置 HA的各配置文件

配置心跳的加密方式:authkeys

#vi /etc/ha.d/authkeys

如果使用双机对联线(双绞线),可以配置如下:

#vi /etc/hc.d/authkeys

auth 1

1 crc

存盘退出,然后

#chmod 600 authkeys

配置心跳的监控:haresources

#vi /etc/ha.d/haresources

各主机这部分应完全相同。

server201 IPaddr::192.168.10.200 ipvsadm httpd

指定 server201调用ipvsadm启动http服务,系统附加一个虚拟IP 192.168.10.200 给eth0:0

这里如果server201宕机后,server202可以自动启动http服务,并新分配IP 192.168.10.200给server202的eth0:0

配置心跳的配置文件:ha.cf

#vi /etc/ha.d/ha.cf

logfile /var/log/ha_log/ha-log.log ## ha的日志文件记录位置。如没有该目录,则需要手动添加

bcast eth1 ##使用eth1做心跳监测

keepalive 2 ##设定心跳(监测)时间时间为2秒

warntime 10

deadtime 30

initdead 120

hopfudge 1

udpport 694 ##使用udp端口694 进行心跳监测

auto_failback on

node server201 ##节点1,必须要与 uname -n 指令得到的结果一致。

node server202 ##节点2

ping 192.168.10.1 ##通过ping 网关来监测心跳是否正常。

respawn hacluster /usr/lib64/heartbeat/ipfail

apiauth ipfail gid=root uid=root

debugfile /Datas/logs/ha_log/ha-debug.log

设置ipvsadm的巡回监测

ipvsadm -A -t 192.168.10.200:80 -s rr

ipvsadm -a -t 192.168.10.200:80 -r 192.168.10.201:80 -m

ipvsadm -a -t 192.168.10.200:80 -r 192.168.10.202:80 -m

执行后进行监测:

#ipvsadm –list

如果返回结果与下相同,则设置正确。

IP Virtual Server version 1.2.0 (size=4096)

Prot LocalAddress:Port Scheduler Flags

-> RemoteAddress:Port Forward Weight ActiveConn InActConn

TCP 192.168.10.200:http rr

-> server202:http Local 1 0 0

-> server201:http Masq 1 0 0

【五】 HA服务的启动、关闭以及测试

启动HA: service heartbeat start

关闭HA; service heartbeat stop

系统在启动时已经自动把heartbeat 加载了。

使用http服务测试 heartbeat

首先启动httpd服务

#service httpd start

编辑各自主机的测试用html文件,放到/var/www/html/目录下。

启动node1的heartbeat,并执行这个指令进行监控: heartbeat status

【六】 防火墙设置

heartbeat 默认使用udp 694端口进行心跳监测。如果系统有使用iptables 做防火墙,应记住把这个端口打开。

#vi /etc/sysconfig/iptables

加入以下内容

-A RH-Firewall-1-INPUT -p udp -m udp –dport 694 -d 10.0.0.201 -j ACCEPT

意思是udp 694端口对 对方的心跳网卡地址 10.0.0.201 开放。

#service iptables restart

javascript 可以发送 UDP 么?如果是的话,具体使用哪个类?谢谢!!!

var socket = __import__(‘Socket’, null, {}) //导入Socket library..

var client = new socket.Socket(‘127.0.0.1’, 8000) //创建一个Socket.

client.on(‘read’, function(socket){ //注册读数据的事件

var echo = socket.read();//读收到的数据,,返回的结果为string对象

print(‘echo from server:’ + echo)

socket.write(“Bye!”)

socket.close() //关闭socket.

})

client.on(‘connect’, function(socket){//注册连接成功时事件

print(‘connectted….’)

socket.write(“Hello server, i’m a client!”)

})

client.on(‘closed’, function(socket){ //socket 关闭后的事件.

//client.write(“Bye!”)

print(‘Bye!’)

})

client.connect()//连接服务器,,并等待触发事件,,直到socket关闭.

不可以发UDP的,只能是TCP,而且是基于HTTP去实现,

除非你自己写一个OCX控件/COM组件,然后在浏览器里面创建它,进行调用,

不过这样的话,需要用户 安装你的控件。

此方法经常用于,某个大型媒体网站的文章编辑器,

或者管理软件的 报表组件,

总之一般情况下不会有这样的要求,因为网页有的时候有局限,所以才会写一些组件,而且用户群都是非常愿意安装的才行,

用JavaScript的话。。。意思是NodeJs里的UDP编程呗

你导入一个库即可:

dgram

比如:

var dgram = require(‘dgram’);

var serverSocket = dgram.createSocket(‘udp4’);

更详细的例子去查下它的API就行啦

javascript在浏览器里跑的东西,你的浏览器可以发送UDP吗?

如果浏览器不支持,你怎么让javascript来实现?

应该不可以,因为浏览器是http协议通讯(底层使用的TCP协议)。 封装好的,除非你重写浏览器········

在Linux 上,编写一个每秒接收 万UDP数据包的程序究竟有多难

首先,我们假设:

测神世量每秒的数据包(pps)比测量每秒字节数(Bps)更有意思。您可以通过更好的管道输送以及发送更长数据包来获取更高的Bps。而相比之下,提高pps要困难得多。

因为我们对pps感兴趣,我们的实验将使用较短的 UDP 消息。准确来说是 32 字节的 UDP 负载,这相当于以太网层的 74 字节。

在实验中,我们将使用两个物理服务器:“接收器”和“发送器”。

它们都有两个六核2 GHz的 Xeon处理器。每个服务器都启用了 24 个处理器的超线程(HT),有 Solarflare 的 10G 多队列网卡,有 11 个接收队列配置。稍后将详细介绍。

测试程序的源代码分别是:udpsender、udpreceiver。

预备知识

我们使用4321作为UDP数据包的端口,在开始之前,我们必须确保传输不会被iptables干扰:

Shell

receiver$ iptables -I INPUT 1 -p udp –dportj ACCEPT

receiver$ iptables -t raw -I PREROUTING 1 -p udp –dportj NOTRACK

为了后面测试方便,我们显式地定义IP地址:

Shell

receiver$ for i in `seq 1 20`; do

ip addr add 192.168.254.$i/24 dev eth2;

done

sender$ ip addr add 192.168.254.30/24 dev eth3

1. 简单的方法

开始我们做一些最简单的试验。通过简单地发送和接收,有多少包将会被传送?

模拟发送者的伪代码:

Python

fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

fd.bind((“0.0.0.0”, 65400)) # select source port to reduce nondetermini

fd.connect((“192.168.254.1”, 4321))

while True:

fd.sendmmsg( * 1024)

因为我们使用了常见的系统调用的send,所以效率不会很高。上下文切换到内核代价很高所以更好避免它。幸运地是,最近Linux加入了一个方便的系统调用叫sendmmsg。它允许我们在一次调用时,发送很多的数据包。那我们就一次发1024个数据包。

模拟接受者的伪代码裤冲:

Python

fd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

fd.bind((“0.0.0.0”, 4321))

while True:

packets = * 1024

fd.recvmmsg(packets, MSG_WAITFORONE)

同样地,recvmmsg 也是相对于常见的 recv 更有效的一版系统调用。

让我们试试吧:

Shell

sender$ ./udpsender 192.168.254.1:4321

receiver$ ./udpreceiver1 0.0.0.0:4321

0.352M pps 10.730MiB / 90.010Mb

0.284M pps 8.655MiB / 72.603Mb

0.262M pps 7.991MiB / 67.033Mb

0.199M pps 6.081MiB / 51.013Mb

0.195M pps 5.956MiB / 49.966Mb

0.199M pps 6.060MiB / 50.836Mb

0.200M pps 6.097MiB / 51.147Mb

0.197M pps 6.021MiB / 50.509Mb

测试发现,运用最简单的方式可游纯肢以实现 197k – 350k pps。看起来还不错嘛,但不幸的是,很不稳定啊,这是因为内核在核之间交换我们的程序,那我们把进程附在 CPU 上将会有所帮助

Shell

sender$ taskset -c 1 ./udpsender 192.168.254.1:4321

receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321

0.362M pps 11.058MiB / 92.760Mb

0.374M pps 11.411MiB / 95.723Mb

0.369M pps 11.252MiB / 94.389Mb

0.370M pps 11.289MiB / 94.696Mb

0.365M pps 11.152MiB / 93.552Mb

0.360M pps 10.971MiB / 92.033Mb

现在内核调度器将进程运行在特定的CPU上,这提高了处理器缓存,使数据更加一致,这就是我们想要的啊!

2. 发送更多的数据包

虽然 370k pps 对于简单的程序来说已经很不错了,但是离我们 1Mpps 的目标还有些距离。为了接收更多,首先我们必须发送更多的包。那我们用独立的两个线程发送,如何呢:

Shell

sender$ taskset -c 1,2 ./udpsender

2.168.254.1:.168.254.1:4321

receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321

0.349M pps 10.651MiB / 89.343Mb

0.354M pps 10.815MiB / 90.724Mb

0.354M pps 10.806MiB / 90.646Mb

0.354M pps 10.811MiB / 90.690Mb

接收一端的数据没有增加,ethtool –S 命令将显示数据包实际上都去哪儿了:

Shell

receiver$ watch ‘sudo ethtool -S eth2 |grep rx’

rx_nodesc_drop_cnt:.3k/s

rx-0.rx_packets: 8.0/s

rx-1.rx_packets: 0.0/s

rx-2.rx_packets: 0.0/s

rx-3.rx_packets: 0.5/s

rx-4.rx_packets: 355.2k/s

rx-5.rx_packets: 0.0/s

rx-6.rx_packets: 0.0/s

rx-7.rx_packets: 0.5/s

rx-8.rx_packets: 0.0/s

rx-9.rx_packets: 0.0/s

rx-10.rx_packets: 0.0/s

通过这些统计,NIC 显示 4 号 RX 队列已经成功地传输大约 350Kpps。rx_nodesc_drop_cnt 是 Solarflare 特有的计数器,表明NIC发送到内核未能实现发送 450kpps。

有时候,这些数据包没有被发送的原因不是很清晰,然而在我们这种情境下却很清楚:4号RX队列发送数据包到4号CPU,然而4号CPU已经忙不过来了,因为它最忙也只能读350kpps。在htop中显示为:

多队列 NIC 速成课程

从历史上看,网卡拥有单个RX队列,用于硬件和内核之间传递数据包。这样的设计有一个明显的限制,就是不可能比单个CPU处理更多的数据包。

为了利用多核系统,NIC开始支持多个RX队列。这种设计很简单:每个RX队列被附到分开的CPU上,因此,把包送到所有的RX队列网卡可以利用所有的CPU。但是又产生了另一个问题:对于一个数据包,NIC怎么决定把它发送到哪一个RX队列?

用 Round-robin 的方式来平衡是不能接受的,因为这有可能导致单个连接中数据包的重排序。另一种方法是使用数据包的hash值来决定RX号码。Hash值通常由一个元组(源IP,目标IP,源port,目标port)计算而来。这确保了从一个流产生的包将最终在完全相同的RX队列,并且不可能在一个流中重排包。

在我们的例子中,hash值可能是这样的:

Shell

RX_queue_number = hash(‘192.168.254.30’, ‘192.168.254.1’, 65400, 4321) % number_of_queues

多队列 hash 算法

Hash算法通过ethtool配置,设置如下:

Shell

receiver$ ethtool -n eth2 rx-flow-hash udp4

UDP over IPV4 flows use these fields for computing Hash flow key:

IP SA

IP DA

对于IPv4 UDP数据包,NIC将hash(源 IP,目标 IP)地址。即

Shell

RX_queue_number = hash(‘192.168.254.30’, ‘192.168.254.1’) % number_of_queues

这是相当有限的,因为它忽略了端口号。很多NIC允许自定义hash。再一次,使用ethtool我们可以选择元组(源 IP、目标 IP、源port、目标port)生成hash值。

Shell

receiver$ ethtool -N eth2 rx-flow-hash udp4 sdfn

Cannot change RX network flow hashing options: Operation not supported

不幸地是,我们的NIC不支持自定义,我们只能选用(源 IP、目的 IP) 生成hash。

NUMA性能报告

到目前为止,我们所有的数据包都流向一个RX队列,并且一个CPU。我们可以借这个机会为基准来衡量不同CPU的性能。在我们设置为接收方的主机上有两个单独的处理器,每一个都是一个不同的NUMA节点。

在我们设置中,可以将单线程接收者依附到四个CPU中的一个,四个选项如下:

另一个CPU上运行接收器,但将相同的NUMA节点作为RX队列。性能如上面我们看到的,大约是360 kpps。

将运行接收器的同一 CPU 作为RX队列,我们可以得到大约430 kpps。但这样也会有很高的不稳定性,如果NIC被数据包所淹没,性能将下降到零。

当接收器运行在HT对应的处理RX队列的CPU之上,性能是通常的一半,大约在200kpps左右。

接收器在一个不同的NUMA节点而不是RX队列的CPU上,性能大约是330 kpps。但是数字会不太一致。

虽然运行在一个不同的NUMA节点上有10%的代价,听起来可能不算太坏,但随着规模的变大,问题只会变得更糟。在一些测试中,每个核只能发出250 kpps,在所有跨NUMA测试中,这种不稳定是很糟糕。跨NUMA节点的性能损失,在更高的吞吐量上更明显。在一次测试时,发现在一个坏掉的NUMA节点上运行接收器,性能下降有4倍。

3.多接收IP

因为我们NIC上hash算法的限制,通过RX队列分配数据包的唯一方法是利用多个IP地址。下面是如何将数据包发到不同的目的IP:

sender$ taskset -c 1,2 ./udpsender 192.168.254.1:.168.254.2:4321

ethtool 证实了数据包流向了不同的 RX 队列:

Shell

receiver$ watch ‘sudo ethtool -S eth2 |grep rx’

rx-0.rx_packets: 8.0/s

rx-1.rx_packets: 0.0/s

rx-2.rx_packets: 0.0/s

rx-3.rx_packets: 355.2k/s

rx-4.rx_packets: 0.5/s

rx-5.rx_packets: 297.0k/s

rx-6.rx_packets: 0.0/s

rx-7.rx_packets: 0.5/s

rx-8.rx_packets: 0.0/s

rx-9.rx_packets: 0.0/s

rx-10.rx_packets: 0.0/s

接收部分:

Shell

receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321

0.609M pps 18.599MiB / 156.019Mb

0.657M pps 20.039MiB / 168.102Mb

0.649M pps 19.803MiB / 166.120Mb

万岁!有两个核忙于处理RX队列,第三运行应用程序时,可以达到大约650 kpps !

我们可以通过发送数据到三或四个RX队列来增加这个数值,但是很快这个应用就会有另一个瓶颈。这一次rx_nodesc_drop_cnt没有增加,但是netstat接收到了如下错误:

Shell

receiver$ watch ‘netstat -s –udp’

Udp:

.0k/s packets received

.0/s packets to unknown port received.

.9k/s packet receive errors

.0/s packets sent

RcvbufErrors: 123.8k/s

SndbufErrors: 0

InCsumErrors: 0

这意味着虽然NIC能够将数据包发送到内核,但是内核不能将数据包发给应用程序。在我们的case中,只能提供440 kpps,其余的390 kpps + 123 kpps的下降是由于应用程序接收它们不够快。

4.多线程接收

我们需要扩展接收者应用程序。最简单的方式是利用多线程接收,但是不管用:

Shell

sender$ taskset -c 1,2 ./udpsender 192.168.254.1:.168.254.2:4321

receiver$ taskset -c 1,2 ./udpreceiver1 0.0.0.0:

0.495M pps 15.108MiB / 126.733Mb

0.480M pps 14.636MiB / 122.775Mb

0.461M pps 14.071MiB / 118.038Mb

0.486M pps 14.820MiB / 124.322Mb

接收性能较于单个线程下降了,这是由UDP接收缓冲区那边的锁竞争导致的。由于两个线程使用相同的套接字描述符,它们花费过多的时间在UDP接收缓冲区的锁竞争。这篇论文详细描述了这一问题。

看来使用多线程从一个描述符接收,并不是更优方案。

5. SO_REUSEPORT

幸运地是,最近有一个解决方案添加到 Linux 了 —— SO_REUSEPORT 标志位(flag)。当这个标志位设置在一个套接字描述符上时,Linux将允许许多进程绑定到相同的端口,事实上,任何数量的进程将允许绑定上去,负载也会均衡分布。

有了SO_REUSEPORT,每一个进程都有一个独立的socket描述符。因此每一个都会拥有一个专用的UDP接收缓冲区。这样就避免了以前遇到的竞争问题:

Shell

receiver$ taskset -c 1,2,3,4 ./udpreceiver1 0.0.0.0:

1.114M pps 34.007MiB / 285.271Mb

1.147M pps 34.990MiB / 293.518Mb

1.126M pps 34.374MiB / 288.354Mb

现在更加喜欢了,吞吐量很不错嘛!

更多的调查显示还有进一步改进的空间。即使我们开始4个接收线程,负载也会不均匀地分布:

两个进程接收了所有的工作,而另外两个根本没有数据包。这是因为hash冲突,但是这次是在SO_REUSEPORT层。

结束语

我做了一些进一步的测试,完全一致的RX队列,接收线程在单个NUMA节点可以达到1.4Mpps。在不同的NUMA节点上运行接收者会导致这个数字做多下降到1Mpps。

总之,如果你想要一个完美的性能,你需要做下面这些:

确保流量均匀分布在许多RX队列和SO_REUSEPORT进程上。在实践中,只要有大量的连接(或流动),负载通常是分布式的。

需要有足够的CPU容量去从内核上获取数据包。

To make the things harder, both RX queues and receiver processes should be on a single NUMA node.

为了使事情更加稳定,RX队列和接收进程都应该在单个NUMA节点上。

node udp服务器的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于node udp服务器,Node UDP服务器:使用Node.js建立高效的UDP服务器,linux下的服务器双机热备份,如何做?,javascript 可以发送 UDP 么?如果是的话,具体使用哪个类?谢谢!!!,在Linux 上,编写一个每秒接收 万UDP数据包的程序究竟有多难的信息别忘了在本站进行查找喔。


数据运维技术 » Node UDP服务器:使用Node.js建立高效的UDP服务器 (node udp服务器)