討論 Setsockopt 選項

有時候我們要控制套接字的行爲(如修改緩衝區的大小),這個時候我們就要控制套接字的選項了.  

  

getsockopt 和 setsockopt 

獲得套接口選項: 

 int getsockopt ( int sockfd, int level, int optname, void * optval, socklen_t *opteln ) 設置套接口選項: 

 int setsockopt ( int sockfd, int level, int optname, const void * optval, socklen_t *opteln ) 

 sockfd(套接字): 指向一個打開的套接口描述字 

 level:(級別): 指定選項代碼的類型。 

SOL_SOCKET: 基本套接口  

IPPROTO_IP: IPv4套接口  

IPPROTO_IPV6: IPv6套接口  

IPPROTO_TCP: TCP套接口  

optname(選項名): 選項名稱  

optval(選項值): 是一個指向變量的指針 類型:整形,套接口結構, 其他結構類型:linger{}, timeval{ }  

optlen(選項長度optval 的大小  

返回值:標誌打開或關閉某個特徵的二進制選項 

以下說明方法爲 選項名稱 簡要說明 數據類型  補充

===========  

SOL_SOCKET  

------------------------------------------------------------------------  

SO_BROADCAST 允許發送廣播數據 int  適用於 UDP socket。其意義是允許 UDP socket 「廣播」(broadcast)訊息到網路上。 

SO_DEBUG 允許調試 int  

SO_DONTROUTE 不查找路由 int  

SO_ERROR 獲得套接字錯誤 int  

SO_KEEPALIVE 保持連接 int  

檢測對方主機是否崩潰,避免(服務器)永遠阻塞於TCP連接的輸入。 

設置該選項後,如果2小時內在此套接口的任一方向都沒有數據交換,TCP就自動給對方 發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會導致以下三種情況: 對方接收一切正常:以期望的ACK響應。2小時後,TCP將發出另一個探測分節。 對方已崩潰且已重新啓動:以RST響應。套接口的待處理錯誤被置爲ECONNRESET,套接 口本身則被關閉。 對方無任何響應:源自berkeleyTCP發送另外8個探測分節,相隔75秒一個,試圖得到 一個響應。在發出第一個探測分節11分鐘15秒後若仍無響應就放棄。套接口的待處理錯 誤被置爲ETIMEOUT,套接口本身則被關閉。如ICMP錯誤是“host unreachable(主機不 可達)”,說明對方主機並沒有崩潰,但是不可達,這種情況下待處理錯誤被置爲 EHOSTUNREACH。  

SO_DONTLINGER 若爲真,則SO_LINGER選項被禁止。 

SO_LINGER 延遲關閉連接 struct linger  

上面這兩個選項影響close行爲 

選項 間隔 關閉方式 等待關閉與否 

SO_DONTLINGER 不關心 優雅 否 

SO_LINGER 零 強制 否 

SO_LINGER 非零 優雅 是 

若設置了SO_LINGER(亦即linger結構中的l_onoff域設爲非零,參見2.44.1.74.1.21各節),並設置了零超時間隔,則closesocket()不被阻塞立即執行,不論是否有排隊數據未發送或未被確認。這種關閉方式稱爲“強制”或“失效”關閉,因爲套接口的虛電路立即被複位,且丟失了未發送的數據。在遠端的recv()調用將以WSAECONNRESET出錯。 

若設置了SO_LINGER並確定了非零的超時間隔,則closesocket()調用阻塞進程,直到所剩數據發送完畢或超時。這種關閉稱爲“優雅的”關閉。請注意如果套接口置爲非阻塞且SO_LINGER設爲非零超時,則closesocket()調用將以WSAEWOULDBLOCK錯誤返回。 

若在一個流類套接口上設置了SO_DONTLINGER(也就是說將linger結構的l_onoff域設爲零;參見2.44.1.74.1.21節),則closesocket()調用立即返回。但是,如果可能,排隊的數據將在套接口關閉前發送。請注意,在這種情況下WINDOWS套接口實現將在一段不確定的時間內保留套接口以及其他資源,這對於想用所以套接口的應用程序來說有一定影響。 

SO_OOBINLINE 帶外數據放入正常數據流,在普通數據流中接收帶外數據 int  

SO_RCVBUF 接收緩衝區大小 int  設置接收緩衝區的保留大小 

與 SO_MAX_MSG_SIZE TCP滑動窗口無關,如果一般發送的包很大很頻繁,那麼使用這個選項 

SO_SNDBUF 發送緩衝區大小 int  設置發送緩衝區的保留大小 

與 SO_MAX_MSG_SIZE TCP滑動窗口無關,如果一般發送的包很大很頻繁,那麼使用這個選項 

每個套接口都有一個發送緩衝區和一個接收緩衝區。 接收緩衝區被TCPUDP用來將接收到的數據一直保存到由應用進程來讀。 TCPTCP通告另一端的窗口大小。 TCP套接口接收緩衝區不可能溢出,因爲對方不允許發出超過所通告窗口大小的數據。 這就是TCP的流量控制,如果對方無視窗口大小而發出了超過宙口大小的數據,則接 收方TCP將丟棄它。 UDP:當接收到的數據報裝不進套接口接收緩衝區時,此數據報就被丟棄。UDP是沒有 流量控制的;快的發送者可以很容易地就淹沒慢的接收者,導致接收方的UDP丟棄數據報。 

SO_RCVLOWAT 接收緩衝區下限 int  

SO_SNDLOWAT 發送緩衝區下限 int  

每個套接口都有一個接收低潮限度和一個發送低潮限度。它們是函數selectt使用的, 接收低潮限度是讓select返回“可讀”而在套接口接收緩衝區中必須有的數據總量。 ——對於一個TCPUDP套接口,此值缺省爲1。發送低潮限度是讓select返回“可寫” 而在套接口發送緩衝區中必須有的可用空間。對於TCP套接口,此值常缺省爲2048。 對於UDP使用低潮限度, 由於其發送緩衝區中可用空間的字節數是從不變化的,只要 UDP套接口發送緩衝區大小大於套接口的低潮限度,這樣的UDP套接口就總是可寫的。 UDP沒有發送緩衝區,只有發送緩衝區的大小。 

SO_RCVTIMEO 接收超時 struct timeval  

SO_SNDTIMEO 發送超時 struct timeval  

SO_REUSERADDR 允許重用本地地址和端口 int  充許綁定已被使用的地址(或端口號),可以參考bindman 

SO_EXCLUSIVEADDRUSE 

獨佔模式使用端口,就是不充許和其它程序使用SO_REUSEADDR共享的使用某一端口。 

在確定多重綁定使用誰的時候,根據一條原則是誰的指定最明確則將包遞交給誰,而且沒有權限之分,也就是說低級權限的用戶是可以重綁定在高級權限如服務啓動的端口上的,這是非常重大的一個安全隱患如果不想讓自己程序被監聽,那麼使用這個選項 

SO_TYPE 獲得套接字類型 int  

SO_BSDCOMPAT BSD系統兼容 int  

=============  

IPPROTO_IP  

--------------------------------------------------------------------------  

IP_HDRINCL 在數據包中包含IP首部 int  

   這個選項常用於黑客技術中,隱藏自己的IP地址 

IP_OPTINOS IP首部選項 int  

IP_TOS 服務類型  

IP_TTL 生存時間 int  

以下IPV4選項用於組播 

IPv4 選項 數據類型 描 述 

  IP_ADD_MEMBERSHIP struct ip_mreq 加入到組播組中 

  IP_ROP_MEMBERSHIP struct ip_mreq 從組播組中退出 

  IP_MULTICAST_IF struct ip_mreq 指定提交組播報文的接口 

  IP_MULTICAST_TTL u_char 指定提交組播報文的TTL 

  IP_MULTICAST_LOOP u_char 使組播報文環路有效或無效 

在頭文件中定義了ip_mreq結構: 

struct ip_mreq { 

struct in_addr imr_multiaddr; /* IP multicast address of group */ 

struct in_addr imr_interface; /* local IP address of interface */ 

}; 

[/code:1:63724de67f] 

若進程要加入到一個組播組中,用soketsetsockopt()函數發送該選項。該選項類型是ip_mreq結構,它的第一個字段imr_multiaddr指定了組播組的地址,第二個字段imr_interface指定了接口的IPv4地址。 

  IP_DROP_MEMBERSHIP 

  該選項用來從某個組播組中退出。數據結構ip_mreq的使用方法與上面相同。 

  IP_MULTICAST_IF 

  該選項可以修改網絡接口,在結構ip_mreq中定義新的接口。 

  IP_MULTICAST_TTL 

  設置組播報文的數據包的TTL(生存時間)。默認值是1,表示數據包只能在本地的子網中傳送。 

  IP_MULTICAST_LOOP 

  組播組中的成員自己也會收到它向本組發送的報文。這個選項用於選擇是否激活這種狀態。

 

=============  

IPPRO_TCP  

--------------------------------------------------------------------------  

TCP_MAXSEG TCP最大數據段的大小 int  

獲取或設置TCP連接的最大分節大小(MSS)。返回值是我們的TCP發送給另一端的最大數據量,它常常就是由另一端用SYN分節通告的MSS,除非我們的TCP選擇使用一個比 對方通告的MSS小些的值。如果此值在套接口連接之前取得,則返回值爲未從另·—端 收到Mss選項的情況下所用的缺省值。小於此返回值的信可能真正用在連接上,因爲譬 如說使用時間戳選項的話,它在每個分節上佔用12字節的TCP選項容量。我們的TcP將 發送的每個分節的最大數據量也可在連接存活期內改變,但前提是TCP要支持路徑MTU 發現功能。如果到對方的路徑改變了,此值可上下調整。 

TCP_NODELAY 不使用Nagle算法 int  指定TCP開始發送保持存活探測分節前以秒爲單位的連接空閒時間。缺省值至少必須爲7200秒,即2小時。此選項僅在SO_KEPALIVEE套接口選項打開時纔有效。 

TCP_NODELAY 和 TCP_CORK, 這兩個選項都對網絡連接的行爲具有重要的作用。許多UNIX系統都實現了TCP_NODELAY選項,但是,TCP_CORK則是Linux系統所獨有的而且相對較新;它首先在內核版本2.4上得以實現。此外,其他UNIX系統版本也有功能類似的選項,值得注意的是,在某種由BSD派生的系統上的TCP_NOPUSH選項其實就是TCP_CORK的一部分具體實現。 

TCP_NODELAYTCP_CORK基本上控制了包的“Nagle化”,Nagle化在這裏的含義是採用Nagle算法把較小的包組裝爲更大的幀。John NagleNagle算法的發明人,後者就是用他的名字來命名的,他在1984年首次用這種方法來嘗試解決福特汽車公司的網絡擁塞問題(欲瞭解詳情請參看IETF RFC 896)。他解決的問題就是所謂的silly window syndrome ,中文稱“愚蠢窗口症候羣”,具體含義是,因爲普遍終端應用程序每產生一次擊鍵操作就會發送一個包,而典型情況下一個包會擁有一個字節的數據載荷以及40個字節長的包頭,於是產生4000%的過載,很輕易地就能令網絡發生擁塞,。 Nagle化後來成了一種標準並且立即在因特網上得以實現。它現在已經成爲缺省配置了,但在我們看來,有些場合下把這一選項關掉也是合乎需要的。 

現在讓我們假設某個應用程序發出了一個請求,希望發送小塊數據。我們可以選擇立即發送數據或者等待產生更多的數據然後再一次發送兩種策略。如果我們馬上發送數據,那麼交互性的以及客戶/服務器型的應用程序將極大地受益。例如,當我們正在發送一個較短的請求並且等候較大的響應時,相關過載與傳輸的數據總量相比就會比較低,而且,如果請求立即發出那麼響應時間也會快一些。以上操作可以通過設置套接字的TCP_NODELAY選項來完成,這樣就禁用了Nagle算法。 另外一種情況則需要我們等到數據量達到最大時才通過網絡一次發送全部數據,這種數據傳輸方式有益於大量數據的通信性能,典型的應用就是文件服務器。應用Nagle算法在這種情況下就會產生問題。但是,如果你正在發送大量數據,你可以設置TCP_CORK選項禁用Nagle化,其方式正好同TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就讓我們仔細分析下其工作原理。 

假設應用程序使用sendfile()函數來轉移大量數據。應用協議通常要求發送某些信息來預先解釋數據,這些信息其實就是報頭內容。典型情況下報頭很小,而且套接字上設置了TCP_NODELAY。有報頭的包將被立即傳輸,在某些情況下(取決於內部的包計數器),因爲這個包成功地被對方收到後需要請求對方確認。這樣,大量數據的傳輸就會被推遲而且產生了不必要的網絡流量交換。 

但是,如果我們在套接字上設置了TCP_CORK(可以比喻爲在管道上插入“塞子”)選項,具有報頭的包就會填補大量的數據,所有的數據都根據大小自動地通過包傳輸出去。當數據傳輸完成時,最好取消TCP_CORK 選項設置給連接“拔去塞子”以便任一部分的幀都能發送出去。這同“塞住”網絡連接同等重要。 

總而言之,如果你肯定能一起發送多個數據集合(例如HTTP響應的頭和正文),那麼我們建議你設置TCP_CORK選項,這樣在這些數據之間不存在延遲。能極大地有益於WWWFTP以及文件服務器的性能,同時也簡化了你的工作。示例代碼如下: 

intfd, on = 1; 
… 
/* 此處是創建套接字等操作,出於篇幅的考慮省略*/ 
… 
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */ 
write (fd, …); 
fprintf (fd, …); 
sendfile (fd, …); 
write (fd, …); 
sendfile (fd, …); 
… 
on = 0; 
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* 拔去塞子 */

不幸的是,許多常用的程序並沒有考慮到以上問題。例如,Eric Allman編寫的sendmail就沒有對其套接字設置任何選項。 

Apache HTTPD是因特網上最流行的Web服務器,它的所有套接字就都設置了TCP_NODELAY選項,而且其性能也深受大多數用戶的滿意。這是爲什麼呢?答案就在於實現的差別之上。由BSD衍生的TCP/IP協議棧(值得注意的是FreeBSD)在這種狀況下的操作就不同。當在TCP_NODELAY 模式下提交大量小數據塊傳輸時,大量信息將按照一次write()函數調用發送一塊數據的方式發送出去。然而,因爲負責請求交付確認的記數器是面向字節而非面向包(在Linux上)的,所以引入延遲的概率就降低了很多。結果僅僅和全部數據的大小有關係。而 Linux 在第一包到達之後就要求確認,FreeBSD則在進行如此操作之前會等待好幾百個包。 

Linux系統上,TCP_NODELAY的效果同習慣於BSD TCP/IP協議棧的開發者所期望的效果有很大不同,而且在Linux上的Apache性能表現也會更差些。其他在Linux上頻繁採用TCP_NODELAY的應用程序也有同樣的問題。 

TCP_DEFER_ACCEPT 

我們首先考慮的第1個選項是TCP_DEFER_ACCEPT(這是Linux系統上的叫法,其他一些操作系統上也有同樣的選項但使用不同的名字)。爲了理解TCP_DEFER_ACCEPT選項的具體思想,我們有必要大致闡述一下典型的HTTP客戶/服務器交互過程。請回想下TCP是如何與傳輸數據的目標建立連接的。在網絡上,在分離的單元之間傳輸的信息稱爲IP包(或IP 數據報)。一個包總有一個攜帶服務信息的包頭,包頭用於內部協議的處理,並且它也可以攜帶數據負載。服務信息的典型例子就是一套所謂的標誌,它把包標記代表TCP/IP協議棧內的特殊含義,例如收到包的成功確認等等。通常,在經過“標記”的包裏攜帶負載是完全可能的,但有時,內部邏輯迫使TCP/IP協議棧發出只有包頭的IP包。這些包經常會引發討厭的網絡延遲而且還增加了系統的負載,結果導致網絡性能在整體上降低。 

現在服務器創建了一個套接字同時等待連接。TCP/IP式的連接過程就是所謂“3次握手”。首先,客戶程序發送一個設置SYN標誌而且不帶數據負載的TCP包(一個SYN包)。服務器則以發出帶SYN/ACK標誌的數據包(一個SYN/ACK包)作爲剛纔收到包的確認響應。客戶隨後發送一個ACK包確認收到了第2個包從而結束連接過程。在收到客戶發來的這個SYN/ACK包之後,服務器會喚醒一個接收進程等待數據到達。當3次握手完成後,客戶程序即開始把“有用的”的數據發送給服務器。通常,一個HTTP請求的量是很小的而且完全可以裝到一個包裏。但是,在以上的情況下,至少有4個包將用來進行雙向傳輸,這樣就增加了可觀的延遲時間。此外,你還得注意到,在“有用的”數據被髮送之前,接收方已經開始在等待信息了。 

爲了減輕這些問題所帶來的影響,Linux(以及其他的一些操作系統)在其TCP實現中包括了TCP_DEFER_ACCEPT選項。它們設置在偵聽套接字的服務器方,該選項命令內核不等待最後的ACK包而且在第1個真正有數據的包到達才初始化偵聽進程。在發送SYN/ACK包之後,服務器就會

等待客戶程序發送含數據的IP包。現在,只需要在網絡上傳送3個包了,而且還顯著降低了連接建立的延遲,對HTTP通信而言尤其如此。 

這一選項在好些操作系統上都有相應的對等物。例如,在FreeBSD上,同樣的行爲可以用以下代碼實現: 

/* 爲明晰起見,此處略去無關代碼 */ 

struct accept_filter_arg af = { "dataready", "" }; 

setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)); 

