天天看點

十四 setsockopt函數使用

nt setsockopt (

SOCKET s,                 

int level,                

int optname,              

const char FAR * optval, 

int optlen                

);

The Windows Sockets setsockopt function sets a socket option.

中文解釋好像是:設定套接字的選項。

先看如下代碼:

setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int))

這裡是設定SockRaw這個套接字的ip選項中的IP_HDRINCL

參考以下資料:

***************************************************************************************************

Linux網絡程式設計--8. 套接字選項 

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

8.1 getsockopt和setsockopt

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)

level指定控制套接字的層次.可以取三種值: 

1)SOL_SOCKET:通用套接字選項. 

2)IPPROTO_IP:IP選項. 

3)IPPROTO_TCP:TCP選項.

optname指定控制的方式(選項的名稱),我們下面詳細解釋 

optval獲得或者是設定套接字選項.根據選項名稱的資料類型進行轉換 

選項名稱        說明                  資料類型 

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

            SOL_SOCKET 

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

SO_BROADCAST      允許發送廣播資料            int 

SO_DEBUG        允許調試                int 

SO_DONTROUTE      不查找路由               int 

SO_ERROR        獲得套接字錯誤             int 

SO_KEEPALIVE      保持連接配接                int 

SO_LINGER        延遲關閉連接配接              struct linger 

SO_OOBINLINE      帶外資料放入正常資料流         int 

SO_RCVBUF        接收緩沖區大小             int 

SO_SNDBUF        發送緩沖區大小             int 

SO_RCVLOWAT       接收緩沖區下限             int 

SO_SNDLOWAT       發送緩沖區下限             int 

SO_RCVTIMEO       接收逾時                struct timeval 

SO_SNDTIMEO       發送逾時                struct timeval 

SO_REUSERADDR      允許重用本地位址和端口         int 

SO_TYPE         獲得套接字類型             int 

SO_BSDCOMPAT      與BSD系統相容              int 

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

            IPPROTO_IP 

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

IP_HDRINCL       在資料包中包含IP首部          int 

IP_OPTINOSIP首部選項               int 

IP_TOS         服務類型 

IP_TTL         生存時間                int 

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

            IPPRO_TCP 

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

TCP_MAXSEGTCP最大資料段的大小           int 

TCP_NODELAY       不使用Nagle算法             int 

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

setsockopt設定SO_REUSEADDR。

socket關閉之後并不會立即收回,而是要經曆一個TIME_WAIT的階段。windows下最多可以達到4分鐘。

是以在這個時候對這個端口進行重新綁定就會出錯。是以需要先設定 SO_REUSEADDR.

或者在closesocket的時候,使用setsockopt設定SO_DONTLINGER。也不會有TIME_WAIT的階段.

通常使用這個設定來加強網絡程式的健壯性。

下面是别處補充的資料:1.closesocket(一般不會立即關閉而經曆TIME_WAIT的過程)後想繼續重用該socket:

BOOL bReuseaddr=TRUE;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

2. 如果要已經處于連接配接狀态的soket在調用closesocket後強制關閉,不經曆

TIME_WAIT的過程:

BOOL bDontLinger = FALSE;

setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()過程中有時由于網絡狀況等原因,發收不能預期進行,而設定收發時限:

int nNetTimeout=1000;//1秒

//發送時限

setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收時限

setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的時候,傳回的是實際發送出去的位元組(同步)或發送到socket緩沖區的位元組

(異步);系統預設的狀态發送和接收一次為8688位元組(約為8.5K);在實際的過程中發送資料

和接收資料量比較大,可以設定socket緩沖區,而避免了send(),recv()不斷的循環收發:

// 接收緩沖區

int nRecvBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//發送緩沖區

int nSendBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在發送資料的時,希望不經曆由系統緩沖區到socket緩沖區的拷貝而影響

程式的性能:

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(預設情況是将socket緩沖區的内容拷貝到系統緩沖區):

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在發送UDP資料報的時候,希望該socket發送的資料具有廣播特性:

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client連接配接伺服器過程中,如果處于非阻塞模式下的socket在connect()的過程中可

以設定connect()延時,直到accpet()被呼叫(本函數設定隻有在非阻塞的過程中有顯著的

作用,在阻塞的函數調用中作用不大)

BOOL bConditionalAccept=TRUE;

setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在發送資料的過程中(send()沒有完成,還有資料沒發送)而調用了closesocket(),以前我們

一般采取的措施是"從容關閉"shutdown(s,SD_BOTH),但是資料是肯定丢失了,如何設定讓程式滿足具體

應用的要求(即讓沒發完的資料發送出去後在關閉socket)?

struct linger {

u_short l_onoff;

u_short l_linger;

};

linger m_sLinger;

m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有資料沒發送完畢的時候容許逗留)

// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;

m_sLinger.l_linger=5;//(容許逗留的時間為5秒)

setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger)); 

原文:http://blog.csdn.net/fht511/article/details/6130602

#include int setsockopt( int socket, int level, int option_name, const void *option_value, size_t option_len);

第一個參數socket是套接字描述符。

第二個參數level是被設定的選項的級别,如果想要在套接字級别上設定選項,就必須把level設定為 SOL_SOCKET。

option_name指定準備設定的選項,option_name可以有哪些取值,這取決于level,以linux 2.6核心為例(在不同的平台上,這種關系可能會有不同),在套接字級别上(SOL_SOCKET),

option_name可以有以下取值:

SO_DEBUG,打開或關閉調試資訊。 當option_value不等于0時,打開調試資訊,否則,關閉調試資訊。它實際所做的工作是在sock->sk->sk_flag中置SOCK_DBG(第10)位,或清SOCK_DBG位。

SO_REUSEADDR,打開或關閉位址複用功能。 當option_value不等于0時,打開,否則,關閉。它實際所做的工作是置sock->sk->sk_reuse為1或0。

SO_DONTROUTE,打開或關閉路由查找功能。 當option_value不等于0時,打開,否則,關閉。它實際所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。

 SO_BROADCAST,允許或禁止發送廣播資料。 當option_value不等于0時,允許,否則,禁止。它實際所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。

 SO_SNDBUF,設定發送緩沖區的大小。 發送緩沖區的大小是有上下限的,其上限為256 * (sizeof(struct sk_buff) + 256),下限為2048位元組。該操作将sock->sk->sk_sndbuf設定為val * 2,之是以要乘以2,是防止大資料量的發送,突然導緻緩沖區溢出。最後,該操作完成後,因為對發送緩沖的大小作了改變,要檢查sleep隊列,如果有程序正在等待寫,将它們喚醒。

 SO_RCVBUF,設定接收緩沖區的大小。 接收緩沖區大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256位元組。該操作将sock->sk->sk_rcvbuf設定為val * 2。

 SO_KEEPALIVE,套接字保活。 如果協定是TCP,并且目前的套接字狀态不是偵聽(listen)或關閉(close),那麼,當option_value不是零時,啟用TCP保活定時器,否則關閉保活定時器。對于所有協定,該操作都會根據option_value置或清sock->sk->sk_flag中的 SOCK_KEEPOPEN位。

SO_OOBINLINE,緊急資料放入普通資料流。 該操作根據option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。

SO_NO_CHECK,打開或關閉校驗和。 該操作根據option_value的值,設定sock->sk->sk_no_check。

SO_PRIORITY,設定在套接字發送的所有包的協定定義優先權。Linux通過這一值來排列網絡隊列。 這個值在0到6之間(包括0和6),由option_value指定。賦給sock->sk->sk_priority。

SO_LINGER,如果選擇此選項, close或 shutdown将等到所有套接字裡排隊的消息成功發送或到達延遲時間後>才會傳回. 否則, 調用将立即傳回。

該選項的參數(option_value)是一個linger結構: struct linger { int l_onoff; int l_linger; };

 如果linger.l_onoff值為0(關閉),則清sock->sk->sk_flag中的SOCK_LINGER位;否則,置該位,并賦sk->sk_lingertime值為linger.l_linger。 SO_PASSCRED,允許或禁止SCM_CREDENTIALS 控制消息的接收。 該選項根據option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。

SO_TIMESTAMP,打開或關閉資料報中的時間戳接收。 該選項根據option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打開,則還需設sock->sk->sk_flag中的SOCK_TIMESTAMP位,同時,将全局變量 netstamp_needed加1。

SO_RCVLOWAT,設定接收資料前的緩沖區内的最小位元組數。 在Linux中,緩沖區内的最小位元組數是固定的,為1。即将sock->sk->sk_rcvlowat固定指派為1。

