【 getsockopt/setsockopt系統調用
功能描述:
獲取或者設置與某個套接字關聯的選 項。選項可能存在於多層協議中,它們總會出現在最上面的套接字層。當操作套接字選項時,選項位於的層和選項的名稱必須給出。為了操作套接字層的選項,應該 將層的值指定為SOL_SOCKET。為了操作其它層的選項,控制選項的合適協議號必須給出。例如,為了表示一個選項由TCP協議解析,層應該設定為協議 號TCP。
用法:
#include
#include
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
參數:
sock:將要被設置或者獲取選項的套接字。
level:選項所在的協議層。
optname:需要訪問的選項名。
optval:對於getsockopt(),指向返回選項值的緩沖。對於setsockopt(),指向包含新選項值的緩沖。
optlen:對於getsockopt(),作為入口參數時,選項值的最大長度。作為出口參數時,選項值的實際長度。對於setsockopt(),現選項的長度。
返回說明:
成功執行時,返回0。失敗返回-1,errno被設為以下的某個值
EBADF:sock不是有效的文件描述詞
EFAULT:optval指向的內存並非有效的進程空間
EINVAL:在調用setsockopt()時,optlen無效
ENOPROTOOPT:指定的協議層不能識別選項
ENOTSOCK:sock描述的不是套接字
首先知道setsockopt()函數的KEEPALIVE屬性是周期地測試連接是否仍存活,
我上網查了很多資料還是不知道如何使用,
最后硬着頭皮自己寫了一個服務器端和一個客戶端的套接字連接
分別設置了兩端的KEEPALIVE屬性為打開
服務器端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 6000 //設定服務器服務端口為6000
#define MAX_LISTEN_SOCK_NUM 20 //設定可監聽套接字的最大個數為20
int main()
{
//sockfd為本地監聽套接字標識符,client_fd為客戶端套接字標識符
int sockfd,client_fd;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
//創建本地監聽套接字
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("套接字創建失敗!/n");
exit(1);
}
//設置套接字的屬性使它能夠在計算機重啟的時候可以再次使用套接字的端口和IP
int err,sock_reuse=1;
err=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(char *)&sock_reuse,sizeof(sock_reuse));
if(err!=0){
printf("套接字可重用設置失敗!/n");
exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(SERVPORT);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
//綁定套接字
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
perror("綁定失敗!/n");
exit(1);
}
//設置監聽
if((listen(sockfd,MAX_LISTEN_SOCK_NUM))==-1){
perror("設置監聽失敗!/n");
exit(1);
}
printf("套接字進入監聽狀態,等待請求連接:/n");
//int time_to_quit=1;
//while(time_to_quit){ //可以通過設置time_to_quit來主動關閉服務器端
while(1){
//有連接請求時進行連接
socklen_t sin_size=sizeof(struct sockaddr_in);
if((client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&sin_size))==-1){
perror("接受連接失敗!/n");
continue;
}
int opt;
socklen_t len=sizeof(int);
if((getsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(char*)&opt,&len))==0){
printf("SO_KEEPALIVE Value: %d/n", opt);
}
printf("接到一個來自%s的連接/n",inet_ntoa(client_addr.sin_addr));
//創建子進程來處理已連接的客戶端套接字
if(send(client_fd,"您好,您已經連接成功!/n",50,0)==-1){
perror("發送通知信息失敗!/n");
exit(0);
}
}
close(client_fd);
return 0;
}
客戶端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXDATASIZE 100
int main()
{
int sockfd,nbytes,serv_port;
char buf_serv_ip[16],buf[26];
struct sockaddr_in serv_addr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("創建套接字失敗!/n");
exit(1);
}
//創建套接字成功后設置其可重用的屬性
int KeepAlive=1;
socklen_t KPlen=sizeof(int);
if(setsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(char *)&KeepAlive,KPlen)!=0){
perror("設置周期測試連接是否仍存活失敗!/n");
exit(1);
}
printf("請輸入要連接主機的IP地址:/n");
scanf("%s",buf_serv_ip);
printf("請輸入要連接主機的端口號:/n");
scanf("%d",&serv_port);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(buf_serv_ip);
serv_addr.sin_port=htons(serv_port);
bzero(&(serv_addr.sin_zero),8);
if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("連接服務器失敗!/n");
exit(1);
}
printf("連接服務器成功!/n");
//在此處可以先接受判斷將要接受數據的長度再創建數組
if((nbytes=recv(sockfd,buf,26,0))==-1){
perror("接受數據失敗!/n");
exit(1);
}
buf[nbytes]='/0';
printf("接受的數據為:%s/n",buf);
close(sockfd);
return 0;
}
分別在兩個終端運(在同一個主機上)行:先啟動服務器端;再啟動客戶端,向服務器端請求連接,連接成功后,客戶端套接字關閉,服務器端套接字始終打開,等待兩個小時多也沒有反應,
肯請高手指點:
(1)這樣做能達到預期報告連接是否仍存在的效果嗎,
(2)不能的話應該如何做,
(3)還有getsockopt()的相關功能又是什么哪?在上面的程序中沒有用到該函數
(4)看了unix網絡編程上的一點關於該問題的解釋還是不明白,因為她提到要兩個小時才回應,但等了撒小時也沒有反應,她還說可以縮短時間,我卻不知道如何縮短
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_OPTINOS IP首部選項 int
IP_TOS 服務類型
IP_TTL 生存時間 int
========================================================================
IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG TCP最大數據段的大小 int
TCP_NODELAY 不使用Nagle算法 int
========================================================================
返回說明:
成功執行時,返回0。失敗返回-1,errno被設為以下的某個值
EBADF:sock不是有效的文件描述詞
EFAULT:optval指向的內存並非有效的進程空間
EINVAL:在調用setsockopt()時,optlen無效
ENOPROTOOPT:指定的協議層不能識別選項
ENOTSOCK:sock描述的不是套接字
SO_RCVBUF和SO_SNDBUF每個套接口都有一個發送緩沖區和一個接收緩沖區,使用這兩個套接口選項可以改變缺省緩沖區大小。
// 接收緩沖區
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));
注意:
當設置TCP套接口接收緩沖區的大小時,函數調用順序是很重要的,因為TCP的窗口規模選項是在建立連接時用SYN與對方互換得到的。對於客戶,SO_RCVBUF選項必須在connect之前設置;對於服務器,SO_RCVBUF選項必須在listen前設置。
如下內容轉自http://linux.chinaunix.net/techdoc/net/2007/11/01/971094.shtml
有時候我們要控制套接字的行為(如修改緩沖區的大小),這個時候我們就要控制套接字的選項了.
以下資料均從網上收集得到
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,套接 口本身則被關閉。
對方無任何響應:源自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結構:
代碼:
struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
若進程要加入到一個組播組中,用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
組播組中的成員自己也會收到它向本組發送的報文。這個選項用於選擇是否激活這種狀態。
==========================================================================
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協議的要求和安全方面的考慮)。結果不僅是獲得更快的數據傳輸和請求處理速度而且還使客戶/服務器雙向延遲實現了最小化。
關於這些選項的詳細情況請查看 Linux Programmer's Manual
如下內容轉自http://hi.baidu.com/taozpwater/item/55983ec132eb304fa8ba9423
今天發現setsockopt的用法太強大了,現在記錄下該函數的使用方法:
簡述:
setsockopt函數用於設置套接口的選項。
函數定義如下:
int setsockopt(
__in SOCKET s,
__in int level,
__in int optname,
__in const char *optval,
__in int optlen
);
s:標識一個套接口的描述字。
level:選項定義的層次;目前僅支持SOL_SOCKET、IPPROTO_TCP、NSPROTO_IPX層次。
optname:需設置的選項。
optval:指針,指向存放選項值的緩沖區。
optlen:optval緩沖區長度。
詳解:
level = SOL_SOCKET
SO_BROADCAST BOOL 允許套接口傳送廣播信息
SO_CONDITIONAL_ACCEPT BOOL Enables incoming connections are to be accepted or rejected by the application, not by the protocol stack.
SO_DEBUG BOOL 記錄調試信息
SO_DONTLINGER BOOL 不要因為數據未發送就阻塞關閉操作。設置本選項相當於將SO_LINGER的l_onoff元素置為零。
SO_DONTROUTE BOOL 禁止選徑;直接傳送。This option is not supported on ATM sockets (results in an error).
SO_GROUP_PRIORITY int Reserved.
SO_KEEPALIVE BOOL 發送“保持活動”包。 Not supported on ATM sockets (results in an error).
SO_LINGER LINGER Lingers on close if unsent data is present.
SO_OOBINLINE BOOL Indicates that out-of-bound data should be returned in-line with regular data. This option is only valid for connection-oriented protocols that support out-of-band data. For a discussion of this topic, see Protocol Independent Out-Of-band Data.
SO_RCVBUF int 為接收確定緩沖區大小
SO_REUSEADDR BOOL Allows the socket to be bound to an address that is already in use. For more information, see bind. Not applicable on ATM sockets.
SO_EXCLUSIVEADDRUSE BOOL Enables a socket to be bound for exclusive access. Does not require administrative privilege.
SO_RCVTIMEO DWORD Sets the timeout, in milliseconds, for blocking receive calls.
SO_SNDBUF int 指定發送緩沖區大小。
SO_SNDTIMEO DWORD The timeout, in milliseconds, for blocking send calls.
SO_UPDATE_ACCEPT_CONTEXT int Updates the accepting socket with the context of the listening socket.
PVD_CONFIG Service Provider Dependent This object stores the configuration information for the service provider associated with socket s. The exact format of this data structure is service provider specific.
level = IPPROTO_TCP
TCP_NODELAY BOOL 禁止發送合並的Nagle算法。This socket option is included for backward compatibility with Windows Sockets1.1
level = IPPROTO_UDP
UDP_NOCHECKSUM BOOL When TRUE, UDP datagrams are sent with the checksum of zero. Required for service providers. If a service provider does not have a mechanism to disable UDP checksum calculation, it may simply store this option without taking any action.
level = NSPROTO_IPX
IPX_PTYPE int Sets the IPX packet type.
IPX_FILTERPTYPE int Sets the receive filter packet type
IPX_STOPFILTERPTYPE int Stops filtering the filter type set with IPX_FILTERTYPE
IPX_DSTYPE int Sets the value of the data stream field in the SPX header on every packet sent.
IPX_EXTENDED_ADDRESS BOOL Sets whether extended addressing is enabled.
IPX_RECVHDRBOOLSets whether the protocol header is sent up on all receive headers.
IPX_RECEIVE_BROADCAST BOOL Indicates broadcast packets are likely on the socket. Set to TRUE by default. Applications that do not use broadcasts should set this to FALSE for better system performance.
IPX_IMMEDIATESPXACK BOOL Directs SPX connections not to delay before sending an ACK. Applications without
back-and-forth traffic should set this to TRUE to increase performance.
level = IPPROTO_IP
IP_ADD_MEMBERSHIP struct ip_mreq Joins the socket to multicast group on the specified interface.
IP_DROP_MEMBERSHIP struct ip_mreq Leaves the specified multicast group from the specified interface. Service providers must support this option when multicast is supported. Support is indicated in the WSAPROTOCOL_INFO structure returned by a WSAEnumProtocols function call with the following: XPI_SUPORTS_MULTIPOINT=1, XP1_MULTIPOINT_CONTROL_PLANE=0, XP1_MULTIPOINT_DATA_PLANE=0.
IP_MULTICAST_IF DWORD Sets the outgoing (IPv4) interface for multicast traffic. Important for multihomed machines. The input value is the 4-byte network byte order IPv4 address. If optval is null, the default interface is used.
IP_MULTICAST_TTL DWORD Sets/gets the TTL value associated with IP multicast traffic on the socket.
level = SOL_IRLMP
IRLMP_ENUMDEVICES DEVICELIST Returns a list of IrDA device IDs for IR capable devices within range.
IRLMP_IAS_QUERY IAS_QUERY Queries IAS on a given service and class name for its attributes.
IRLMP_IAS_SET IAS_SET Enables an application to set a single class in the local IAS. The application specifies the class to set, the attribute, and attribute type.
IRMP_IRLPT_MODE int Disables TinyTP mode and sends data directly over IrLMP frames.
IRLMP_EXCLUSIVE_MODE int Sets socket to bypass TinyTP layer to directly communicate with IrLMP
IRLMP_9WIRE_MODE int Puts the IrDA socket into IrCOMM mode
IRLMP_SHARP_MODE int Enables the Sharp mode.
IRLMP_SEND_PDU_LEN int Retrieves the maximum PDU length required to use IRLMP_9WIRE_MODE
IPV6_ADD_MEMBERSHIP struct ipv6_mreq Join the supplied multicast group on the given interface index
IPV6_DROP_MEMBERSHIP struct ipv6_mreq Leave the supplied multicast group from the given interface
IPV6_HDRINCL BOOL Indicates IPv6 header will be supplied on all outgoing data
IPV6_HOPLIMIT BOOL Indicates that hop (TTL) information should be returned in the WSARecvMsg function
IPV6_JOIN_GROUP struct ipv6_mreq Same as IPV6_ADD_MEMBERSHIP
IPV6_LEAVE_GROUP struct ipv6_m Same as IPV6_DROP_MEMBERSHIP
IPV6_MULTICAST_HOPS DWORD Sets/gets the TTL value associated with IPv6 multicast traffic on the socket
IPV6_MULTICAST_IF DWORD Sets the outgoing (IPv6) interface for multicast traffic. This is important for multihomed
machines. The input value is the 4-byte interface index of the desired outgoing interface (use GetAdaptersAddresses to obtain index information).
IPV6_MULTICAST_LOOP BOOL Indicates multicast data sent on socket will be echoed to the sockets receive buffer if it is also joined on the destination multicast group
IPV6_PKTINFO BOOL Indicates that packet information should be returned in the WSARecvMsg function
IPV6_PROTECTION_LEVEL INT Enables restriction of a socket to a specified scope, such as addresses with the same link local or site local prefix. Provides various restriction levels and default settings. See Using IPV6_PROTECTION_LEVEL for more information.
IPV6_UNICAST_HOPS DWORD Sets/gets the current TTL value associated with IPv6 socket
返回值:
若無錯誤發生,setsockopt()返回0。否則的話,返回SOCKET_ERROR錯誤,應用程序可通過WSAGetLastError()獲取相應錯誤代碼。
錯誤代碼:
WSANOTINITIALISED:在使用此API之前應首先成功地調用WSAStartup()。
WSAENETDOWN:WINDOWS套接口實現檢測到網絡子系統失效。
WSAEFAULT:optval不是進程地址空間中的一個有效部分。
WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運行中。
WSAEINVAL:level值非法,或optval中的信息非法。
WSAENETRESET:當SO_KEEPALIVE設置后連接超時。
WSAENOPROTOOPT:未知或不支持選項。其中,SOCK_STREAM類型的套接口不支持SO_BROADCAST選項,SOCK_DGRAM類型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE選項。
WSAENOTCONN:當設置SO_KEEPALIVE后連接被復位。
WSAENOTSOCK:描述字不是一個套接口。
實例:
1.設置調用closesocket()后,仍可繼續重用該socket。調用closesocket()一般不會立即關閉socket,而經歷TIME_WAIT的過程。
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_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
//接收時限
setsockopt( socket, SOL_SOCKET, 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.在接收數據時,不執行將socket緩沖區的內容拷貝到系統緩沖區: int nZero = 0;
setsockopt( s, 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 ) );