1. 程式人生 > >淺談Linux環境下Socket選項的設定

淺談Linux環境下Socket選項的設定

0.前言

TCP/IP協議棧是Linux核心的重要組成部分和網路程式設計的基石,雖然Linux和BSD有很大的聯絡,但是對於某些Socket選項和核心操作仍然存在差異,因此文中適用場景均為CentOS環境。

《UNIX網路程式設計》是已故UNIX網路專家W. Richard Stevens博士(1951-1999)、世界著名網路專家Bill Fenner和Andrew M. Rudoff完成的權威著作,該書對網路程式設計進行全面而深入的闡述,是提高網路程式設計功力的不二之選。

通過本文將瞭解到以下內容:

1.socket層和TCP/IP協議的區別和聯絡

2.操作socket的基礎API和簡單實用方法

3.常用Socket選項舉例

 

1.Socket和TCP/IP的關係

"All problems in computer science can be solved by another level of indirection."

為滿足應用層需求,系統對TCP/IP層進行細節遮蔽和抽象,Socket層就相當於TCP/IP和應用層之間的中間層。

常用的socket/bind/accept/connect就是抽象出來的介面,使用它們可以快速進行網路程式開發,可見Socket中間層的重要性,Socket選項就是為滿足使用者的定製化需求而生的。我們經常遇到的情況包括地址複用、埠複用、讀寫超時時間、讀寫緩衝區大小等。

在Linux的TCP/IP協議棧中包括很多Socket選項,它們會出現在TCP層、IP層、Socket層等,為此在讀取和設定socket選項時需要指定level。

如圖可以看到Socket層作為中間層以及各層支援的部分Socket選項:

注:可通過man 7 tcp/man 7 ip檢視tcp/ip各層Socket選項詳細定義和新增核心版本等資訊。

2.操作Socket選項的API

讀取和設定Socket選項的API包括:getsockopt、setsockopt、fcntl、ioctl等;

其中fcntl和ioctl用來設定socket的阻塞和非阻塞狀態。

通過man獲得的函式定義:

/ioctl函式定義
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);

//fcntl函式定義
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

//get/setsockopt函式定義
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

 

3.get/setsockopt使用說明

使用時需要按照函式要求的形參格式進行傳遞,顯式指明其所在的level以及選項名稱optname、optval型別和長度optlen。

  • level引數說明

從sys/socket.h的原始碼中可以看到對於level的說明如下:

/* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */
#define SOL_IP    0
#define SOL_IPX    256
#define SOL_AX25  257
#define SOL_ATALK  258
#define  SOL_NETROM  259
#define SOL_TCP    6
#define SOL_UDP    17
#define SOL_SOCKET  0xffff
  • optval和optlen引數說明

optval和optlen均為指標型別,這兩個引數與當前操作的option有直接關係,可以看到optval使用void*型別,optlen使用socklen_t*型別。

socklen_t型別說明:socklen_t和int應該具有相同的長度,否則會破壞 BSD套接字層的填充,POSIX開始時候用的是size_t。

吃瓜時間:Linus Torvalds 向他們解釋使用size_t是完全錯誤的,因為在64位結構中 size_t和int的長度是不一樣的,而這個引數的長度必須和int一致,最終POSIX的那幫傢伙找到了解決的辦法,創造了 一個新的型別socklen_t。

Linux Torvalds說這是由於他們發現了自己的錯誤但又不好意思承認,所以另外創造了一個新的資料型別。

指標使用:optval和optlen兩個指標型別是缺一不可的,optval為void*型別如果沒有長度說明,系統函式在呼叫時就無法獲取邊界,optlen為底層呼叫指明記憶體起始地址對應的偏移量,這是C中常用的指標操作模式。

Socket選項多是int和bool型別 但是也有一些複合型別比如linger,因此在讀寫選項是對於optval和optlen的編寫要根據實際而定。

 

4. SO_REUSEADDR選項

典型場景:

在《Unix網路程式設計》卷一中指出了SO_REUSEADDR的重要使用場景:當有一個有相同本地地址和埠的socket1處於TIME_WAIT狀態時,而你啟動的程式的socket2要佔用該地址和埠,你的程式就要用到該選項。

TIME_WAIT:如何優雅關閉Socket是個值得思考的問題, TIME_WAIT狀態是TCP協議為了保證全雙工連線可靠性設定的,感興趣可以查閱TIME_WAIT的作用,並不要一味的談TIME_WAIT色變,這裡就不展開了。

設定方法:

未設定SO_REUSEADDR,在重啟時就會繫結失敗顯示資源被佔用,需要等待該IP+Port被釋放才可以重啟成功,該問題對於線上服務不可接受。因此需要將服務端的socket設定為地址複用:

int enable = 1;
setsocketopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(void*)&enable,sizeof(enable));

5. SO_REUSEPORT選項

作用效果:

埠複用選項SO_REUSEPORT是在SO_REUSEADDR之後於Linux3.9版本加入的,並不是所有系統都支援該選項。SO_REUSEPORT允許多個程序監聽相同的IP和Port,但是為了防止埠劫持增加了對程序所屬使用者的限制。

核心支援:

埠複用選項是個非常大的進步,有利於服務端程式擴充套件、提高併發能力。值得一提的是SO_REUSEPORT在核心層面實現了簡單的負載均衡,為監聽的多個程序進行流量分發。Nginx應用:Nginx的1.9.1版本引入了SO_REUSEPORT套接字選項,對於Nginx而言,啟用該選項可以減少在某些場景下的鎖競爭而改善效能。Linux 3.9版本和Nginx1.9.1版本(含)之後的版本,Nginx已經無需再使用互斥鎖ngx_use_accept_mutex,引入SO_REUSEPORT選項由核心層面實現負責均衡來解決驚群問題。

6. TCP_NODELAY選項

簡單背景:

為解決福特公司區域網擁塞問題,Nagle演算法由福特公司的John Nagle 在1984年提出。同時代的其他網路也存在這種情況,因此Naggle演算法被引入到協議棧。

演算法原則:

儘可能傳送大塊資料,避免網路中充斥著許多小資料塊,任意時刻最多隻能有一個未被確認的小段。未被確認是指一個數據塊傳送出去後,沒有收到對方傳送的ACK確認。

通俗解釋:

就是在兩座城市的高速路上之前充斥著非常多的貨車,貨車的車廂中可能是一根羽毛、一個玩具熊或者一臺機器等,造成了高速路的擁堵。為此要求每次最多隻有一輛未被授權的貨車行駛且每個貨車裝載儘可能多的東西,從而提高單次運輸效率和降低貨車數量,緩解高速路的擁堵。

演算法弊端:

上世紀80年代網路頻寬有限,Nagle演算法有效改善了網路擁塞情況,但是隨著網路頻寬的增加和通訊基礎設施水平的提高,最多隻能有一個未被確認的小段的限制導致了無意義的等待,無法有效利用當前的網路頻寬。

演算法禁用:

TCP_NODELAY可以解決Nagle演算法帶來的問題,開啟TCP_NODELAY意味著允許小包的傳送且不強制等待,對時效高且資料量小的應用非常實用。從應用程式的角度來說應該儘量避免寫小包,從而實現資料包大小和資料包數量的效率最大化。

 

7. 小結

在瞭解了Socket作為TCP/IP層和應用層在網路程式設計領域的中間層之後,進一步明確讀寫套接字選項的函式,以及常見的套接字選項的設定方法以及設定原因,從而對整個套接字選項有一個基本認識。

套接字選項本身很多,但是我們常用的並不多,需要根據自己的實際情況和該選項的作用來進行調整,不理解背後機理的調整多半會留