SO_RCVTIMEO,設定接收逾時時間。 該選項最終将接收逾時時間賦給sock->sk->sk_rcvtimeo。

SO_SNDTIMEO,設定發送逾時時間。 該選項最終将發送逾時時間賦給sock->sk->sk_sndtimeo。

SO_BINDTODEVICE,将套接字綁定到一個特定的裝置上。 該選項最終将裝置賦給sock->sk->sk_bound_dev_if。 SO_ATTACH_FILTER和SO_DETACH_FILTER。 關于資料包過濾,它們最終會影響sk->sk_filter。 以上所介紹的都是在SOL_SOCKET層的一些套接字選項,如果超出這個範圍,給出一些不在這一level的選項作為參數,最終會得到- ENOPROTOOPT的傳回值。但以上的分析僅限于這些選項對sock-sk的值的影響,這些選項真正如何發揮作用,我們的探索道路将漫漫其修遠

在TCP連接配接中,recv等函數預設為阻塞模式(block),即直到有資料到來之前函數不會傳回,而我們有時則需要一種逾時機制使其在一定時間後傳回而不管是否有資料到來,這裡我們就會用到setsockopt()函數:

    int  setsockopt(int  s, int level, int optname, void* optval, socklen_t* optlen);

    這裡我們要涉及到一個結構:

    struct timeval

    {

            time_t tv_sec;

            time_t tv_usec;

    };

    這裡第一個域的機關為秒,第二個域的機關為微秒。

    struct timeval tv_out;

    tv_out.tv_sec = 1;

    tv_out.tv_usec = 0;

    填充這個結構後,我們就可以以如下的方式調用這個函數:

    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));(具體參數可以man一下,或檢視MSDN)

    這樣我們就設定了recv()函數的逾時機制,當超過tv_out設定的時間而沒有資料到來時recv()就會傳回0值。

    第二個我們要介紹的是多路複用機制,也就是同時監聽多個套接字連接配接。

    int select(int n, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

    這裡涉及到了fd_set結構:

    typedef struct fd_set

    {

            u_int fd_count;

            int fd_array[FD_SETSIZE];

    }

    fd_count為fd_set結構中包含的套接字個數,fd_array唯一個int 數組,包含了我們要監聽的套接字。

    首先我們需要使用FD_SET将我們要監聽的套接字添加到fd_set結構中:

    fd_set readfd;

    FD_SET(fd, &readfd);

    然後我們這樣調用select函數:

    select(max_fd + 1, &readfd, NULL, NULL, NULL);(具體參數可以man一下,或檢視MSDN)

    FD_ISSET(fd, &readfd);

    其中max_fd為我們要監聽的套接字中值最大的一個,同時在調用select是要将其加1,readfd即為我們監聽的要進行讀操作的套接字連接配接,第三個參數是我們監聽的要進行寫操作的套接字連接配接,第四個參數用于異常,而最後一個參數可以用來設定逾時,這裡同樣使用了struct timeval結構,可以實作與前面介紹的同樣的效果。這裡如果連接配接進來的話select即傳回一個大于零的值,然後我們調用FD_ISSET宏來檢測具體是那一個套接字有資料進來(FD_ISSET傳回非零值)。

    最後介紹的是另一種實作非阻塞的方法,這種方法在有些應用中會起到一定作用,尤其是在select()函數監聽的套接字個數超過1024個時(因為fd_set結構在大部分UNIX系統中都對其可以監聽的套接字個數作了1024的限制,如果要突破這個限制,必須修改頭檔案并重新編譯核心),我們就不能使用select多路複用機制。

    拿recv()函數來說,我們可以這樣進行調用:

    recv(fd, buf, sizeof(buf), MSG_DONTWAIT);

    注意到我們這裡采用了MSG_DONTWAIT标志,它的作用是告訴recv()函數如果有資料到來的話就接受全部資料并立刻傳回,沒有資料的話也是立刻傳回,而不進行任何的等待。采用這個機制就可以在多于1024個套接字連接配接時使用for()循環對全部的連接配接進行監聽。

int setsockopt(

SOCKET s,

int level,

int optname,

const char* optval,

int optlen

);

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

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

SOL_SOCKET: 基本套接口

IPPROTO_IP: IPv4套接口

IPPROTO_IPV6: IPv6套接口

IPPROTO_TCP: TCP套接口

optname(選項名): 選項名稱

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

optlen(選項長度) :optval 的大小

傳回值:标志打開或關閉某個特征的二進制選項

[/code:1:59df4ce128]

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

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,套接 口本身則被關閉。 對方無任何響應:源自berkeley的TCP發送另外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.4,4.1.7和4.1.21各節),并設定了零逾時間隔,則 closesocket()不被阻塞立即執行,不論是否有排隊資料未發送或未被确認。這種關閉方式稱為“強制”或“失效”關閉,因為套接口的虛電路立即被 複位,且丢失了未發送的資料。在遠端的recv()調用将以WSAECONNRESET出錯。