這個特徵在FreeBSD上叫做“接受過濾器”,而且具有多種用法。不過,在幾乎所有的情況下其效果與TCP_DEFER_ACCEPT是一樣的:服務器不等待最後的ACK包而僅僅等待攜帶數據負載的包。要了解該選項及其對高性能Web服務器的重要意義的更多信息請參考Apache文檔上的有關內容。 

HTTP客戶/服務器交互而言,有可能需要改變客戶程序的行爲。客戶程序爲什麼要發送這種“無用的”ACK包呢?這是因爲,TCP協議棧無法知道ACK包的狀態。如果採用FTP而非HTTP,那麼客戶程序直到接收了FTP服務器提示的數據包之後才發送數據。在這種情況下,延遲的ACK將導致客戶/服務器交互出現延遲。爲了確定ACK是否必要,客戶程序必須知道應用程序協議及其當前狀態。這樣,修改客戶行爲就成爲必要了。 

Linux客戶程序來說,我們還可以採用另一個選項,它也被叫做TCP_DEFER_ACCEPT。我們知道,套接字分成兩種類型,偵聽套接字和連接套接字,所以它們也各自具有相應的TCP選項集合。因此,經常同時採用的這兩類選項卻具有同樣的名字也是完全可能的。在連接套接字上設置該選項以後,客戶在收到一個SYN/ACK包之後就不再發送ACK包,而是等待用戶程序的下一個發送數據請求;因此,服務器發送的包也就相應減少了。 

