linux socket網絡編程詳解





什麼是Socket
   Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程式員能夠用他們來研發TCP/IP網絡上的應用程式。要學Internet上的TCP/IP網絡編程,必須理解Socket接口。
Socket接口設計者最先是將接口放在Unix操作系統裏面的。假如瞭解Unix系統的輸入和輸出的話,就很容易瞭解Socket了。網絡的 Socket數據傳輸是一種特別的I/O,Socket也是一種文檔描述符。Socket也具備一個類似於打開文檔的函數調用Socket(),該函數返回一個整型的Socket描述符,隨後的連接建立、數據傳輸等操作都是通過該Socket實現的。常用的Socket類型有兩種:流式Socket (SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對於面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應於無連接的UDP服務應用。
----socket基於tcp/ip層和應用層的中間層。
Socket建立
  爲了建立Socket,程式能夠調用Socket函數,該函數返回一個類似於文檔描述符的句柄。socket函數原型爲:
   int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常爲PF_INET,表示互連網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,Socket接口還定義了原始Socket(SOCK_RAW),允許程式使用低層協議;protocol通常賦值"0",protocol,IPPROTO_TCP是TCP傳輸協議 IPPROTO_UDP是UDP傳輸協議。 Socket()調用返回一個整型socket描述符,您能夠在後面的調用使用他。
   Socket描述符是個指向內部數據結構的指針,他指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味着爲一個Socket數據結構分配存儲空間。Socket執行體爲您管理描述符表。
  兩個網絡程式之間的一個網絡連接包括五種信息:通信協議、本地協議地址、本地主機端口、遠端主機地址和遠端協議端口。Socket數據結構中包含這五種信息。




sockaddr_in--介紹

sockaddr_in(在netinet/in.h中定義):
struct sockaddr_in {
short int sin_family;                      /* Address family */
unsigned short int sin_port;       /* Port number */
struct in_addr sin_addr;              /* Internet address */
unsigned char sin_zero[8];         /* Same size as struct sockaddr */
};
struct in_addr {
unsigned long s_addr;
};

typedef struct in_addr {
union {
            struct{
                        unsigned char s_b1,
                        s_b2,
                        s_b3,
                        s_b4;
                        } S_un_b;
           struct {
                        unsigned short s_w1,
                        s_w2;
                        } S_un_w;
            unsigned long S_addr;
          } S_un;
} IN_ADDR;

sin_family指代協議族,在socket編程中只能是AF_INET
sin_port存儲端口號(使用網絡字節順序)
sin_addr存儲IP地址,使用in_addr這個數據結構
sin_zero是爲了讓sockaddr與sockaddr_in兩個數據結構保持大小相同而保留的空字節。
s_addr按照網絡字節順序存儲IP地址

sockaddr_in和sockaddr是並列的結構,指向sockaddr_in的結構體的指針也可以指向
sockadd的結構體,並代替它。也就是說,你可以使用sockaddr_in建立你所需要的信息,
在最後用進行類型轉換就可以了bzero((char*)&mysock,sizeof(mysock));//初始化
mysock結構體名
mysock.sa_family=AF_INET;
mysock.sin_addr.s_addr=inet_addr("192.168.0.1");
……
等到要做轉換的時候用:
(struct sockaddr*)mysock


Socket配置
通過socket調用返回一個socket描述符後,在使用socket進行網絡傳輸以前,必須配置該socket。面向連接的socket客戶端通過調用Connect函數在socket數據結構中保存本地和遠端信息。無連接socket的客戶端和服務端連同面向連接socket的服務端通過調用 bind函數來配置本地信息。
Bind函數將socket和本機上的一個端口相關聯,隨後您就能夠在該端口監聽服務請求。Bind函數原型爲:
   int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
   Sockfd是調用socket函數返回的socket描述符,my_addr是個指向包含有本機IP地址及端口號等信息的sockaddr類型的指針;addrlen常被配置爲sizeof(struct sockaddr)。
   struct sockaddr結構類型是用來保存socket信息的:
   struct sockaddr {
   unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
   sa_family一般爲AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP地址和端口號。
   另外更有一種結構類型:
   struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 端口號 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充0 以保持和struct sockaddr同樣大小 */
   };
這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到和struct sockaddr同樣的長度,能夠用bzero()或memset()函數將其置爲零。指向sockaddr_in 的指針和指向sockaddr的指針能夠相互轉換,這意味着假如一個函數所需參數類型是sockaddr時,您能夠在函數調用的時候將一個指向 sockaddr_in的指針轉換爲指向sockaddr的指針;或相反。
  使用bind函數時,能夠用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被佔用的端口號:
   my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的端口號 */
   my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置爲0,函數會自動爲您選擇一個未佔用的端口來使用。同樣,通過將my_addr.sin_addr.s_addr置爲INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數是需要將sin_port和sin_addr轉換成爲網絡字節優先順序;而sin_addr則無需轉換。
  電腦數據存儲有兩種字節優先順序:高位字節優先和低位字節優先---大端小端。Internet上數據以高位字節優先順序在網絡上傳輸,所以對於在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
   下面是幾個字節順序轉換函數:
·htonl():把32位值從主機字節序轉換成網絡字節序
·htons():把16位值從主機字節序轉換成網絡字節序
·ntohl():把32位值從網絡字節序轉換成主機字節序
·ntohs():把16位值從網絡字節序轉換成主機字節序
   Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置爲相應的錯誤號。需要注意的是,在調用bind函數時一般不要將端口號置爲小於1024的值,因爲1到1024是保留端口號,您能夠選擇大於1024中的任何一個沒有被佔用的端口號。



setsockopt()用法詳解




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; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of 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)); /* 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_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




連接建立
  面向連接的客戶程式使用Connect函數來配置socket並和遠端服務器建立一個TCP連接,其函數原型爲:
   int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd 是socket函數返回的socket描述符;serv_addr是包含遠端主機IP地址和端口號的指針;addrlen是遠端地質結構的長度。 Connect函數在出現錯誤時返回-1,並且配置errno爲相應的錯誤碼。進行客戶端程式設計無須調用bind(),因爲這種情況下只需知道目的機器的IP地址,而客戶通過哪個端口和服務器建立連接並無需關心,socket執行體爲您的程式自動選擇一個未被佔用的端口,並通知您的程式數據什麼時候到打斷口。
   Connect函數啓動和遠端主機的直接連接。只有面向連接的客戶程式使用socket時才需要將此socket和遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啓動一個連接,他只是被動的在協議端口監聽客戶的請求。
   Listen函數使socket處於被動的監聽模式,併爲該socket建立一個輸入數據隊列,將到達的服務請求保存在此隊列中,直到程式處理他們。
   int listen(int sockfd, int backlog);
Sockfd 是Socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連接請求將在隊列中等待accept()他們(參考下文)。Backlog對隊列中等待服務的請求的數目進行了限制,大多數系統缺省值爲20。假如一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯信息。
當出現錯誤時listen函數返回-1,並置相應的errno錯誤碼。
   accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列後,服務器就調用accept函數,然後睡眠並等待客戶的連接請求。
   int accept(int sockfd, void *addr, int *addrlen);
sockfd是被監聽的socket描述符,addr通常是個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的信息(某臺主機從某個端口發出該請求);addrten通常爲一個指向值爲sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。
  首先,當accept函數監控的 socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯繫起來,收到服務請求的初始socket仍能夠繼續在以前的 socket上監聽,同時能夠在新的socket描述符上進行數據傳輸操作。
數據傳輸
   Send()和recv()這兩個函數用於面向連接的socket上進行數據傳輸。
   Send()函數原型爲:
   int send(int sockfd, const void *msg, int len, int flags);
Sockfd是您想用來傳輸數據的socket描述符;msg是個指向要發送數據的指針;Len是以字節爲單位的數據的長度;flags一般情況下置爲0(關於該參數的用法可參照man手冊)。
   Send()函數返回實際上發送出的字節數,可能會少於您希望發送的數據。在程式中應該將send()的返回值和欲發送的字節數進行比較。當send()返回值和len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
   recv()函數原型爲:
   int recv(int sockfd,void *buf,int len,unsigned int flags);
   Sockfd是接受數據的socket描述符;buf 是存放接收數據的緩衝區;len是緩衝的長度。Flags也被置爲0。Recv()返回實際上接收的字節數,當出現錯誤時,返回-1並置相應的errno值。
Sendto()和recvfrom()用於在無連接的數據報socket方式下進行數據傳輸。由於本地socket並沒有和遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型爲:
   int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  該函數比send()函數多了兩個參數,to表示目地機的IP地址和端口號信息,而tolen常常被賦值爲sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
   Recvfrom()函數原型爲:
   int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是個struct sockaddr類型的變量,該變量保存源機的IP地址及端口號。fromlen常置爲sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。
假如您對數據報socket調用了connect()函數時,您也能夠利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動爲之加上目地和源地址信息。
結束傳輸
  當任何的數據操作結束以後,您能夠調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
close(sockfd);
  您也能夠調用shutdown()函數來關閉該socket。該函數允許您只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如您能夠關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入任何數據。
   int shutdown(int sockfd,int how);
   Sockfd是需要關閉的socket的描述符。參數 how允許爲shutdown操作選擇以下幾種方式:
   ·0-------不允許繼續接收數據
   ·1-------不允許繼續發送數據
   ·2-------不允許繼續發送和接收數據,
   ·均爲允許則調用close ()
   shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。

下面來羅列一下判斷遠端已經斷開的方法:

法一:

當recv()返回值小於等於0時,socket連接斷開。但是還需要判斷 errno是否等於 EINTR,如果errno == EINTR 則說明recv函數是由於程序接收到信號後返回的,socket連接還是正常的,不應close掉socket連接。

 

法二:

  struct tcp_info info; 
  int len=sizeof(info); 
  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
  if((info.tcpi_state==TCP_ESTABLISHED))  則說明未斷開  else 斷開

 

法三:

若使用了select等系統函數,若遠端斷開,則select返回1,recv返回0則斷開。其他注意事項同法一。

 

法四:

int keepAlive = 1; // 開啓keepalive屬性
int keepIdle = 60; // 如該連接在60秒內沒有任何數據往來,則進行探測
int keepInterval = 5; // 探測時發包的時間間隔爲5 秒
int keepCount = 3; // 探測嘗試的次數.如果第1次探測包就收到響應了,則後2次的不再發.

setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

設置後,若斷開,則在使用該socket讀寫時立即失敗,並返回ETIMEDOUT錯誤

 

法五:

自己實現一個心跳檢測,一定時間內未收到自定義的心跳包則標記爲已斷開。


Socket編程實例
   代碼實例中的服務器通過socket連接向客戶端發送字符串"Hello, you are connected!"。只要在服務器上運行該服務器軟件,在客戶端運行客戶軟件,客戶端就會收到該字符串。
   該服務器軟件代碼如下:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define SERVPORT 3333 /*服務器監聽端口號 */