若設定了SO_LINGER并确定了非零的逾時間 隔,則closesocket()調用阻塞程序,直到所剩資料發送完畢或逾時。這種關閉稱為“優雅的”關閉。請注意如果套接口置為非阻塞且 SO_LINGER設為非零逾時,則closesocket()調用将以WSAEWOULDBLOCK錯誤傳回。

若在一個流類套接口上設定了 SO_DONTLINGER(也就是說将linger結構的l_onoff域設為零;參見2.4,4.1.7,4.1.21節),則 closesocket()調用立即傳回。但是,如果可能,排隊的資料将在套接口關閉前發送。請注意,在這種情況下WINDOWS套接口實作将在一段不确 定的時間内保留套接口以及其他資源,這對于想用是以套接口的應用程式來說有一定影響。

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

SO_RCVBUF 接收緩沖區大小 int

設定接收緩沖區的保留大小

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

SO_SNDBUF 發送緩沖區大小 int

設定發送緩沖區的保留大小

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

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

SO_RCVLOWAT 接收緩沖區下限 int

SO_SNDLOWAT 發送緩沖區下限 int

每 個套接口都有一個接收低潮限度和一個發送低潮限度。它們是函數selectt使用的, 接收低潮限度是讓select傳回“可讀”而在套接口接收緩沖區中必須有的資料總量。 ——對于一個TCP或UDP套接口,此值預設為1。發送低潮限度是讓select傳回“可寫” 而在套接口發送緩沖區中必須有的可用空間。對于TCP套接口,此值常預設為2048。 對于UDP使用低潮限度, 由于其發送緩沖區中可用空間的位元組數是從不變化的,隻要 UDP套接口發送緩沖區大小大于套接口的低潮限度,這樣的UDP套接口就總是可寫的。 UDP沒有發送緩沖區,隻有發送緩沖區的大小。

SO_RCVTIMEO 接收逾時 struct timeval

SO_SNDTIMEO 發送逾時 struct timeval

SO_REUSERADDR 允許重用本地位址和端口 int

充許綁定已被使用的位址(或端口号),可以參考bind的man

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結構:

[code:1:63724de67f]

struct ip_mreq {

struct in_addr imr_multiaddr; 

struct in_addr imr_interface; 

};

[/code:1:63724de67f]

若程序要加入到一個多點傳播組中,用soket的setsockopt()函數發送該選項。該選項類型是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

多點傳播組中的成員自己也會收到它向本組發送的封包。這個選項用于選擇是否激活這種狀态。

無雙 回複于:2003-05-08 21:21:52

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_NODELAY和TCP_CORK基本上控制了包的 “Nagle化”,Nagle化在這裡的含義是采用Nagle算法把較小的包組裝為更大的幀。John Nagle是Nagle算法的發明人,後者就是用他的名字來命名的,他在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選項,這樣在這些資料之間不存在延遲。能極大地有益于WWW、FTP以及檔案伺服器的性能,同時也簡化了你的工作。示例代碼如下:

intfd, on = 1;

setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); 

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_QUICKACK為0以提高性能是完全可能的。在伺服器方,這兩種選項都隻能在偵聽套接字上設定一次。所有的 套接字,也就是被接受呼叫間接建立的套接字則會繼承原有套接字的所有選項。

通過TCP_CORK、TCP_DEFER_ACCEPT和 TCP_QUICKACK選項的組合,參與每一HTTP互動的資料包數量将被降低到最小的可接受水準(根據TCP協定的要求和安全方面的考慮)。結果不僅 是獲得更快的資料傳輸和請求處理速度而且還使客戶/伺服器雙向延遲實作了最小化。

二、使用例子說明

1.closesocket(一般不會立即關閉而經曆TIME_WAIT的過程)後想繼續重用該socket:

BOOL bReuseaddr=TRUE;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

2. 如果要已經處于連接配接狀态的soket在調用closesocket後強制關閉,不經曆

TIME_WAIT的過程:

BOOL bDontLinger = FALSE;

setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()過程中有時由于網絡狀況等原因,發收不能預期進行,而設定收發時限:

int nNetTimeout=1000;//1秒

//發送時限

setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收時限

setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的時候,傳回的是實際發送出去的位元組(同步)或發送到socket緩沖區的位元組

(異步);系統預設的狀态發送和接收一次為8688位元組(約為8.5K);在實際的過程中發送資料

和接收資料量比較大,可以設定socket緩沖區,而避免了send(),recv()不斷的循環收發:

// 接收緩沖區

int nRecvBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//發送緩沖區

int nSendBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在發送資料的時,希望不經曆由系統緩沖區到socket緩沖區的拷貝而影響

程式的性能:

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(預設情況是将socket緩沖區的内容拷貝到系統緩沖區):

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在發送UDP資料報的時候,希望該socket發送的資料具有廣播特性:

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client連接配接伺服器過程中,如果處于非阻塞模式下的socket在connect()的過程中可

以設定connect()延時,直到accpet()被呼叫(本函數設定隻有在非阻塞的過程中有顯著的

作用,在阻塞的函數調用中作用不大)

BOOL bConditionalAccept=TRUE;

setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在發送資料的過程中(send()沒有完成,還有資料沒發送)而調用了closesocket(),以前我們

一般采取的措施是"從容關閉"shutdown(s,SD_BOTH),但是資料是肯定丢失了,如何設定讓程式滿足具體

應用的要求(即讓沒發完的資料發送出去後在關閉socket)?

struct linger {

u_short l_onoff;

u_short l_linger;

};

linger m_sLinger;

m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有資料沒發送完畢的時候容許逗留)

// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;

m_sLinger.l_linger=5;//(容許逗留的時間為5秒)

setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

setsockopt()用法

2007/12/05 19:01

一下來源于網際網路:

1.closesocket(一般不會立即關閉而經曆TIME_WAIT的過程)後想繼續重用該socket:

BOOL bReuseaddr=TRUE;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));

2. 如果要已經處于連接配接狀态的soket在調用closesocket後強制關閉,不經曆

TIME_WAIT的過程:

BOOL bDontLinger = FALSE;

setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

3.在send(),recv()過程中有時由于網絡狀況等原因,發收不能預期進行,而設定收發時限:

int nNetTimeout=1000;//1秒

//發送時限

setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

//接收時限

setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

4.在send()的時候,傳回的是實際發送出去的位元組(同步)或發送到socket緩沖區的位元組

(異步);系統預設的狀态發送和接收一次為8688位元組(約為8.5K);在實際的過程中發送資料

和接收資料量比較大,可以設定socket緩沖區,而避免了send(),recv()不斷的循環收發:

// 接收緩沖區

int nRecvBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

//發送緩沖區

int nSendBuf=32*1024;//設定為32K

setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

5. 如果在發送資料的時,希望不經曆由系統緩沖區到socket緩沖區的拷貝而影響

程式的性能:

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));

6.同上在recv()完成上述功能(預設情況是将socket緩沖區的内容拷貝到系統緩沖區):

int nZero=0;

setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

7.一般在發送UDP資料報的時候,希望該socket發送的資料具有廣播特性:

BOOL bBroadcast=TRUE;

setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));

8.在client連接配接伺服器過程中,如果處于非阻塞模式下的socket在connect()的過程中可

以設定connect()延時,直到accpet()被呼叫(本函數設定隻有在非阻塞的過程中有顯著的

作用,在阻塞的函數調用中作用不大)

BOOL bConditionalAccept=TRUE;

setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));

9.如果在發送資料的過程中(send()沒有完成,還有資料沒發送)而調用了closesocket(),以前我們

一般采取的措施是"從容關閉"shutdown(s,SD_BOTH),但是資料是肯定丢失了,如何設定讓程式滿足具體

應用的要求(即讓沒發完的資料發送出去後在關閉socket)?

struct linger {

u_short l_onoff;

u_short l_linger;

};

linger m_sLinger;

m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有資料沒發送完畢的時候容許逗留)

// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;

m_sLinger.l_linger=5;//(容許逗留的時間為5秒)

setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

繼續閱讀