websocket 编译编程为什么下面两段代码可以正常编译,但是程序接受数据方,接受不到数据?

简介 Java是一个强大的面向对象开发语言,支持很多功能,比如,通过Socket编程达到的C/S通讯,基于Windows的编程,基于CONSOLE的编程,还有数据库连接,图象和声音编程等。Java多用在基于INTERNET的网络编程上,创建一些嵌入到Html页面中的Applet小程序来实现。在开始实际编写代码之前,为了使得概念更加清楚,需要提及几个重要的概念。广播信使(broadcast messenger)是要创建一个服务器,用来负责接收和响应来自客户机的网络消息。这个就叫做广播(Broadcasting),意思是发送数据包或者消息到所有的客户机。这里使用的是服务器/客户机(C/S)框架,因为有一台计算机扮演服务器的角色来响应客户机的消息,所有其他的计算机都扮演客户机的角色,仅仅只是发送请求到服务器来执行它们的一些任务。Socket是连接计算机彼此的一个逻辑连接。要创建一个socket,需要提供一个端口号和一个主机IP地址/主机名。多线程意味着一个进程的多个线程能够同时运行在分配给它们的同一个处理器上,就感觉好象只有进程在运行。所以,通过多线程技术,许多客户机可以连接服务器的同一个端口。线程是占有资源的进程或程序的一部分,比如文件,I/O等等,它们能够独立运行。Java代码解释首先...
的相关文章
1.Socket传输模式 Sockets有两种主要的操作方式:面向连接的和无连接的。面向连接的sockets操作就像一部电话,他们必须建立一个连接和一人呼叫。所有的事情在到达时的顺序与它们出发时的顺序时一样。!-- frame contents -- !-- /frame contents --无连接的sockets操作就像是一个邮件投递,没有什么保证,多个邮件可能在到达时的顺序与出发时的顺序不一样。 到底用哪种模式是邮应用程...
//返回本地主机名称及IP地址; InetAddress i = InetAddress.getLocalHost(); i.getHostAddress();//IP i.getAddress();//??? //通过计算机名称获取计算机相关信息; InetAddress i = InetAddress.getByName("Livingstone-PC"); //通过域名获取主机相关信息 InetAddress ibaidu = InetAddress.getByName("www.baidu.com"); URL url = new URL("http://localhost:8080/demo.html"); url.getHost(); TCP: S...
笔者在前一段的工作中,需要开发一套简单的网络数据传输程序。由于平时常用Delphi做点开发,故此次也不例外。Delphi 7中带有两套TCP Socket组件:Indy Socket组件(IdTCPClient和IdTCPServer)和Delphi原生的TCP Socket组件(ClientSocket和ServerSocket)。但是,Borland已宣称ClientSocket和ServerSocket组件即将被废弃,建议用相应的Indy组件来代替。因此,笔者使用了Indy。本文在对Indy进行简要介...
&&&&&&& Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以较为方便的编写网络上数据的传递。在Java中,有专门的Socket类来处理用户的请求和响应。利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。 在Java中Socket可以理解为客户端或者服务器端的一个非...
& 前言: 关于 JWS JWS 是作为 JSR-56 的一部分而创建的,目的是提供一种方法来分发在客户端上的 JVM 中运行的 Java 应用程序。JWS 包括以下特征: 部署 &&& 运行 JWS 应用程序包括选择 Html 链接,与打开新的页面没有什么区别,惟一的不同之处在于 Web 服务器页面是 .jnlp 文件,该文件启动 JWS 插件来在客户端上本地下载和执行应用程序。 缓存 &nb...
ClientSocket组件为客户端组件。它是通信的请求方,也就是说,它是主动地与服务器端建立连接。 ServerSocket组件为服务器端组件。它是通信的响应方,也就是说,它的动作是监听以及被动接受客户端的连接请求,并对请求进行回复。 ServerSocket组件可以同时接受一个或多个ClientSocket组件的连接请求,并与每个ClientSocket组件建立单独的连接,进行单独的通信。因此,一个服务器端可以为多...
大多数程序都需要输出一些文本,比如邮件消息、HTML文件或控制台输出。但是,计算机本质上只能处理二进制数据,程序员必须让软件来生成可理解的文本。在这篇文章中,我要介绍的是在生成和输出文本时,为何使用模板引擎能够节省时间。你将了解模板的优点,如何针对不同的情形创建高效的模板。和System.println说再见! 虽然程序员可以很轻松地编写出输出文字信息的代码(因为这毕竟是从Hello World范...
1. Client/Server Networking Java通过socket来完成它所有的网络底层的通讯,socket是一种通讯的通道,通过它可以将数据通过特定的端中发送及接收。Java中的socket可以分成二大类: (1) Datagram Sockets:数据包socket; (2) Stream Sockets:流socket; 1.2 Datagram Socket Datagram socket使用UDP来实现数据通讯,因此它不能保证数据能够到达目的地,但是由...
IP协议是Internet上所有信息的传播手段,UDP(User Datagram Protocol,用户数据报协议)数据报被封装在IP包中,发送到网络上适当的机器。众所周知,大多数IP使用单点发送,即从一个主机发送一个包到另一个主机上。 !-- frame contents -- !-- /frame contents -- 然而,IP协议也具有多点发送的能力,若要使用多点发送时,一个报文标有一组目标主机地址,当报文发出后,整个组都能收到。为支...
这个学期,我们学习了Java编程,期末之时老师要我们编个记事本作为这门课程的课程设计,我写了一个,大体上的功能都实现了,只有&撤销&的功能没有实现,请各位大虾指点一下,撤销的算法,不甚感激! 特将记事本的源码附下,请大家指点,看还有没有更简洁高效的算法,请不要吝啬,我的E-mail:,欢迎大家来信指点! /* * *题目:记事本程序 *Author: Jeason * * 2004-...
[文章导读]Windows Sockets 是从 Berkeley Sockets 扩展而来的,其在继续 Berkeley Sockets 的基础上,又进行了新的扩充 一、简介 !-- frame contents -- !-- /frame contents -- Windows Sockets 是从 Berkeley Sockets 扩展而来的,其在继续 Berkeley Sockets 的基础上,又进行了新的扩充。这些扩充主要是提供了一些异步函数,并增加了符合WINDOWS消息驱动特性的网络事件异步选择机制。Window...
信使服务是Windows 2000/XP下面的一种通信服务,通过它我们可以在网上象QQ一样进行实时的交流,但和QQ不同的是信使服务不需要拥有一个号码,它只要知道对方的IP地址--如果在局域网中,只要知道对方的计算机名就可以了。我们可以利用"net send"在命令行下面发送消息,也可以通过"控制面板-管理工具-计算机管理"中的"操作"菜单里面的"所有任务-发送控制台消息…"来发送。接收方的计算机要求必须是Win2000/X...
========================================= package myprojects. import javax.swing.UIM import java.awt.*; public class JMemoryDemo { private boolean packFrame = public JMemoryDemo() { MainFrame frame = new MainFrame(); if (packFrame) { frame.pack(); } else { frame.validate(); } Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize()...
$z = new PHPZip(); //新建立一个zip的类 方法一: $z - Zip(&&, &out1.zip&); //添加当前目录和子目录下的所有档案 方法二: $files=array('1.txt','gb.txt'); $files[]='5.txt'; $z - Zip($files, &out2.zip&); //添加文件列表 方法三: $z - Zip(&/usr/local/sext/&, &out3.zip&); //添加指定目录
&& 在我们构造Google 搜索程序之前我们还需要Google Web APIs Developer's Kit,你可以从http ://www.google.com/apis/download.html下截直接解压缩就可以了。里面已经含了所需要的文件和已经编译好的文件,官方地址是http ://www.google.com/apis。不过实际上对我们有用的只是GoogleSearch.wsdl这个文件,把这个文件和SWF文件放在同一文件夹运行既可。 构造一个Flash 表单屏屏幕 1. 在 F...
---- 摘要:在Java出现之前,编写多线程程序是一件烦琐且伴随许多不安全因素的事情。利用Java,编写安全高效的多线程程序变得简单,而且利用多线程和Java的网络包我们可以方便的实现多线程服务器程序。 ---- Java是伴随Internet的大潮产生的,对网络及多线程具有内在的支持,具有网络时代编程语言的一切特点。从Java的当前应用看,Java主要用于在Internet或局域网上的网络编程,而且将Java作为...
使用Java语言编写应用程序最大的优点在于“一次编译,处处运行”,然而这并不是说所有的Java程序都具有跨平台的特性,事实上,相当一部分的Java程序是不能在别的操作系统上正确运行的,那么如何才能编写一个真正的跨平台的Java程序呢?下面是在编写跨平台的Java程序是需要注意的一些事情: 1.编写Java跨平台应用程序时,你可以选择JDK1.0,1.1,1.2或支持它们的GUI开发工具如:Jbuilder,...
Ajax(即异步 JavaScript 和 XML)是一种 Web 应用程序开发的手段,它采用客户端脚本与 Web 服务器交换数据。所以,不必采用会中断交互的完整页面刷新,就可以动态地更新 Web 页面。使用 Ajax,可以创建更加丰富、更加动态的 Web 应用程序用户界面,其即时性与可用性甚至能够接近本机桌面应用程序。 Ajax 不是一项技术,而更像是一个 模式 —— 一种识别和描述有用的设计技术的方式。Ajax 是新颖的,因为许...
'######################################################## '大风(XuanKong) '下面所显示的代码生成的jar文件你可以在www.xuankong.com获得! '####################################################### Sun公司java语言的的推出可以说是一个划时代的语言革命,这种语言同时能适应应用软件领域和网络开发领域,其优秀的网络开发功能更是让人惊叹,利用java你可以轻松的实现在其他的语言中要花很多代码...
本技巧将向您讲述如何编写可通过代理访问因特网上的Web服务器的Java应用程序。在Java应用程序中加入代理支持只需额外编写几行代码,且不依赖任何安全性“漏洞”。 几乎所有的公司都十分关注保护自己的内部网络,以防黑客及入窃者。一种常见的安全措施是完全断开与因特网的连接。如果黑客们不能连接到您的任何一台机器,他们就不能非法进入您的系统。这种策略产生的不利副作用是,内部用户...写一个每秒接收 100 万数据包的程序究竟有多难? - 文章 - 伯乐在线
& 写一个每秒接收 100 万数据包的程序究竟有多难?
在上周的一次非正式谈话中,我偶然听同事说:“Linux 的网络栈太慢了!你别指望每秒在每个核上传输超过 5 万的数据包”。
这让我陷入了沉思,虽然对于任意的实际应用来说,每个核 5 万的速率可能是极限了,但 Linux 的网络栈究竟可能达到多少呢?我们换一种更有趣的方式来问:
在 Linux 上,编写一个每秒接收 100 万 UDP 数据包的程序究竟有多难?
我希望,通过对这个问题的解答,我们将获得关于如何设计现代网络栈很好的一课。
首先,我们假设:
测量每秒的数据包(pps)比测量每秒字节数(Bps)更有意思。您可以通过更好的管道输送以及发送更长数据包来获取更高的Bps。而相比之下,提高pps要困难得多。
因为我们对pps感兴趣,我们的实验将使用较短的 UDP 消息。准确来说是 32 字节的 UDP 负载,这相当于以太网层的 74 字节。
在实验中,我们将使用两个物理服务器:“接收器”和“发送器”。
它们都有两个六核2 GHz的 Xeon处理器。每个服务器都启用了 24 个处理器的超线程(HT),有 Solarflare 的 10G 多队列网卡,有 11 个接收队列配置。稍后将详细介绍。
测试程序的源代码分别是:、。
我们使用4321作为UDP数据包的端口,在开始之前,我们必须确保传输不会被iptables干扰:
receiver$ iptables -I INPUT 1 -p udp --dport 4321 -j ACCEPT
receiver$ iptables -t raw -I PREROUTING 1 -p udp --dport 4321 -j NOTRACK
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地址:
receiver$ for i in `seq 1 20`; do
ip addr add 192.168.254.$i/24 dev eth2;
sender$ ip addr add 192.168.254.30/24 dev eth3
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
简单的方法
开始我们做一些最简单的试验。通过简单地发送和接收,有多少包将会被传送?
模拟发送者的伪代码:
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)
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个数据包。
模拟接受者的伪代码:
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)
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 更有效的一版系统调用。
让我们试试吧:
sender$ ./udpsender 192.168.254.1:4321
receiver$ ./udpreceiver1 0.0.0.0:4321
0.352M pps
10.730MiB /
0.284M pps
8.655MiB /
0.262M pps
7.991MiB /
0.199M pps
6.081MiB /
0.195M pps
5.956MiB /
0.199M pps
6.060MiB /
0.200M pps
6.097MiB /
0.197M pps
6.021MiB /
12345678910
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 &#k pps。看起来还不错嘛,但不幸的是,很不稳定啊,这是因为内核在核之间交换我们的程序,那我们把进程附在 CPU 上将会有所帮助
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 /
0.374M pps
11.411MiB /
0.369M pps
11.252MiB /
0.370M pps
11.289MiB /
0.365M pps
11.152MiB /
0.360M pps
10.971MiB /
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上,这提高了处理器缓存,使数据更加一致,这就是我们想要的啊!
发送更多的数据包
虽然 370k pps 对于简单的程序来说已经很不错了,但是离我们 1Mpps 的目标还有些距离。为了接收更多,首先我们必须发送更多的包。那我们用独立的两个线程发送,如何呢:
sender$ taskset -c 1,2 ./udpsender
192.168.254.1:.254.1:4321
receiver$ taskset -c 1 ./udpreceiver1 0.0.0.0:4321
0.349M pps
10.651MiB /
0.354M pps
10.815MiB /
0.354M pps
10.806MiB /
0.354M pps
10.811MiB /
sender$ taskset -c 1,2 ./udpsender&& &&&&&&&&&&&&192.168.254.1:4321 192.168.254.1:4321receiver$ 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 命令将显示数据包实际上都去哪儿了:
receiver$ watch 'sudo ethtool -S eth2 |grep rx'
rx_nodesc_drop_cnt:
rx-0.rx_packets:
rx-1.rx_packets:
rx-2.rx_packets:
rx-3.rx_packets:
rx-4.rx_packets:
rx-5.rx_packets:
rx-6.rx_packets:
rx-7.rx_packets:
rx-8.rx_packets:
rx-9.rx_packets:
rx-10.rx_packets:
12345678910111213
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值可能是这样的:
RX_queue_number = hash('192.168.254.30', '192.168.254.1', 6) % number_of_queues
RX_queue_number = hash('192.168.254.30', '192.168.254.1', 65400, 4321) % number_of_queues
多队列 hash 算法
Hash算法通过ethtool配置,设置如下:
receiver$ ethtool -n eth2 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
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)地址。即
RX_queue_number = hash('192.168.254.30', '192.168.254.1') % number_of_queues
RX_queue_number = hash('192.168.254.30', '192.168.254.1') % number_of_queues
这是相当有限的,因为它忽略了端口号。很多NIC允许自定义hash。再一次,使用ethtool我们可以选择元组(源 IP、目标 IP、源port、目标port)生成hash值。
receiver$ ethtool -N eth2 rx-flow-hash udp4 sdfn
Cannot change RX network flow hashing options: Operation not supported
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:.254.2:4321
sender$ taskset -c 1,2 ./udpsender 192.168.254.1:4321 192.168.254.2:4321
ethtool 证实了数据包流向了不同的 RX 队列:
receiver$ watch 'sudo ethtool -S eth2 |grep rx'
rx-0.rx_packets:
rx-1.rx_packets:
rx-2.rx_packets:
rx-3.rx_packets:
rx-4.rx_packets:
rx-5.rx_packets:
rx-6.rx_packets:
rx-7.rx_packets:
rx-8.rx_packets:
rx-9.rx_packets:
rx-10.rx_packets:
123456789101112
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
接收部分:
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
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接收到了如下错误:
receiver$ watch 'netstat -s --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:
SndbufErrors: 0
InCsumErrors: 0
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.多线程接收
我们需要扩展接收者应用程序。最简单的方式是利用多线程接收,但是不管用:
sender$ taskset -c 1,2 ./udpsender 192.168.254.1:.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
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 了 —— (flag)。当这个标志位设置在一个套接字描述符上时,Linux将允许许多进程绑定到相同的端口,事实上,任何数量的进程将允许绑定上去,负载也会均衡分布。
有了SO_REUSEPORT,每一个进程都有一个独立的socket描述符。因此每一个都会拥有一个专用的UDP接收缓冲区。这样就避免了以前遇到的竞争问题:
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
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在技术上是可行的,但是应用程序将不会对收到的数据包做任何实际处理——甚至连看都不看内容的流量。别太指望这样的性能,因为对于任何实际应用并没有太大用处。
关于作者:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线}

我要回帖

更多关于 c 下socket编译选项 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信