#define BACKLOG 10 /* 最大同時連接請求數 */
main()
{
  int sockfd,client_fd; /*sock_fd:監聽socket;client_fd:數據傳輸socket */
  struct sockaddr_in my_addr; /* 本機地址信息 */
  struct sockaddr_in remote_addr; /* 客戶端地址信息 */
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
   perror("socket創建出錯!"); 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("bind出錯!");
    exit(1);
  }
  if (listen(sockfd, BACKLOG) == -1) {
    perror("listen出錯!");
    exit(1);
  }
  while(1) {
   sin_size = sizeof(struct sockaddr_in);
   if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
      perror("accept出錯");
      continue;
   }
   printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr));
   if (!fork()) { /* 子進程代碼段 */
     if (send(client_fd, "Hello, you are connected!\n", 26, 0) == -1)
     perror("send出錯!");
     close(client_fd);
     exit(0);
   }
   close(client_fd);
   }
  }
}
服務器的工作流程是這樣的:首先調用socket函數創建一個Socket,然後調用bind函數將其和本機地址連同一個本地端口號綁定,然後調用 listen在相應的socket上監聽,當accpet接收到一個連接服務請求時,將生成一個新的socket。服務器顯示該客戶機的IP地址,並通過新的socket向客戶端發送字符串"Hello,you are connected!"。最後關閉該socket。
  代碼實例中的fork()函數生成一個子進程來處理數據傳輸部分,fork()語句對於子進程返回的值爲0。所以包含fork函數的if語句是子進程代碼部分,他和if語句後面的父進程代碼部分是併發執行的。
