當前位置:編程學習大全網 - 源碼下載 - Udp連接源代碼

Udp連接源代碼

首先,讓我們假設:

測量每秒數據包數(pps)比測量每秒字節數(Bps)更有趣。妳可以通過更好的管道傳輸和發送更長的數據包來獲得更高的Bps。相比之下,pps的提升就困難多了。

因為我們對pps感興趣,所以我們的實驗將使用較短的UDP消息。準確的說是32字節的UDP負載,相當於以太網層的74字節。

在實驗中,我們將使用兩個物理服務器:“接收者”和“發送者”。

它們都有兩個六核2 GHz至強處理器。每臺服務器都為24個處理器啟用了超線程(HT),采用Solarflare的10G多隊列網卡和11接收隊列配置。後面會詳細介紹。

測試程序的源代碼是:udpsender和udpreceiver。

基本原理

我們使用4321作為UDP數據包的端口。開始之前,我們必須確保傳輸不會受到iptables的幹擾:

接收器$ iptables -I輸入1 -p udp - dport 4321 -j接受

接收器$ iptables -t raw -I預路由1-p UDP-dport 4321-j no track

為了方便後面的測試,我們明確定義了IP地址:

“seq 1 20”中I的接收方$;做

ip地址add 192.168.254。$ I/24 dev eth 2;

完成的

發件人$ ip地址add 192.168.254 . 30/24 dev eth 3

1.簡單方法

起初,我們做了壹些簡單的實驗。單純的收發會傳輸多少個包?

模擬發送者的偽代碼:

計算機編程語言

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

fd.bind(("0.0.0.0 ",65400)) #選擇源端口以減少不確定性

