本系列轉(zhuǎn)自陶輝大牛的博客。
高性能網(wǎng)絡編程(一)----accept建立連接
高性能網(wǎng)絡編程2----TCP消息的發(fā)送
- SO_SNDTIMEO
發(fā)送超時時間,可以簡單的認為是把用戶態(tài)數(shù)據(jù)copy到TCP發(fā)送緩沖區(qū)的超時時間。
JVM中該參數(shù)就是0。
3. 高性能網(wǎng)絡編程3----TCP消息的接收
3.1 四種隊列
為了功能區(qū)分和減小并發(fā)加鎖競爭,所以組織了多種隊列
1.1 receive(數(shù)據(jù)是已去除TCP協(xié)議之后的可以直接被copy到用戶態(tài)的數(shù)據(jù))
1.2 out-of-order
1.3 prequeue
1.4 backlog(socket被加鎖時放入)
3.2 prequeue與tcp_low_latency
當有socket正在睡眠以等待更多的數(shù)據(jù)時,新到的包根據(jù)tcp_low_latency的配置可能到prequeue隊列,也可能到receive或者out-of-order隊列。
tcp_low_latency默認為0,即關閉,此時新到的包進入tcp_low_latency
3.2.1 prequeue
-
tcp_low_latency打開時
在TCP中斷時需要處理ACK響應等TCP協(xié)議,去除TCP頭等信息(放入receive隊列的需要去除),甚至還可能把數(shù)據(jù)直接copy到用戶態(tài)buffer。
所以,這種模式下用戶進程能快速的得到數(shù)據(jù),但是軟中斷的時間長,造成TCP吞吐量下降。 -
tcp_low_latency關閉時
與普通機制的主要區(qū)別在于,在進程沒有收取到足夠的數(shù)據(jù)而睡眠等待時,prequeue機制會將skb放入prequeue隊列中再喚醒進程,再由進程對skb進行TCP協(xié)議處理,再copy數(shù)據(jù);而普通模式下skb會在軟中斷上下文處理,在放入sk->sk_receive_queue隊列中后再喚醒進程,進程被喚醒后只是copy數(shù)據(jù)。對比普通模式,prequeue機制下使得skb的TCP協(xié)議處理延遲,延遲的時間為從skb被放入prequeue隊列并喚醒進程開始,到進程被調(diào)度到時調(diào)用tcp_prequeue_process函數(shù)處理skb時截止。對于收數(shù)據(jù)的進程而言在一次數(shù)據(jù)接收過程中其實并沒有延遲,因為普通模式下進程也會經(jīng)歷睡眠-喚醒的過程。但由于TCP協(xié)議處理被延遲,導致ACK的發(fā)送延遲,從而使數(shù)據(jù)發(fā)送端的數(shù)據(jù)發(fā)送延遲,最終會使得整個通信過程延遲增大?,F(xiàn)在我們知道prequeue機制延遲大的原因了:skb的TCP協(xié)議處理不是在軟中斷中進行,而是推遲到應用進程調(diào)用收包系統(tǒng)調(diào)用時。
3.2.2 為什么有用戶進程因等待數(shù)據(jù)睡眠時才有tcp_low_latency機制
因為有進程在等待,所以可以讓新到的包的TCP協(xié)議完成由等待的進程完成。
3.3 一些參數(shù)
-
SO_RCVTIMEO
JAVA不不支持設置該參數(shù),該參數(shù)JVM設的為0 -
SO_RCVLOWAT
《UNIX網(wǎng)絡編程中》描述該參數(shù)用于select/epoll,和本位描述不符。 - TP_LOW_LATENCY
4. 網(wǎng)絡編程4--TCP連接的關閉
4.1 監(jiān)聽句柄的關閉
半連接直接發(fā)RST
4.2 關閉ESTABLISH狀態(tài)的連接
- 如果還有數(shù)據(jù)未讀取
發(fā)RST - 如果還有待發(fā)送的數(shù)據(jù)
發(fā)送,在最后一個報文加上FIN - so_linger
so_linger是close(無論socket是否工作在阻塞模式,都是阻塞的)的超時時間,用來盡量保證對方收到了close時發(fā)出的消息,即,至少需要對方通過發(fā)送ACK且到達本機。
5. 高性能網(wǎng)絡編程5--IO復用與并發(fā)編程
- select和epoll
select每次調(diào)用都需要把所有欲監(jiān)控的socket傳入內(nèi)核態(tài) - epoll提供的2種玩法ET和LT
LT是每次滿足期待狀態(tài)的連接,都得在epoll_wait中返回,所以它一視同仁,都在一條水平線上。ET則不然,它傾向更精確的返回連接。在上面的例子中,連接第一次變?yōu)榭蓪懞?,若是程序未向連接上寫入任何數(shù)據(jù),那么下一次epoll_wait是不會返回這個連接的。ET叫做 邊緣觸發(fā),就是指,只有連接從一個狀態(tài)轉(zhuǎn)到另一個狀態(tài)時,才會觸發(fā)epoll_wait返回它??梢姡珽T的編程要復雜不少,至少應用程序要小心的防止epoll_wait的返回的連接出現(xiàn):可寫時未寫數(shù)據(jù)后卻期待下一次“可寫”、可讀時未讀盡數(shù)據(jù)卻期待下一次“可讀”。
6. 高性能網(wǎng)絡編程6--reactor反應堆與定時器管理
7. 高性能網(wǎng)絡編程7--tcp連接的內(nèi)存使用
net.ipv4.tcp_rmem = 8192 87380 16777216
net.ipv4.tcp_wmem = 8192 65536 16777216
net.ipv4.tcp_mem = 8388608 12582912 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
7.1 net.ipv4.tcp_adv_win_scale = 2
讀取緩沖包含兩部分:
- 于應用程序的延時報文讀取
- 接收窗口
tcp_adv_win_scale意味著,將要拿出1/(2^tcp_adv_win_scale)緩存出來做應用緩存。即,默認tcp_adv_win_scale配置為2時,就是拿出至少1/4的內(nèi)存用于應用讀緩存,那么,最大的接收滑動窗口的大小只能到達讀緩存的3/4。
7.2 初始的擁塞窗口
以廣為使用的linux2.6.18內(nèi)核為例,在以太網(wǎng)里,MSS大小為1460,此時初始窗口大小為4倍的MSS。有些網(wǎng)絡中,會在TCP的可選頭部里,使用12字節(jié)作為時間戳使用,這樣,有效數(shù)據(jù)就是MSS再減去12,初始窗口就是(1460-12)4=5792*,這與窗口想表達的含義是一致的,即:我能夠處理的有效數(shù)據(jù)長度。
在linux3以后的版本中,初始窗口調(diào)整到了10個MSS大小,這主要來自于GOOGLE的建議。原因是這樣的,接收窗口雖然常以指數(shù)方式來快速增加窗口大?。〒砣y值以下是指數(shù)增長的,閥值以上進入擁塞避免階段則為線性增長,而且,擁塞閥值自身在收到128以上數(shù)據(jù)報文時也有機會快速增加),若是傳輸視頻這樣的大數(shù)據(jù),那么隨著窗口增加到(接近)最大讀緩存后,就會“開足馬力”傳輸數(shù)據(jù),但若是通常都是幾十KB的網(wǎng)頁,那么過小的初始窗口還沒有增加到合適的窗口時,連接就結束了。這樣相比較大的初始窗口,就使得用戶需要更多的時間(RTT)才能傳輸完數(shù)據(jù),體驗不好。
7.3 接收窗口應該設置多大?