客戶端程式代碼如下:
#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define SERVPORT 3333
#define MAXDATASIZE 100 /*每次最大數據傳輸量 */
main(int argc, char *argv[]){
  int sockfd, recvbytes;
  char buf[MAXDATASIZE];
  struct hostent *host;
  struct sockaddr_in serv_addr;
  if (argc h_addr);
  bzero(&(serv_addr.sin_zero),8);
  if (connect(sockfd, (struct sockaddr *)&serv_addr, \
   sizeof(struct sockaddr)) == -1) {
perror("connect出錯!");
exit(1);
}
  if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {
perror("recv出錯!");
exit(1);
}
  buf[recvbytes] = '\0';
  printf("Received: %s",buf);
  close(sockfd);
}
  客戶端程式首先通過服務器域名獲得服務器的IP地址,然後創建一個socket,調用connect函數和服務器建立連接,連接成功之後接收從服務器發送過來的數據,最後關閉socket。
  函數gethostbyname()是完成域名轉換的。由於IP地址難以記憶和讀寫,所以爲了方便,人們常常用域名來表示主機,這就需要進行域名和IP地址的轉換。函數原型爲:
   struct hostent *gethostbyname(const char *name);
   函數返回爲hosten的結構類型,他的定義如下:
   struct hostent {
  char *h_name; /* 主機的官方域名 */
   char **h_aliases; /* 一個以NULL結尾的主機別名數組 */
   int h_addrtype; /* 返回的地址類型,在Internet環境下爲AF-INET */
   int h_length; /* 地址的字節長度 */
   char **h_addr_list; /* 一個以0結尾的數組,包含該主機的任何地址*/
   };
   #define h_addr h_addr_list[0] /*在h-addr-list中的第一個地址*/
   當 gethostname()調用成功時,返回指向struct hosten的指針,當調用失敗時返回-1。當調用gethostbyname時,您不能使用perror()函數來輸出錯誤信息,而應該使用herror()函數來輸出。
  無連接的客戶/服務器程式的在原理上和連接的客戶/服務器是相同的,兩者的區別在於無連接的客戶/服務器中的客戶一般無需建立連接,而且在發送接收數據時,需要指定遠端機的地址。