FD . connect(((“192.168 . 254 . 1”,4321))

雖然正確:

FD . send mmsg([" x00 " * 32]* 1024)

因為我們用的是通用系統調用的send,效率不會很高。上下文切換到內核開銷很大,所以最好避免。幸運的是,Linux最近增加了壹個方便的系統調用sendmmsg。它允許我們在壹次通話中發送多個數據包。那我們就壹次發1024個包。

模擬接收者的偽代碼:

計算機編程語言

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

FD . bind(((" 0 . 0 . 0 . 0 ",4321))

雖然正確:

數據包=[無] * 1024

fd.recvmmsg(數據包,MSG_WAITFORONE)

類似地,recvmmsg是比普通recv更有效的系統調用版本。

讓我們來試試:

發送者美元。/UDP sender 192.168.254 . 1:4321

接收器美元。/UDP receiver 1 0 . 0 . 0 . 0:4321

0.352兆PPS 10.730兆字節/90.010兆字節

0.284兆pps 8.655MiB兆字節/72.603兆字節

0.262兆PPS 7.991兆字節/67.033兆字節

0.199兆PPS 6.081兆字節/51.01.03兆字節

0.195兆字節/49.966兆字節

0.199兆pps 6.060MiB兆字節/50.836兆字節

0.200兆pps 6.097MiB兆字節/51.147兆字節

0.197兆字節/50.509兆字節

測試表明,197K–350K PPS可以用最簡單的方式實現。看起來不錯,可惜很不穩定。這是因為內核在內核之間交換我們的程序,所以如果我們將進程附加到CPU上,將會有所幫助。

發件人$ taskset -c 1。/UDP sender 192.168.254 . 1:4321

接收器$ taskset -c 1。/UDP receiver 1 0 . 0 . 0 . 0:4321

0.362兆PPS 11.058兆字節/92.760兆字節

0.374m PPS 11.411 MIB/95.723 MB

0.369兆PPS 11.252兆字節/94.389兆字節

0.370兆PPS 11.289兆字節/94.696兆字節

0.365兆PPS 11.152兆字節/93.552兆字節

0.360兆PPS 10.971兆字節/92.033兆字節

現在內核調度器在特定的CPU上運行進程,提高了處理器緩存,使數據更加壹致,這就是我們想要的!

2.發送更多數據包

雖然370k的pps對於簡單的程序來說已經不錯了,但是距離我們1Mpps的目標還很遠。為了接收更多,我們必須首先發送更多的數據包。我們用兩個獨立的線程發送怎麽樣?

發件人$ taskset -c 1,2。/udpsender

192.168.254.1:4321 192.168.254.1:4321

接收器$ taskset -c 1。/UDP receiver 1 0 . 0 . 0 . 0:4321

0.349兆PPS 10.651兆字節/89.343兆字節

0.354兆PPS 10.815兆字節/90.724兆字節

0.354兆PPS 10.806兆字節/90.646兆字節

0.354兆PPS 10.811兆字節/90.690兆字節

接收端的數據沒有增加,ethtool–s命令將顯示數據包的實際去向:

receiver $ watch ' sudo ethtool-S eth 2 | grep rx '

rx _ nodesc _ drop _ CNT:451.3k/s

rx-0 . rx _ packets:8.0/秒

rx-1 . rx _ packets:0.0/秒

rx-2 . rx _ packets:0.0/秒

rx-3 . rx _ packets:0.5/秒

rx-4.rx_packets: 355.2k/s

rx-5 . rx _ packets:0.0/秒

rx-6 . rx _ packets:0.0/秒

rx-7 . rx _ packets:0.5/秒

rx-8 . rx _ packets:0.0/秒

rx-9 . rx _ packets:0.0/秒

rx-10 . rx _ packets:0.0/秒

通過這些統計,NIC顯示RX queue 4已經成功傳輸了大約350Kpps。Rx_nodesc_drop_cnt是Solarflare的唯壹計數器,表示NIC向內核發送450kpps失敗。

有時候,不發送這些包的原因不是很清楚,但是在我們的情況下,很清楚:RX隊列4號向CPU 4號發送包,但是CPU 4號太忙了,因為它最忙的時候只能讀取350kpps。在htop中顯示為:

多隊列網卡速成班

歷史上,網卡只有壹個RX隊列,用於在硬件和內核之間傳輸數據包。這種設計的壹個明顯的限制是不可能處理比單個CPU更多的數據包。

為了利用多核系統,網卡開始支持多個接收隊列。這種設計很簡單:每個RX隊列都連接到壹個單獨的CPU,因此將數據包發送到所有RX隊列的網卡可以使用所有的CPU。但另壹個問題出現了:NIC如何決定將數據包發送到哪個RX隊列?

循環平衡是不可接受的,因為它可能會導致單個連接中的數據包重新排序。另壹種方法是使用數據包的哈希值來確定RX號碼。哈希值通常由壹個元組(源IP、目的IP、源端口、目的端口)計算得出。這確保了從壹個流中生成的分組將在完全相同的RX隊列中結束,並且不可能在壹個流中重新排列分組。

在我們的示例中,哈希值可能如下所示:

1

RX _ queue _ number = hash(' 192.168.254 . 30 ',' 192.168.254 . 1 ',65400,4321) %隊列數

多隊列哈希算法

哈希算法由ethtool配置,設置如下:

接收器$ ethtool -n eth2 rx-flow-hash udp4

IPV4上的UDP流使用這些字段來計算哈希流密鑰:

IP SA

IP DA

對於IPv4 UDP數據包,NIC將散列(源IP,目標IP)地址。也就是

1

RX _ queue _ number = hash(' 192.168.254 . 30 ',' 192.168.254 . 1 ')% number _ of _ queues

這是非常有限的,因為它忽略了端口號。許多網卡允許自定義哈希。同樣,使用ethtool,我們可以選擇元組(源IP、目的IP、源端口和目的端口)來生成哈希值。

接收器$ eth tool-N eth 2 rx-flow-hash UDP 4 sdfn

無法更改RX網絡流哈希選項:不支持該操作

可惜我們的NIC不支持定制,只能選擇(源IP,目的IP)生成hash。

NUMA業績報告

到目前為止,我們所有的數據包都流向壹個RX隊列和壹個CPU。我們可以以此為基準來衡量不同CPU的性能。在我們設置為接收器的主機上有兩個獨立的處理器,每個處理器都是壹個不同的NUMA節點。

在我們的設置中,您可以將單線程接收器連接到四個CPU中的壹個。這四個選項如下:

接收器運行在另壹個CPU上,但是同壹個NUMA節點被用作RX隊列。從上面我們可以看到,性能大約是360 kpps。

以運行接收方的同壹個CPU作為RX隊列,我們可以得到大約430 kpps。但是,這樣也會有很高的不穩定性。如果網卡被數據包淹沒,性能將下降到零。

當接收器運行在處理RX隊列的HT對應的CPU上時,性能是平時的壹半,大約200kpps。

接收器位於不同的NUMA節點上,而不是RX隊列的CPU上,其性能約為330 kpps。但數字會有所不同。

雖然在不同的NUMA節點上運行要花費10%,聽起來可能不算太糟糕,但是隨著規模越來越大,問題只會越來越嚴重。在壹些測試中,每個內核只能發出250 kpps,在所有跨NUMA測試中,這種不穩定性非常糟糕。在吞吐量較高的情況下,NUMA節點的性能損失更加明顯。在壹次測試中,發現當接收器在斷開的NUMA節點上運行時,其性能降低了4倍。

3.接收更多IP。

由於我們的網卡上哈希算法的限制,通過RX隊列分發數據包的唯壹方法是使用多個IP地址。以下是如何將數據包發送到不同的目的IP:

1

發件人$ taskset -c 1,2。/UDP sender 192.168.254 . 1:4321 192.168.254 . 2:4321

Ethtool確認數據包流向不同的接收隊列:

receiver $ watch ' sudo ethtool-S eth 2 | grep rx '

rx-0 . rx _ packets:8.0/秒

rx-1 . rx _ packets:0.0/秒

rx-2 . rx _ packets:0.0/秒

rx-3.rx_packets: 355.2k/s

rx-4 . rx _ packets:0.5/秒

rx-5.rx_packets: 297.0k/s

rx-6 . rx _ packets:0.0/秒

rx-7 . rx _ packets:0.5/秒

rx-8 . rx _ packets:0.0/秒

rx-9 . rx _ packets:0.0/秒

rx-10 . rx _ packets:0.0/秒

接收部分:

接收器$ taskset -c 1。/UDP receiver 1 0 . 0 . 0 . 0:4321

0.609兆PPS 18.599兆字節/156.019兆字節

0.657兆pps 20.039MiB兆字節/168.102兆字節

0.649兆PPS 19.803兆字節/166.120兆字節

萬歲!有兩個核心忙於處理RX隊列,第三個在運行應用程序時可以達到650 kpps左右!

我們可以通過向三個或四個RX隊列發送數據來增加這個數字,但很快這個應用程序就會出現另壹個瓶頸。Rx_nodesc_drop_cnt這次沒有增加,但是netstat收到了以下錯誤:

接收器$ watch 'netstat -s - udp '

Udp:

收到437.0k/s數據包

收到0.0/s到未知端口的數據包。

386.9k/s數據包接收錯誤

每秒發送0.0個數據包

RcvbufErrors: 123.8k/s

SndbufErrors: 0

消費者錯誤數:0

這意味著盡管NIC可以向內核發送數據包,但內核不能向應用程序發送數據包。在我們的例子中,只能提供440 kpps,剩下的390 kpps+123 kpps會減少,因為應用程序接收它們的速度不夠快。

4.多線程接收

我們需要擴展接收器應用程序。最簡單的方法是使用多線程接收,但它不起作用:

發件人$ taskset -c 1,2。/UDP sender 192.168.254 . 1:4321 192.168.254 . 2:4321

接收器$ taskset -c 1,2。/udpreceiver 1 0 . 0 . 0:4321 2

0.495兆PPS 15.108兆字節/126.733兆字節

0.480兆PPS 14.636兆字節/122.775兆字節

0.461M PPS 14.071 MIB/118.038 MB

0.486兆PPS 14.820兆字節/124.322兆字節

接收性能低於單線程,這是由UDP接收緩沖區上的鎖競爭造成的。因為兩個線程使用相同的套接字描述符,所以它們花費太多時間來競爭UDP接收緩沖區的鎖。本文對此問題進行了詳細描述。

似乎使用多線程從描述符接收不是最佳解決方案。

5.SO_REUSEPORT

幸運的是,Linux最近增加了壹個解決方案——so _ reuse端口標誌。當在套接字描述符上設置這個標誌位時,Linux將允許許多進程綁定到同壹個端口。事實上,將允許綁定任意數量的進程,並且負載將均勻分布。

使用SO_REUSEPORT,每個進程都有壹個獨立的套接字描述符。因此每個都有壹個專用的UDP接收緩沖區。這就避免了以前遇到的競爭問題:

1

2

接收器$ taskset -c 1,2,3,4。/UDP receiver 1 0 . 0 . 0 . 0:4321 4 1

1.114M PPS 34.007 MIB/285.271Mb

1.147兆字節/293.518兆字節

1.126兆pps 34.374MiB兆字節/288.354兆字節

現在更喜歡了,吞吐量很好!

更多的調查顯示,還有進壹步改善的空間。即使我們啟動四個接收線程,負載也不會均勻分布:

兩個進程接收所有工作,而另外兩個進程根本沒有數據包。這是因為哈希沖突,但這次是在SO_REUSEPORT層。

結束語

我做了進壹步的測試。使用完全壹致的RX隊列,接收線程在單個NUMA節點上可以達到1.4Mpps。在不同的NUMA節點上運行接收器將導致這個數字下降到1Mpps。

簡而言之,如果妳想要完美的表現,妳需要做到以下幾點:

確保流量均勻分布在許多RX隊列和SO_REUSEPORT進程上。在實踐中,只要有很多連接(或流),負載通常是分布的。

需要有足夠的CPU能力從內核獲取數據包。

讓事情變得更困難的是,接收隊列和接收方進程應該在同壹個NUMA節點上。

為了使事情更加穩定,RX隊列和接收過程應該在同壹個NUMA節點上。

盡管我們已經證明了在Linux機器上接收1Mpps在技術上是可行的,但是應用程序不會對接收到的數據包進行任何實際的處理——甚至不會查看內容流量。不要對這樣的性能期望太高,因為對於任何實際應用來說都不是很有用。

  • 上一篇:香港有哪間第三方支付公司同時提供收款和推廣平臺?
  • 下一篇:誰能告訴我《今古傳奇·奇幻》2008年1A到現在的所有期刊目錄啊!!!!萬分感激
  • copyright 2024編程學習大全網