套接字具有多種特性,這些特性可通過可選項(xiàng)更改芳室,本篇文章將介紹更改套接字可選項(xiàng)的方法,并以此為基礎(chǔ)進(jìn)一步觀察套接字內(nèi)部
前面介紹了套接字通信的基本函數(shù)套接字(Socket)編程(一) 函數(shù)概念篇和套接字通信的基本原理接字(Socket)編程(二) 內(nèi)部通信原理坝冕,有需要了解的可以看我前面的兩篇文章梨树。
我們之前寫程序都是在創(chuàng)建好套接字之后(未經(jīng)特殊操作)直接使用的,此時(shí)通過默認(rèn)的套接字特性進(jìn)行同性。之前的示例都較為簡(jiǎn)單纽帖,無需特別操作套接字特性宠漩,但有時(shí)的確需要更改。
一抛计、可設(shè)置套接字的多種可選項(xiàng)
套接字可選項(xiàng)是分層的哄孤,不同的協(xié)議成可設(shè)置的套接字可選項(xiàng)是不一樣的
協(xié)議層 | 功能 |
---|---|
SOL_SOCKET | 套接字相關(guān)通用可選項(xiàng)的設(shè)置 |
IPPROTO_IP | 在IP層設(shè)置套接字的相關(guān)屬性 |
IPPROTO_TCP | 在TCP層設(shè)置套接字相關(guān)屬性 |
下面我們列出套接字三個(gè)協(xié)議層部分可選項(xiàng),并對(duì)其中常用的做下介紹和使用示例
SOL_SOCKET 選項(xiàng)名 | 說明 | 數(shù)據(jù)類型 |
---|---|---|
SO_DEBUG | 打開或關(guān)閉調(diào)試信息 | int |
SO_BROADCAST | 允許或禁止發(fā)送廣播數(shù)據(jù) | int |
SO_DONTROUTE | 打開或關(guān)閉路由查找功能 | int |
SO_ERROR | 獲得套接字錯(cuò)誤 | int |
SO_KEEPALIVE | 開啟套接字贝到兀活機(jī)制 | int |
SO_REUSEADDR | 是否啟用地址再分配,主要原理是操作關(guān)閉套接字的Time-wait時(shí)間等待的開啟和關(guān)閉 | int |
SO_LINGER | 是否開啟延時(shí)關(guān)閉凝危,開啟的情況下調(diào)用 close() 函數(shù)會(huì)被阻塞波俄,同時(shí)可以設(shè)置延遲關(guān)閉的超時(shí)時(shí)間,如果到超時(shí)未發(fā)送完數(shù)據(jù)則直接復(fù)位套接口的虛電路(屬于異常關(guān)閉)蛾默,如果超時(shí)時(shí)間內(nèi)發(fā)送完數(shù)據(jù)則正常關(guān)閉套接字回調(diào) close() 函數(shù) |
struct linger |
SO_TYPE | 獲得套接字類型(這個(gè)只能獲取懦铺,不能設(shè)置) | int |
SO_RCVBUF | 接收緩沖區(qū)大小 | int |
SO_SNDBUF | 發(fā)送緩沖區(qū)大小 | int |
SO_RCVLOWAT | 接收緩沖區(qū)下限 | int |
SO_SNDLOWAT | 發(fā)送緩沖區(qū)下限 | int |
SO_RCVTIMEO | 接收超時(shí) | struct timeval |
SO_SNDTIMEO | 發(fā)送超時(shí) | struct timeval |
IPPROTO_IP 選項(xiàng)名 | 說明 | 數(shù)據(jù)類型 |
---|---|---|
IP_MULTICAST_TTL | 生存時(shí)間(Time To Live),組播傳送距離 | int |
IP_ADD_MEMBERSHIP | 加入組播 | int |
IP_DROP_MEMBERSHIP | 離開組播 | int |
IP_MULTICAST_IF | 取默認(rèn)接口或默認(rèn)設(shè)置 | int |
IP_MULTICAST_LOOP | 禁止組播數(shù)據(jù)回送 | int |
IP_HDRINCL | 在數(shù)據(jù)包中包含IP首部 | int |
IP_OPTINOS | IP首部選項(xiàng) | int |
IPPROTO_TCP 選項(xiàng)名 | 說明 | 數(shù)據(jù)類型 |
---|---|---|
|
TCP敝ЪΓ活機(jī)制開啟下冬念,設(shè)置保活包空閑發(fā)送時(shí)間間隔 | int |
TCP_KEEPINTVL | TCP蹦琳酰活機(jī)制開啟下急前,設(shè)置保活包無響應(yīng)情況下重發(fā)時(shí)間間隔 | int |
TCP_KEEPCNT | TCP逼俟梗活機(jī)制開啟下裆针,設(shè)置保活包無響應(yīng)情況下重復(fù)發(fā)送次數(shù) | int |
TCP_MAXSEG | TCP最大數(shù)據(jù)段的大小 | int |
TCP_NODELAY | 不使用Nagle算法 | int |
二寺晌、getsockopt & setsockopt
我們幾乎可以對(duì)照上面所有可選項(xiàng)進(jìn)行讀取和設(shè)置(當(dāng)然有些套接字只能進(jìn)行其中的一中操作)世吨,可選項(xiàng)的讀取和設(shè)置通過下面兩個(gè)函數(shù)完成
#include <sys/socket.h>
int getsockopt(int sock, //用于查看選項(xiàng)套接字描述符
int level, //要查看可選項(xiàng)的協(xié)議層
int optname, //要查看可選項(xiàng)名字
void *optval, //保存查看結(jié)果的緩沖地址值
socklen_t *optlen //指向第四個(gè)參數(shù)optval傳遞的緩沖大小的指針
)
//成功時(shí)返回0,失敗返回-1
#include <sys/socket.h>
int setsockopt(int sock, //用于更改可選項(xiàng)套接字描述符
int level, //要更改可選項(xiàng)的協(xié)議層
int optname, //要更改可選項(xiàng)名字
void *optval, //要更改可選項(xiàng)的值
socklen_t optlen //第四個(gè)參數(shù)optval傳遞的可選項(xiàng)信息的字節(jié)數(shù)
)
//成功時(shí)返回0呻征,失敗返回-1
三耘婚、應(yīng)用舉例
下面來介紹有關(guān)可選項(xiàng)的設(shè)置讀取和示例,我將從我認(rèn)為重要的往下列舉
1. SO_KEEPALIVE
SO_KEEPALIVE 是否開啟TCP的甭礁常活機(jī)制
平時(shí)開發(fā)中沐祷,常見的對(duì)于面向連接的TCP連接,斷開分兩種情況
①奏甫、連接正常關(guān)閉戈轿,調(diào)用 close()
會(huì)經(jīng)歷斷開的四次握手,send()
阵子、recv()
立馬返回錯(cuò)誤;
②思杯、連接的對(duì)端異常關(guān)閉,比如網(wǎng)絡(luò)斷掉或突然斷電,這種斷開對(duì)方是檢測(cè)不到連接出現(xiàn)異常的
解決這種異常斷開的檢測(cè)機(jī)制一般有兩種:
①色乾、自己在應(yīng)用層定時(shí)發(fā)送心跳包來判斷連接是否正常誊册,此方法比較通用,靈活可控暖璧,但改變了現(xiàn)有的協(xié)議;
②案怯、使用TCP的keepalive機(jī)制,TCP協(xié)議自帶的迸彀欤活功能嘲碱,使用起來簡(jiǎn)單,減少了應(yīng)用層代碼的復(fù)雜度局蚀, 推測(cè)也會(huì)更節(jié)省流量麦锯,因?yàn)橐话銇碚f應(yīng)用層的數(shù)據(jù)傳輸?shù)絽f(xié)議層時(shí)都會(huì)被加上額外的包頭包尾,由TCP協(xié)議提供的檢活琅绅,其發(fā)的探測(cè)包扶欣,理論上實(shí)現(xiàn)的會(huì)更精妙(用更少的字節(jié)完成更多的目標(biāo)),耗費(fèi)更少的流量千扶;
keepalive原理:TCP內(nèi)嵌有心跳包,以服務(wù)端為例料祠,當(dāng)server檢測(cè)到超過一定時(shí)間(tcp_keepalive_time 7200s 即2小時(shí))沒有數(shù)據(jù)傳輸,那么會(huì)向client端發(fā)送一個(gè)keepalive packet澎羞,此時(shí)client端有三種反應(yīng):
- client端連接正常髓绽,返回一個(gè)ACK,server端收到ACK后重置計(jì)時(shí)器煤痕,在2小時(shí)后在發(fā)送探測(cè)(如果2小時(shí)內(nèi)連接上有數(shù)據(jù)傳輸,那么在該時(shí)間的基礎(chǔ)上向后推延2小時(shí)發(fā)送探測(cè)包)
- 客戶端異常關(guān)閉或網(wǎng)絡(luò)斷開梧宫,client無響應(yīng),server收不到ACK摆碉,在一定時(shí)間(
tcp_keepalive_intvl
75 即75秒)后重發(fā)keepalive packet
塘匣, 并且重發(fā)一定次數(shù)(tcp_keepalive_probes
9 即9次),仍然得不到相應(yīng)則返回超時(shí) - 客戶端曾經(jīng)崩潰巷帝,但已經(jīng)重啟忌卤,server收到的探測(cè)響應(yīng)是一個(gè)復(fù)位,server端終止連接
我們可以根據(jù)自己的需求來修改這三個(gè)參數(shù)的系統(tǒng)默認(rèn)值楞泼,下面我們來通過一個(gè)簡(jiǎn)單的回響服務(wù)器代碼來實(shí)現(xiàn)背刍玻活機(jī)制的邏輯實(shí)現(xiàn)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
//這個(gè)頭文件里面包含設(shè)置TCP協(xié)議層保活三個(gè)參數(shù)的可選項(xiàng)
#include <netinet/tcp.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int serv_sockfd, clnt_sockfd;
struct sockaddr_in serv_addr,clnt_addr;
serv_addr.sin_len = sizeof(struct sockaddr_in);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(2000);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (bind(serv_sockfd, (struct sockaddr*)&serv_addr, serv_addr.sin_len) < 0) return 0;
if (listen(serv_sockfd, 1) < 0) return 0;
socklen_t len_t = sizeof(struct sockaddr_in);
clnt_addr.sin_len = len_t;
clnt_sockfd = accept(serv_sockfd, (struct sockaddr*)&clnt_addr, &len_t);
if (clnt_sockfd < 0) return 0;
close(serv_sockfd);
printf("有客戶端連接 IP:%s Port:%d\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
//1. 開啟keepalive機(jī)制
int keepAlive = 1; // 開啟keepalive屬性. 缺省值: 0(關(guān)閉)
if (setsockopt(clnt_sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) == -1)
printf("開啟keepalive機(jī)制失敗 errorCode:%d descreption:%s\n",errno,strerror(errno));
//2. 設(shè)置倍槔活包檢測(cè)發(fā)送時(shí)間間隔(TCP_KEEPIDLE 好像被替換為 TCP_KEEPALIVE)
int keepIdle = 10; // 如果在60秒內(nèi)沒有任何數(shù)據(jù)交互,則進(jìn)行探測(cè). 缺省值:7200(s)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPALIVE, &keepIdle, sizeof(keepIdle)) == -1)
printf("設(shè)置惫鞒В活包檢測(cè)發(fā)送時(shí)間間隔 errorCode:%d descreption:%s\n",errno,strerror(errno));
//3. 設(shè)置保活包發(fā)送無響應(yīng)重發(fā)時(shí)間間隔
int keepInterval = 5; // 探測(cè)時(shí)發(fā)探測(cè)包的時(shí)間間隔為5秒. 缺省值:75(s)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(keepInterval)) == -1)
printf("設(shè)置背剑活包發(fā)送無響應(yīng)重發(fā)時(shí)間間隔 errorCode:%d descreption:%s\n",errno,strerror(errno));
//4. 設(shè)置蔽活包發(fā)送無響應(yīng)重發(fā)次數(shù)
int keepCount = 3; // 探測(cè)重試的次數(shù). 全部超時(shí)則認(rèn)定連接失效..缺省值:9(次)
if (setsockopt(clnt_sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(keepCount)) == -1)
printf("設(shè)置保活包發(fā)送無響應(yīng)重發(fā)次數(shù) errorCode:%d descreption:%s\n",errno,strerror(errno));
char recv_buffer[512];
while (1) {
memset(recv_buffer, 0, 512);
ssize_t recv_len = recv(clnt_sockfd, recv_buffer, sizeof(recv_buffer), 0);
if (recv_len == 0) {
printf("收到數(shù)據(jù)包長(zhǎng)度為0,可能是EOF包 errorCode:%d descreption:%s\n",errno,strerror(errno));
break;
}else if (recv_len < 0) {
printf("讀取數(shù)據(jù)出錯(cuò) errorCode:%d descreption:%s\n",errno,strerror(errno));
break;
}else {
printf("recv: %s\n",recv_buffer);
ssize_t sendLen = send(clnt_sockfd, recv_buffer, recv_len, 0);
if (sendLen < 0) {
printf("發(fā)送失敗 errorCode:%d description:%s\n",errno,strerror(errno));
}else {
printf("send: %s\n",recv_buffer);
}
}
}
printf("服務(wù)器關(guān)閉\n");
close(clnt_sockfd);
}
return 0;
}
對(duì)于運(yùn)行結(jié)果的總結(jié):
上面示例設(shè)置
TCP_KEEPALIVE :設(shè)置闭牌活包空閑發(fā)送時(shí)間間隔為 10s
TCP_KEEPINTVL:設(shè)置本活包無響應(yīng)情況下重發(fā)時(shí)間間隔為 5s
TCP_KEEPCNT:設(shè)置保活包無響應(yīng)情況下重復(fù)發(fā)送次數(shù)為 3次
計(jì)算出來航攒,如果對(duì)方TCP連接異常磺陡,服務(wù)器這邊可以在 10+5*3 = 25
大概25s左右的時(shí)間內(nèi)檢測(cè)出異常,并在讀取數(shù)據(jù)時(shí)拋出errno為60原因?yàn)镺peration timed out的錯(cuò)誤
2. SO_REUSEADDR
SO_REUSEADDR 是否啟用地址再分配漠畜,設(shè)置這個(gè)可選項(xiàng)將影響套接字關(guān)閉的Time-wait狀態(tài)币他,還是比較重要的一個(gè)可選項(xiàng)。
在第二篇文章接字(Socket)編程(二) 內(nèi)部通信原理里面有講到套接字關(guān)閉時(shí)的四次握手原理盆驹,從圖中看出主動(dòng)關(guān)閉的一方在接收到對(duì)方的FIN包后有一個(gè)ACK確認(rèn)圆丹,然后進(jìn)入Time-wait狀態(tài),當(dāng)時(shí)講到這個(gè)Time-wait等待狀態(tài)主要的為了確認(rèn)對(duì)方已經(jīng)收到我們最后一個(gè)ACK確認(rèn)消息躯喇,確認(rèn)收到則關(guān)閉,沒有或者超時(shí)則重發(fā)最后的確認(rèn)包硝枉,上篇文章也詳細(xì)的介紹了為什么有這個(gè)等待狀態(tài)廉丽,這里就不做過多的重說了,需要了解的可以點(diǎn)擊前面連接查看妻味。
PS:這個(gè)Wait-time狀態(tài)只發(fā)生在主動(dòng)斷開連接的一方正压,被斷開一方是沒有這個(gè)狀態(tài)的。
作為服務(wù)器责球,通常都是客戶端主動(dòng)斷開焦履,這個(gè)時(shí)候沒有Wait-time狀態(tài),所以不會(huì)發(fā)生特別的事情雏逾,重啟也不成問題嘉裤,但有的時(shí)候由于自己異常需要重啟,自己作為斷開的一方栖博,系統(tǒng)對(duì)于這個(gè)Socket斷開有Wait-time狀態(tài)屑宠,重新運(yùn)行起來綁定相同的地址和端口就會(huì)出現(xiàn)問題,系統(tǒng)會(huì)返回“bind error”的錯(cuò)誤仇让,因?yàn)檫@個(gè)端口正在被使用典奉,這個(gè)時(shí)候需要Time-wait時(shí)間用完,大概2分鐘丧叽,再運(yùn)行服務(wù)器就可以綁定該端口了卫玖!
但是一般服務(wù)器是不能等這個(gè)時(shí)間的,有用戶在那重啟一次可能已經(jīng)造成損失踊淳,再停個(gè)兩分鐘損失更大假瞬,這個(gè)時(shí)候就需要設(shè)置套接字可選項(xiàng)SO_REUSEADDR值為1,去掉最后的Wait-time時(shí)間。
#include <sys/socket.h>
int enableReuseAddr(int sock)
{
int optval = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
return 0;
}
return 1;
}
3. SO_LINGER
SO_LINGER 此選項(xiàng)從字面意思上來看就是延遲關(guān)閉笨触,我們可以這么理解懦傍,在調(diào)用關(guān)閉套接字的情況下,設(shè)置了此參數(shù)就可以讓系統(tǒng)盡量把緩沖區(qū)的數(shù)據(jù)發(fā)送出去(因?yàn)橛谐瑫r(shí)時(shí)間)芦劣,但是對(duì)于此參數(shù)的設(shè)置分為四種情況粗俱,下面我們來列舉下
①. 設(shè)置結(jié)構(gòu)體里面參數(shù) l_onoff=0
時(shí),為內(nèi)核缺失情況(系統(tǒng)默認(rèn)情況)虚吟,關(guān)閉套接字時(shí)系統(tǒng)正常調(diào)用 close()
函數(shù)
②. 設(shè)置結(jié)構(gòu)體里面參數(shù) l_onoff=1
非0時(shí)寸认,但是參數(shù) l_linger=0
超時(shí)時(shí)間為0時(shí),在關(guān)閉套接字時(shí)會(huì)向?qū)Ψ桨l(fā)送RST信號(hào)給對(duì)方(非正常的四次握手關(guān)閉)串慰,對(duì)方會(huì)讀取到Socket重置的錯(cuò)誤(errno = 45 \* Connection reset by peer *\
)
③. 設(shè)置結(jié)構(gòu)體里面參數(shù) l_onoff=1
非0時(shí)偏塞,但是參數(shù) l_linger
設(shè)置的超時(shí)時(shí)間太小,系統(tǒng)在規(guī)定的超時(shí)時(shí)間內(nèi)沒有將全部的數(shù)據(jù)發(fā)送出去邦鲫,這個(gè)時(shí)候情況和上面第二種情況 l_linger=0
時(shí)是一樣的灸叼,直接重置Socket向?qū)Ψ桨l(fā)送RST信號(hào)(非正常的四次握手關(guān)閉)
④. 設(shè)置結(jié)構(gòu)體里面參數(shù) l_onoff=1
非0時(shí),參數(shù) l_linger
設(shè)置的超時(shí)時(shí)間足夠庆捺,在發(fā)送完全部數(shù)據(jù)后系統(tǒng)正常調(diào)用 close()
函數(shù)正常關(guān)閉
#include <sys/socket.h>
int enableLinger(int sock)
{
struct linger so_linger;
so_linger.l_onoff = 1; //開啟(非0) 關(guān)閉(0)
so_linger.l_linger = 5; //滯留時(shí)間古今,設(shè)置為大于0時(shí)才起作用
if (setsockopt(sock, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)) == -1) {
return 0;
}
return 1;
}
套接字關(guān)閉(主要針對(duì)TCP套接字?jǐn)嚅_)
可能通過上面的介紹,我們理解起來還是感覺哪不對(duì)勁滔以,到底Socket關(guān)閉都經(jīng)歷了什么捉腥?套接字的幾種關(guān)閉方式有什么區(qū)別?設(shè)置超時(shí)和不超時(shí)有什么區(qū)別你画?貌似直接調(diào)用 close()
函數(shù)抵碟,系統(tǒng)也是將數(shù)據(jù)全部發(fā)送給對(duì)方后才關(guān)閉....帶著這些疑問,我們來斷下面的分析
①. cloes函數(shù)關(guān)閉:我們最常用的關(guān)閉套接字方式坏匪,說到這種關(guān)閉我們?cè)俅尾坏貌惶嵯露鄠€(gè)進(jìn)程使用同一個(gè)套接字(我看過一本有關(guān)網(wǎng)絡(luò)編程的書拟逮,里面提到fork出子進(jìn)程來使用同一個(gè)套接字,章標(biāo)題叫編寫多進(jìn)程服務(wù)器端剥槐,書名叫《TCP/IP網(wǎng)絡(luò)編程》是韓國人寫的)唱歧,當(dāng)多個(gè)進(jìn)程使用同一個(gè)Socket時(shí),其中某一個(gè)進(jìn)程調(diào)用 close()
函數(shù)只是使套接字的引用計(jì)數(shù) -1
粒竖,此時(shí)該進(jìn)程無法使用該Socket進(jìn)行讀寫操作颅崩,其他使用該Socket的進(jìn)程可以進(jìn)行正常通信,當(dāng)引用計(jì)數(shù)減到0時(shí)蕊苗,系統(tǒng)才會(huì)開始釋放該套接字沿后,下面我們來說下該函數(shù)關(guān)閉的過程。
系統(tǒng)默認(rèn)(就是在沒有設(shè)置SO_LINGER可選項(xiàng)的情況下)在調(diào)用 close()
時(shí)朽砰,只是將EOF字段(即FIN報(bào)文段)寫進(jìn)套接字緩沖區(qū)尖滚,寫進(jìn)緩沖區(qū)就返回喉刘,這就是說調(diào)用 close()
函數(shù)返回,并不代表已經(jīng)成功將FIN報(bào)文段發(fā)送給對(duì)方(拿A漆弄、B兩個(gè)主機(jī)來舉例)睦裳。
- A主動(dòng)斷開,調(diào)用
close()
函數(shù)將EOF字段(FIN報(bào)文段)寫進(jìn)輸出緩沖撼唾,系統(tǒng)等待前面的數(shù)據(jù)傳送完再將后來加進(jìn)緩沖區(qū)的EOF字段(FIN報(bào)文段)發(fā)送給對(duì)方 - B主機(jī)系統(tǒng)在收到A的FIN報(bào)文段會(huì)立即向?qū)Ψ交厮鸵粋€(gè)ACK確認(rèn)包(表示我收到了你的FIN報(bào)文段)
- B主機(jī)的應(yīng)用層這時(shí)調(diào)用
recv()
函數(shù)會(huì)收到EOF字段(EOF字符長(zhǎng)度為0廉邑,也就是recv()
函數(shù)返回接收到數(shù)據(jù)長(zhǎng)度為0),就知道A要斷開連接了倒谷,自己處理完數(shù)據(jù)也調(diào)用close()
向?qū)Ψ桨l(fā)送FIN報(bào)文段 - A主機(jī)系統(tǒng)在收到對(duì)方的FIN報(bào)文段后蛛蒙,會(huì)向?qū)Ψ桨l(fā)送ACK確認(rèn),確認(rèn)發(fā)送成功之后系統(tǒng)釋放掉該套接字渤愁,B收到ACK后也會(huì)釋放掉要關(guān)閉的套接字牵祟,關(guān)閉到此結(jié)束
上面就是調(diào)用 cloes()
函數(shù)經(jīng)歷的四次握手?jǐn)嚅_,看文字難理解可以結(jié)合我前面的一篇文章里面的套接字?jǐn)嚅_四次握手圖對(duì)比著理解抖格。
②. 套接字設(shè)置了SO_LINGER可選項(xiàng)下诺苹,調(diào)用close函數(shù)關(guān)閉:與第①種情況不同的是,在調(diào)用 close()
函數(shù)并不是立即返回雹拄,超時(shí)時(shí)間足夠的情況下筝尾,會(huì)等待里面所有的數(shù)據(jù)發(fā)送完以及最后調(diào)用 close()
函數(shù)加進(jìn)輸出緩沖的FIN報(bào)文段也發(fā)送給對(duì)方之后,才會(huì)返回办桨,在此之前阻塞了調(diào)用 close()
函數(shù)的線程。
這個(gè)時(shí)候就會(huì)有疑問站辉,這個(gè)可選項(xiàng)會(huì)在什么時(shí)候用到呢呢撞?設(shè)和不設(shè)置SO_LINGER可選項(xiàng)系統(tǒng)都會(huì)將數(shù)據(jù)都發(fā)送給對(duì)方后再釋放套接字,只不過一個(gè)是寫進(jìn)輸出緩沖就返回饰剥,一個(gè)是等發(fā)送成功才返回殊霞。我在開發(fā)中就遇到了一個(gè)必須設(shè)置該可選項(xiàng)的情況,因?yàn)槲沂亲鲋悄芗揖娱_發(fā)方面汰蓉,其中有一種配置單片機(jī)聯(lián)網(wǎng)方式就是連接上單片機(jī)的LAN網(wǎng)絡(luò)绷蹲,這個(gè)時(shí)候手機(jī)跟單片機(jī)在同一個(gè)局域網(wǎng)下,就可以跟單片機(jī)建立TCP連接并將路由器的WiFi名字和密碼發(fā)送給對(duì)方顾孽,單片機(jī)收到后解析完會(huì)給手機(jī)端發(fā)送一個(gè)數(shù)據(jù)包祝钢,表示自己已經(jīng)收到聯(lián)網(wǎng)需要的信息然后重啟自己連上該路由器,但有時(shí)網(wǎng)絡(luò)不好這個(gè)信息并不一定發(fā)給手機(jī)端(單片機(jī)那邊只是調(diào)用 send()
函數(shù)返回后再close()
關(guān)閉套接字)若厚,這時(shí)只能說明數(shù)據(jù)已經(jīng)寫進(jìn)單片機(jī)的輸出緩沖拦英,并不代表已經(jīng)發(fā)送給手機(jī)端,然后緊接著重啟测秸,這時(shí)所有的數(shù)據(jù)都丟失疤估,導(dǎo)致手機(jī)端應(yīng)用層并不知道設(shè)備有沒有收到發(fā)送的指令灾常,也不知道設(shè)備有沒有解析出報(bào)文連接上路由器,此時(shí)如果用SO_LINGER可選項(xiàng)對(duì)套接字進(jìn)行設(shè)置就很容易解決這個(gè)問題铃拇,調(diào)用 close()
函數(shù)直到發(fā)送FIN報(bào)文段成功才會(huì)返回钞瀑。
③. shutdown半關(guān)閉套接字:
從字面意思上我們很容易理解,半關(guān)閉就是要么關(guān)閉套接字的輸入流要么關(guān)閉套接字的輸出流慷荔,要么兩個(gè)都關(guān)閉雕什。
int shutdown(int sock, //需要斷開套接字的文件描述符
int howto //傳遞斷開方式信息
);
//成功時(shí)返回0.失敗返回-1
howto的可選值分別有如下三種
#define SHUT_RD 0 斷開輸入流
#define SHUT_WR 1 斷開輸出流
#define SHUT_RDWR 2 同時(shí)斷開I/O流
①. 填寫 SHUT_RD
斷開輸入流,這時(shí)輸出流還可以接著發(fā)送數(shù)據(jù)拧廊,這時(shí)調(diào)用 recv()
一直返回讀取數(shù)據(jù)長(zhǎng)度為0监徘,第一次是原因是“No such file or directory”,后面都是“Operation timed out”
②. 填寫 SHUT_WR
斷開輸出流吧碾,這時(shí)輸入流還可以接著讀取數(shù)據(jù)凰盔,套接字對(duì)方調(diào)用recv()
一直返回讀取數(shù)據(jù)長(zhǎng)度為0,第一次可以理解為收到對(duì)方的EOF報(bào)文倦春,輸出原因是“No such file or directory”户敬,后面都是“Operation timed out”,這時(shí)自己調(diào)用 send()
函數(shù)會(huì)拋出SIGPIPE信號(hào)量睁本,內(nèi)核缺失情況下項(xiàng)目停止運(yùn)行
③. 填寫 SHUT_RDWR
同事斷開I/O流尿庐,原理上就是先調(diào) SHUT_RD
然后再調(diào)一次 SHUT_WR
shutdown()
函數(shù)與 close()
函數(shù)最大的區(qū)別就是不管大少個(gè)進(jìn)程用同一個(gè)套接字,前者只要其中一個(gè)進(jìn)程調(diào)用了半關(guān)閉呢堰,其他的進(jìn)程使用該套接字也將進(jìn)入半關(guān)閉狀態(tài)
4. SO_RCVTIMEO & SO_SNDTIMEO
SO_RCVTIMEO 設(shè)置套接字讀取數(shù)據(jù)的超時(shí)時(shí)間抄瑟,SO_SNDTIMEO 設(shè)置套接字發(fā)送數(shù)據(jù)的超時(shí)時(shí)間。
#include <sys/socket.h>
int setRecvTimeout(int sock)
{
//設(shè)置套接字讀取數(shù)據(jù)超時(shí)時(shí)間為5s
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) {
return 0;
}
return 1;
}
int setSendTimeout(int sock)
{
//設(shè)置套接字發(fā)送數(shù)據(jù)的超時(shí)時(shí)間為5s
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1) {
return 0;
}
return 1;
}
這個(gè)超時(shí)在平時(shí)的開發(fā)中用來設(shè)置TCP套接字倒是不多枉疼,一般UDP套接字用的比較多皮假,我前面的一篇iOS開發(fā)-TFTP客戶端和服務(wù)器的實(shí)現(xiàn)文章里面在用UDP套接字發(fā)送數(shù)據(jù)給對(duì)方,對(duì)方收到之后會(huì)用UDP回發(fā)一個(gè)ACK包骂维,這個(gè)時(shí)候我們才知道對(duì)方收到了我的上一個(gè)數(shù)據(jù)包惹资,接著發(fā)送下一個(gè)數(shù)據(jù)包,這個(gè)里面就用到了套接字的超時(shí)選項(xiàng)設(shè)置航闺,在規(guī)定的時(shí)間內(nèi)沒有收到對(duì)方的ACK確認(rèn)包褪测,則重發(fā)!UDP套接字發(fā)送數(shù)據(jù)不用建立連接潦刃,所以數(shù)據(jù)發(fā)送出去并不知道對(duì)方是否收到侮措,這樣一應(yīng)一答的通信設(shè)計(jì)用超時(shí)可選項(xiàng)進(jìn)行超時(shí)設(shè)置是再合適不過!
4. SO_RCVBUF & SO_SNDBUF
SO_RCVBUF 設(shè)置或獲取套接字輸入緩沖區(qū)的大小福铅,這個(gè)緩沖區(qū)是我們?cè)趧?chuàng)建套接字時(shí)系統(tǒng)為我們創(chuàng)建好的萝毛,SO_SNDBUF 設(shè)置或獲取套接字輸出緩沖區(qū)的大小
#include <sys/socket.h>
int getRecvBufferSize(int sock)
{
int recvSize;
socklen_t len;
if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvSize, &len) == -1) {
return 0;
}else {
printf("套接字的輸入緩沖大小是: %d\n",recvSize);
}
return recvSize;
}
int setSendBufferSize(int sock)
{
int sendSize;
socklen_t len;
if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendSize, &len) == -1) {
return 0;
}else {
printf("套接字的輸出緩沖大小是: %d\n",sendSize);
}
return sendSize;
}
我自己寫了一個(gè)客戶端的TCP套接字,打印了一下看滑黔,這個(gè)應(yīng)該以系統(tǒng)為準(zhǔn)笆包,系統(tǒng)不一樣分配的大小也不一樣环揽!
套接字的輸入緩沖大小是: 408300
套接字的輸出緩沖大小是: 146988
5. SO_RCVBUF & SO_SNDBUF
SO_RCVLOWAT 可選項(xiàng)用來設(shè)置套接字接收緩沖區(qū)下限,對(duì)于TCP套接字而言庵佣,一般被I/O復(fù)用系統(tǒng)用來判斷套接字是否可讀歉胶,當(dāng)套接字緩沖區(qū)中可讀數(shù)據(jù)總數(shù)大于我們?cè)O(shè)置的接收緩沖區(qū)下限時(shí),I/O復(fù)用系統(tǒng)調(diào)用將通知應(yīng)用程序可以從對(duì)應(yīng)的socket上讀取數(shù)據(jù)(觸發(fā) select()
函數(shù) 或 Linux獨(dú)有的 epoll()
函數(shù))巴粪,調(diào)用 recv()
函數(shù)才會(huì)返回通今,得到輸入緩沖區(qū)中的數(shù)據(jù)。
SO_SNDLOWAT 可選項(xiàng)用來設(shè)置套接字發(fā)送緩沖區(qū)的下限肛根,當(dāng)TCP套接字發(fā)送緩沖區(qū)中空閑空間(可以寫入數(shù)據(jù)的空間)大于設(shè)置的發(fā)送緩沖區(qū)下限時(shí)辫塌,I/O復(fù)用系統(tǒng)調(diào)用將通知應(yīng)用程序可以往對(duì)應(yīng)的socket上寫入數(shù)據(jù),調(diào)用 send()
函數(shù)才有返會(huì)(才能將數(shù)據(jù)寫進(jìn)輸出緩沖中)派哲。
默認(rèn)情況下臼氨,TCP套接字接收緩沖區(qū)的下限和TCP套接字發(fā)送緩沖區(qū)的下限標(biāo)記均為1字節(jié)
#include <sys/socket.h>
int setRcvBufferLowerLimit(int sock)
{
//設(shè)置套接字接收下限為10個(gè)字節(jié)
int opval = 10;
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
int setSendBufferLowerLimit(int sock)
{
//設(shè)置套接字發(fā)送緩沖區(qū)的下限為10個(gè)字節(jié)
int opval = 10;
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
6. SO_TYPE
SO_TYPE 獲取套接字類型,只能用來獲取而不能用來設(shè)置
#include <sys/socket.h>
int getSocketType(int sock)
{
int opval;
socklen_t opvalLen;
if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &opval, &opvalLen) == -1) {
return 0;
}else {
if (opval == SOCK_DGRAM) {
printf("UDP套接字\n");
}else if (opval == SOCK_STREAM) {
printf("TCP套接字\n");
}else {
printf("套接字類型為: %d\n",opval);
}
}
return opval;
}
7. SO_BROADCAST
SO_BROADCAST 允許或禁止發(fā)送廣播數(shù)據(jù)芭届,以及后面有關(guān)多播的參數(shù)設(shè)置(IPPROTO_IP協(xié)議層的IP_MULTICAST_TTL設(shè)置多播傳輸距離储矩,IP_ADD_MEMBERSHIP加入多播組,IP_DROP_MEMBERSHIP離開多播組...)褂乍,都會(huì)在我的下篇文章里面做詳細(xì)的介紹持隧,這里就不做過多累述了。
8. TCP_NODELAY
TCP_NODELAY 是否禁用Nagle算法逃片。
為了防止網(wǎng)絡(luò)數(shù)據(jù)包過多而發(fā)生網(wǎng)絡(luò)過載屡拨,Nagle算法在1984年誕生了,他應(yīng)用于TCP層褥实,非常簡(jiǎn)單洁仗,是否使用差異看下圖
TCP套接字默認(rèn)使用Nagle算法進(jìn)行數(shù)據(jù)交換,因此最大限度的進(jìn)行緩沖性锭,只有收到上個(gè)包的確認(rèn)ACK后才會(huì)傳輸下個(gè)數(shù)據(jù)包。
在不使用Nagle算法的情況下發(fā)送數(shù)據(jù)叫胖,不需要等待收到上個(gè)數(shù)據(jù)包的ACK確認(rèn)草冈,接著就開始發(fā)送下面的數(shù)據(jù),這種情況下會(huì)對(duì)網(wǎng)絡(luò)流量產(chǎn)生極大的負(fù)面影響瓮增,即使只傳輸1個(gè)字節(jié)的數(shù)據(jù)怎棱,其頭信息都有可能是幾十個(gè)字節(jié)。
因此為了提高網(wǎng)絡(luò)傳輸效率绷跑,必須使用Nagle算法拳恋。
但是對(duì)于大文件的網(wǎng)絡(luò)傳輸,有時(shí)不使用Nagle算法能提高傳輸速度
int enableNagel(int sock)
{
//禁用Nagle算法
int opval = 1;
if (setsockopt(sock, SOL_SOCKET, TCP_NODELAY, &opval, sizeof(opval)) == -1) {
return 0;
}
return 1;
}
四砸捏、結(jié)語
對(duì)于套接字可選項(xiàng)的總結(jié)就到這谬运,開發(fā)中常用的都已經(jīng)列舉出來了隙赁,有遺漏的日后會(huì)補(bǔ)上。