当前位置:首页 > 代码 > 正文

udp测速代码(udp协议怎么测试)

admin 发布:2022-12-19 18:25 168


本篇文章给大家谈谈udp测速代码,以及udp协议怎么测试对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

有没有windows下c语言实现udp协议的代码

Windows下C语言的Socket编程例子(TCP和UDP)

一。  TCP

server端:

复制代码

 1 #include "stdafx.h"

 2 #include stdio.h

 3 #include winsock2.h

 4 

 5 #pragma comment(lib,"ws2_32.lib")

 6 

 7 int main(int argc, char* argv[])

 8 {

 9     //初始化WSA

10     WORD sockVersion = MAKEWORD(2,2);

11     WSADATA wsaData;

12     if(WSAStartup(sockVersion, wsaData)!=0)

13     {

14         return 0;

15     }

16 

17     //创建套接字

18     SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

19     if(slisten == INVALID_SOCKET)

20     {

21         printf("socket error !");

22         return 0;

23     }

24 

25     //绑定IP和端口

26     sockaddr_in sin;

27     sin.sin_family = AF_INET;

28     sin.sin_port = htons(8888);

29     sin.sin_addr.S_un.S_addr = INADDR_ANY; 

30     if(bind(slisten, (LPSOCKADDR)sin, sizeof(sin)) == SOCKET_ERROR)

31     {

32         printf("bind error !");

33     }

34 

35     //开始监听

36     if(listen(slisten, 5) == SOCKET_ERROR)

37     {

38         printf("listen error !");

39         return 0;

40     }

41 

42     //循环接收数据

43     SOCKET sClient;

44     sockaddr_in remoteAddr;

45     int nAddrlen = sizeof(remoteAddr);

46     char revData[255]; 

47     while (true)

48     {

49         printf("等待连接...\n");

50         sClient = accept(slisten, (SOCKADDR *)remoteAddr, nAddrlen);

51         if(sClient == INVALID_SOCKET)

52         {

53             printf("accept error !");

54             continue;

55         }

56         printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));

57         

58         //接收数据

59         int ret = recv(sClient, revData, 255, 0);        

60         if(ret  0)

61         {

62             revData[ret] = 0x00;

63             printf(revData);

64         }

65 

66         //发送数据

67         char * sendData = "你好,TCP客户端!\n";

68         send(sClient, sendData, strlen(sendData), 0);

69         closesocket(sClient);

70     }

71     

72     closesocket(slisten);

73     WSACleanup();

74     return 0;

75 }

复制代码

client端:

复制代码

 1 #include "stdafx.h"

 2 #include WINSOCK2.H

 3 #include STDIO.H

 4 

 5 #pragma  comment(lib,"ws2_32.lib")

 6 

 7 

 8 int main(int argc, char* argv[])

 9 {

10     WORD sockVersion = MAKEWORD(2,2);

11     WSADATA data; 

12     if(WSAStartup(sockVersion, data) != 0)

13     {

14         return 0;

15     }

16 

17     SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

18     if(sclient == INVALID_SOCKET)

19     {

20         printf("invalid socket !");

21         return 0;

22     }

23 

24     sockaddr_in serAddr;

25     serAddr.sin_family = AF_INET;

26     serAddr.sin_port = htons(8888);

27     serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 

28     if (connect(sclient, (sockaddr *)serAddr, sizeof(serAddr)) == SOCKET_ERROR)

29     {

30         printf("connect error !");

31         closesocket(sclient);

32         return 0;

33     }

34     char * sendData = "你好,TCP服务端,我是客户端!\n";

35     send(sclient, sendData, strlen(sendData), 0);

36 

37     char recData[255];

38     int ret = recv(sclient, recData, 255, 0);

39     if(ret  0)

40     {

41         recData[ret] = 0x00;

42         printf(recData);

43     }

44     closesocket(sclient);

45     WSACleanup();

46     return 0;

47 }

复制代码

 

二. UDP

SERVER 端

复制代码

 1 #include "stdafx.h"

 2 #include stdio.h

 3 #include winsock2.h

 4 

 5 #pragma comment(lib, "ws2_32.lib") 

 6 

 7 int main(int argc, char* argv[])

 8 {

 9     WSADATA wsaData;

10     WORD sockVersion = MAKEWORD(2,2);

11     if(WSAStartup(sockVersion, wsaData) != 0)

12     {

13         return 0;

14     }

15 

16     SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 

17     if(serSocket == INVALID_SOCKET)

18     {

19         printf("socket error !");

20         return 0;

21     }

22 

23     sockaddr_in serAddr;

24     serAddr.sin_family = AF_INET;

25     serAddr.sin_port = htons(8888);

26     serAddr.sin_addr.S_un.S_addr = INADDR_ANY;

27     if(bind(serSocket, (sockaddr *)serAddr, sizeof(serAddr)) == SOCKET_ERROR)

28     {

29         printf("bind error !");

30         closesocket(serSocket);

31         return 0;

32     }

33     

34     sockaddr_in remoteAddr;

35     int nAddrLen = sizeof(remoteAddr); 

36     while (true)

37     {

38         char recvData[255];  

39         int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr *)remoteAddr, nAddrLen);