所以:接收buffer大小=BDP*4/3
7.4 內(nèi)存
7.4.1 TCP緩存上限自動調(diào)整策略關閉
- SO_SNDBUF和SO_RCVBUF
應用程序可以為某個連接設置的參數(shù),分別代表寫緩沖和讀緩沖的最大值。
在內(nèi)核中會把這個值翻一倍再作為寫緩存上限使用。 - net.core.wmem_max和net.core.rmem_max
操作系統(tǒng)級別的定義的參數(shù)。當SO_SNDBUF和SO_RCVBUF大于系統(tǒng)級的參數(shù)時,以系統(tǒng)級的為準。
在內(nèi)核中也會把這個值翻一倍再作為寫緩存上限使用。 - net.core.rmem_default和net.core.wmem_default
定義了讀寫緩沖區(qū)大小的默認值
7.4.2 TCP緩存上限自動調(diào)整策略
在并發(fā)連接比較少時,把緩存限制放大一些,讓每一個TCP連接開足馬力工作;當并發(fā)連接很多時,此時系統(tǒng)內(nèi)存資源不足,那么就把緩存限制縮小一些,使每一個TCP連接的緩存盡量的小一些,以容納更多的連接。
net.ipv4.tcp_moderate_rcvbuf = 1
默認tcp_moderate_rcvbuf配置為1,表示打開了TCP內(nèi)存自動調(diào)整功能。若配置為0,這個功能將不會生效(慎用)。