? ? ? ? 最近在新的平臺上測試程序,以前一些沒有注意到的問題都成為了性能瓶頸,通過設置一些TCP/IP選項能夠解決一部分問題,當然根本的解決方法是重構代碼,重新設計服務器框架。先列出幾個TCP/IP選項:
選項
man 7 socket:
SO_REUSEADDR
SO_RECVBUF/SO_SNDBUF
SO_KEEPALIVE
SO_LINGER
man 7 tcp:
TCP_CORK
TCP_NODELAY
TCP_DEFER_ACCEPT
TCP_KEEPCNT/TCP_KEEPIDLE/TCP_KEEPINTVL
SO_REUSEADDR
man 命令的?領域?名稱 說明
1 用戶命令, 可由任何人啟動的。
2 系統調用, 即由內核提供的函數。
3 例程, 即庫函數。
4 設備, 即/dev目錄下的特殊文件。
5 文件格式描述, 例如/etc/passwd。
6 游戲, 不用解釋啦!
7 雜項, 例如宏命令包、慣例等。
8 系統管理員工具, 只能由root啟動。
9 其他(Linux特定的), 用來存放內核例行程序的文檔。
SO_REUSEADDR選項:
在服務器程序中,SO_REUSEADDR socket選項通常在調用bind()之前被設置。
SO_REUSEADDR可以用在以下四種情況下:?
(摘自《Unix網絡編程》卷一,即UNPv1)
1、當有一個有相同本地地址和端口的socket1處于TIME_WAIT狀態時,而你啟動的程序的socket2要占用該地址和端口,你的程序就要用到該選項。?
2、SO_REUSEADDR允許同一port上啟動同一服務器的多個實例(多個進程)。但每個實例綁定的IP地址是不能相同的。在有多塊網卡或用IP Alias技術的機器可以測試這種情況。?
3、SO_REUSEADDR允許單個進程綁定相同的端口到多個socket上,但每個socket綁定的ip地址不同。這和2很相似,區別請看UNPv1。?
4、SO_REUSEADDR允許完全相同的地址和端口的重復綁定。但這只用于UDP的多播,不用于TCP。
TCP_NODELAY/TCP_CHORK選項:
TCP_NODELAY/TCP_CHORK
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在這里的含義是采用Nagle算法把較小的包組裝為更大的幀。TCP_NODELAY和TCP_CORK都禁掉了Nagle算法,只不過他們的行為不同而已。
TCP_NODELAY 不使用Nagle算法,不會將小包進行拼接成大包再進行發送,直接將小包發送出去,會使得小包時候用戶體驗非常好。
Nagle算法參見自己的博客:http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201231214038740/
當在傳送大量數據的時候,為了提高TCP發送效率,可以設置TCP_CORK,CORK顧名思義,就是"塞子"的意思,它會盡量在每次發送最大的數據量。當設置了TCP_CORK后,會有阻塞200ms,當阻塞時間過后,數據就會自動傳送。
詳細的資料可以查看參考文獻5。
SO_LINGER選項:
SO_LINGER
linger,顧名思義是延遲延緩的意思,這里是延緩面向連接的socket的close操作。
默認,close立即返回,但是當發送緩沖區中還有一部分數據的時候,系統將會嘗試將數據發送給對端。SO_LINGER可以改變close的行為。
控制SO_LINGER通過下面一個結構:
struct linger
{
????? int l_onoff; /*0=off, nonzero=on*/
????? int l_linger; /*linger time, POSIX specifies units as seconds*/
};
通過結構體中成員的不同賦值,可以表現為下面幾種情況:
1. l_onoff設置為0,選項被關閉。l_linger值被忽略,就是上面的默認情形,close立即返回。
2. l_onoff設置為非0,l_linger被設置為0,則close()不被阻塞立即執行,丟棄socket發送緩沖區中的數據,并向對端發送一個RST報文。
????這種關閉方式稱為“強制”或“失效”關閉。
3. l_onoff設置為非0,l_linger被設置為非0,則close()調用阻塞進程,直到所剩數據發送完畢或超時。
????這種關閉稱為“優雅的”關閉。
注意:
?????? 這個選項需要謹慎使用,尤其是強制式關閉,會丟失服務器發給客戶端的最后一部分數據。UNP中:
The TIME_WAIT state is our friend and is there to help us(i.e., to let the old duplicate segments expire in the network).
?
TCP_DEFER_ACCEPT選項:
TCP_DEFER_ACCEPT
defer accept,從字面上理解是推遲accept,實際上是當接收到第一個數據之后,才會創建連接,三次握手完成,連接還沒有建立。
對于像HTTP等非交互式的服務器,這個很有意義,可以用來防御空連接攻擊(只是建立連接,但是不發送任何數據)。
使用方法如下:
val = 5; setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)) ; |
里面 val 的單位是秒,注意如果打開這個功能,kernel 在 val 秒之內還沒有收到數據,不會繼續喚醒進程,而是直接丟棄連接。如果服務器設置TCP_DEFER_ACCEPT選項后,服務器受到一個CONNECT請求后,三次握手之后,新的socket狀態依然為SYN_RECV,而不是ESTABLISHED,操作系統不會Accept。
由于設置TCP_DEFER_ACCEPT選項之后,三次握手后狀態沒有達到ESTABLISHED,而是SYN_RECV。這個時候,如果客戶端一直沒有發送"數據"報文,服務器將重傳SYN/ACK報文,重傳次數受net.ipv4.tcp_synack_retries參數控制,達到重傳次數之后,才會再次進行setsockopt中設置的超時值,因此會出現SYN_RECV生存時間比設置值大一些的情況。
關于SYN_RECV狀態可以查看參考文獻7。
?
SO_KEEPALIVE選項:
SO_KEEPALIVE/TCP_KEEPCNT/TCP_KEEPIDLE/TCP_KEEPINTVL
??????? 如果一方已經關閉或異常終止連接,而另一方卻不知道,我們將這樣的TCP連接稱為半打開的。
??????? TCP通過保活定時器(KeepAlive)來檢測半打開連接。
???????? 在高并發的網絡服務器中,經常會出現漏掉socket的情況,對應的結果有一種情況就是出現大量的CLOSE_WAIT狀態的連接。這個時候,可以通過設置KEEPALIVE選項來解決這個問題,當然還有其他的方法可以解決這個問題,詳細的情況可以查看參考資料8。
使用方法如下:
//Setting For KeepAlive int keepalive = 1; setsockopt(incomingsock,SOL_SOCKET,SO_KEEPALIVE,(void*)(&keepalive),(socklen_t)sizeof(keepalive)); int keepalive_time = 30; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPIDLE,(void*)(&keepalive_time),(socklen_t)sizeof(keepalive_time)); int keepalive_intvl = 3; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPINTVL,(void*)(&keepalive_intvl),(socklen_t)sizeof(keepalive_intvl)); int keepalive_probes= 3; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPCNT,(void*)(&keepalive_probes),(socklen_t)sizeof(keepalive_probes)); |
?????? 設置SO_KEEPALIVE選項來開啟KEEPALIVE,然后通過TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT設置keepalive的開始時間、間隔、次數等參數。
??????? 當然,也可以通過設置/proc/sys/net/ipv4/tcp_keepalive_time、tcp_keepalive_intvl和tcp_keepalive_probes等內核參數來達到目的,但是這樣的話,會影響所有的socket,因此建議使用setsockopt設置