40         if (ret  0)

41         {

42             recvData[ret] = 0x00;

43             printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));

44             printf(recvData);            

45         }

46 

47         char * sendData = "一个来自服务端的UDP数据包\n";

48         sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)remoteAddr, nAddrLen);    

49 

50     }

51     closesocket(serSocket); 

52     WSACleanup();

53     return 0;

54 }

复制代码

CLIENT 端

复制代码

 1 #include "stdafx.h"

 2 #include stdio.h

 3 #include winsock2.h

 4 

 5 #pragma comment(lib, "ws2_32.lib") 

 6 

 7 int main(int argc, char* argv[])

 8 {

 9     WORD socketVersion = MAKEWORD(2,2);

10     WSADATA wsaData; 

11     if(WSAStartup(socketVersion, wsaData) != 0)

12     {

13         return 0;

14     }

15     SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

16     

17     sockaddr_in sin;

18     sin.sin_family = AF_INET;

19     sin.sin_port = htons(8888);

20     sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

21     int len = sizeof(sin);

22     

23     char * sendData = "来自客户端的数据包.\n";

24     sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)sin, len);

25 

26     char recvData[255];     

27     int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)sin, len);

28     if(ret  0)

29     {

30         recvData[ret] = 0x00;

31         printf(recvData);

32     }

33 

34     closesocket(sclient);

35     WSACleanup();

36     return 0;

37 }

用DatagramPacket测试UDP通信

使用DatagramSocket和DatagramPacket

其中DatagramSocket使用一个发送的端口号

DatagramPacket的参数

DatagramPacket(bytes[] bytes,int bytes[].length,InetAddress ip, int port);里的port就是目标端口

要向两个端口号发送,需要创建两个DatagramPacket,分别填写两个IP地址和端口号,并用DatagramSocket发送。

另外两个端口需要做监听

要注意ip要为局域网可以畅通无阻,广域网可能导致网管阻止

下面是个例子,发两遍即可。

int SERVER_PORT=7000;

String severIp="192.168.1.100";

DatagramSocket clientSocket;

try

{

InetAddress local = InetAddress.getByName(severIp);

DatagramPacket dPacket;

clientSocket = new DatagramSocket;

dPacket = new DatagramPacket("Hello".getBytes("utf-8"),"Hello".getBytes("utf-8").length,

local, SERVER_PORT);

clientSocket.send(dPacket);//这里是发送

byte[] msg = new byte[11246];

DatagramPacket rPacket = new DatagramPacket(msg, msg.length);

clientSocket.receive(rPacket);//这里是接收监听

}

catch (IOException e)

{

e.printStackTrace();

}

clientSocket.close();

在Linux 上,编写一个每秒接收 100万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 --dport 4321 -j ACCEPT

receiver$ iptables -t raw -I PREROUTING 1 -p udp --dport 4321 -j 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 nondeterminism

fd.connect(("192.168.254.1", 4321))

while True:

fd.sendmmsg(["x00" * 32] * 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 = [None] * 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

192.168.254.1:4321 192.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: 451.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

1

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

1

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:

1

sender$ taskset -c 1,2 ./udpsender 192.168.254.1:4321 192.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:

437.0k/s packets received

0.0/s packets to unknown port received.

386.9k/s packet receive errors

0.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:4321 192.168.254.2:4321

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

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

1

2

3

4

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

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节点上。

虽然我们已经表明,在一台Linux机器上接收1Mpps在技术上是可行的,但是应用程序将不会对收到的数据包做任何实际处理——甚至连看都不看内容的流量。别太指望这样的性能,因为对于任何实际应用并没有太大用处。

关于udp测速代码和udp协议怎么测试的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

版权说明:如非注明,本站文章均为 AH站长 原创,转载请注明出处和附带本文链接;

本文地址:http://ahzz.com.cn/post/17422.html


取消回复欢迎 发表评论:

分享到

温馨提示

下载成功了么?或者链接失效了?

联系我们反馈

立即下载