TCP_QUICKACK 

阻止因發送無用包而引發延遲的另一個方法是使用TCP_QUICKACK選項。這一選項與 TCP_DEFER_ACCEPT不同,它不但能用作管理連接建立過程而且在正常數據傳輸過程期間也可以使用。另外,它能在客戶/服務器連接的任何一方設置。如果知道數據不久即將發送,那麼推遲ACK包的發送就會派上用場,而且最好在那個攜帶數據的數據包上設置ACK 標誌以便把網絡負載減到最小。當發送方肯定數據將被立即發送(多個包)時,TCP_QUICKACK選項可以設置爲0。對處於“連接”狀態下的套接字該選項的缺省值是1,首次使用以後內核將把該選項立即復位爲1(這是個一次性的選項)。 

在某些情形下,發出ACK包則非常有用。ACK包將確認數據塊的接收,而且,當下一塊被處理時不至於引入延遲。這種數據傳輸模式對交互過程是相當典型的,因爲此類情況下用戶的輸入時刻無法預測。在Linux系統上這就是缺省的套接字行爲。 

在上述情況下,客戶程序在向服務器發送HTTP請求,而預先就知道請求包很短所以在連接建立之後就應該立即發送,這可謂HTTP的典型工作方式。既然沒有必要發送一個純粹的ACK包,所以設置TCP_QUICKACK0以提高性能是完全可能的。在服務器方,這兩種選項都只能在偵聽套接字上設置一次。所有的套接字,也就是被接受呼叫間接創建的套接字則會繼承原有套接字的所有選項。 

通過TCP_CORKTCP_DEFER_ACCEPTTCP_QUICKACK選項的組合,參與每一HTTP交互的數據包數量將被降低到最小的可接受水平(根據TCP協議的要求和安全方面的考慮)。結果不僅是獲得更快的數據傳輸和請求處理速度而且還使客戶/服務器雙向延遲實現了最小化。 

關於這些選項的詳細情況請查看 Linux Programmer's Manual

8.2 ioctl  ioctl可以控制所有的文件描述符的情況,這裏介紹一下控制套接字的選項.  

int ioctl(int fd,int req,...)  

=============  

ioctl的控制選項  

--------------------------------------------------------------------------  

SIOCATMARK 是否到達帶外標記 int  

FIOASYNC 異步輸入/輸出標誌 int  

FIONREAD 緩衝區可讀的字節數 int  

==========================================================================  

詳細的選項請用 man ioctl_list 查看

發佈了19 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章