阻塞和非阻塞
阻塞函數在完成其指定的任務以前不允許程式調用另一個函數。例如,程式執行一個讀數據的函數調用時,在此函數完成讀操作以前將不會執行下一程式語句。當服務器運行到accept語句時,而沒有客戶連接服務請求到來,服務器就會停止在accept語句上等待連接服務請求的到來。這種情況稱爲阻塞(blocking)。而非阻塞操作則能夠立即完成。比如,假如您希望服務器僅僅注意檢查是否有客戶在等待連接,有就接受連接,否則就繼續做其他事情,則能夠通過將Socket配置爲非阻塞方式來實現。非阻塞socket在沒有客戶在等待時就使accept調用立即返回。
   #include 
   #include 
   ……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
通過配置socket爲非阻塞方式,能夠實現"輪詢"若干Socket。當企圖從一個沒有數據等待處理的非阻塞Socket讀入數據時,函數將立即返回,返回值爲-1,並置errno值爲EWOULDBLOCK。但是這種"輪詢"會使CPU處於忙等待方式,從而降低性能,浪費系統資源。而調用 select()會有效地解決這個問題,他允許您把進程本身掛起來,而同時使系統內核監聽所需要的一組文檔描述符的任何活動,只要確認在任何被監控的文檔描述符上出現活動,select()調用將返回指示該文檔描述符已準備好的信息,從而實現了爲進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費 CPU開銷。Select函數原型爲:
int select(int numfds,fd_set *readfds,fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
其中readfds、writefds、exceptfds分別是被select()監控的讀、寫和異常處理的文檔描述符集合。假如您希望確定是否能夠從標準輸入和某個socket描述符讀取數據,您只需要將標準輸入的文檔描述符0和相應的sockdtfd加入到readfds集合中;numfds的值是需要檢查的號碼最高的文檔描述符加1,這個例子中numfds的值應爲sockfd+1;當select返回時,readfds將被修改,指示某個文檔描述符已準備被讀取,您能夠通過FD_ISSSET()來測試。爲了實現fd_set中對應的文檔描述符的配置、復位和測試,他提供了一組宏:
   FD_ZERO(fd_set *set)----清除一個文檔描述符集;
   FD_SET(int fd,fd_set *set)----將一個文檔描述符加入文檔描述符集中;
   FD_CLR(int fd,fd_set *set)----將一個文檔描述符從文檔描述符集中清除;
   FD_ISSET(int fd,fd_set *set)----試判斷是否文檔描述符被置位。
   Timeout參數是個指向struct timeval類型的指針,他能夠使select()在等待timeout長時間後沒有文檔描述符準備好即返回。struct timeval數據結構爲:
   struct timeval {
   int tv_sec; /* seconds */
   int tv_usec; /* microseconds */
};
POP3客戶端實例
  下面的代碼實例基於POP3的客戶協議,和郵件服務器連接並取回指定用戶帳號的郵件。和郵件服務器交互的命令存儲在字符串數組POPMessage中,程式通過一個do-while循環依次發送這些命令。
#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define POP3SERVPORT 110
#define MAXDATASIZE 4096
main(int argc, char *argv[]){
int sockfd;
struct hostent *host;
struct sockaddr_in serv_addr;
char *POPMessage[]={
"USER userid\r\n",
"PASS password\r\n",
"STAT\r\n",
"LIST\r\n",
"RETR 1\r\n",
"DELE 1\r\n",
"QUIT\r\n",
NULL
};
int iLength;
int iMsg=0;
int iEnd=0;
char buf[MAXDATASIZE];
if((host=gethostbyname("your.server"))==NULL) {
perror("gethostbyname error");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket error");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(POP3SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("connect error");
exit(1);
}
do {
send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);
printf("have sent: %s",POPMessage[iMsg]);
iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0);
iEnd+=iLength;
buf[iEnd]='\0';
printf("received: %s,%d\n",buf,iMsg);
iMsg++;
} while (POPMessage[iMsg]);
close(sockfd);
}

 

 

 

 

 

 

 

 

服務器中判斷客戶端socket斷開連接的方法

下面來羅列一下判斷遠端已經斷開的方法:

法一:

當recv()返回值小於等於0時,socket連接斷開。但是還需要判斷 errno是否等於 EINTR,如果errno == EINTR 則說明recv函數是由於程序接收到信號後返回的,socket連接還是正常的,不應close掉socket連接。

 

法二:

  struct tcp_info info; 
  int len=sizeof(info); 
  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
  if((info.tcpi_state==TCP_ESTABLISHED))  則說明未斷開  else 斷開

 

應用層

 

NAME

    getsockopt - get options on sockets

SYNOPSIS

    #include <sys/types.h>

    #include <sys/socket.h>

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

 

調用關係

 

 函數的調用關係圖如下:

法三:

若使用了select等系統函數,若遠端斷開,則select返回1,recv返回0則斷開。其他注意事項同法一。

 

法四:

int keepAlive = 1; // 開啓keepalive屬性
int keepIdle = 60; // 如該連接在60秒內沒有任何數據往來,則進行探測
int keepInterval = 5; // 探測時發包的時間間隔爲5 秒
int keepCount = 3; // 探測嘗試的次數.如果第1次探測包就收到響應了,則後2次的不再發.

setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));

設置後,若斷開,則在使用該socket讀寫時立即失敗,並返回ETIMEDOUT錯誤

 

法五:

自己實現一個心跳檢測,一定時間內未收到自定義的心跳包則標記爲已斷開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章