計(jì)算機(jī)基礎(chǔ)知識(shí)——linux socket套接字tcp連接分析

2016.7.4

今天晚上對(duì)項(xiàng)目頂層文件(daemon)進(jìn)行了分析官边,對(duì)其中的TCP連接進(jìn)行具體的代碼級(jí)分析拒逮。

1滩援、需求分析

首先得知道我們這里為什么要用TCP連接玩徊,我們的整個(gè)測(cè)試系統(tǒng)是由上位機(jī)作為客戶端恩袱,發(fā)送測(cè)試文件畔塔,測(cè)試命令給我們測(cè)試程序上,那么我們測(cè)試程序相當(dāng)于作為一個(gè)服務(wù)器端程序寄摆,一直阻塞等待數(shù)據(jù)的到來(lái)婶恼,如果弄清楚了這個(gè)就比較好理解后面的TCP端為什么用的服務(wù)器監(jiān)聽(tīng)函數(shù)勾邦。

對(duì)于整個(gè)測(cè)試系統(tǒng)下發(fā)的分為兩個(gè)部分:測(cè)試查詢命令和測(cè)試腳本參數(shù)文件

對(duì)于查詢命令數(shù)據(jù)量下眷篇,采用不可靠但簡(jiǎn)單的UDP服務(wù)

對(duì)于測(cè)試腳本參數(shù)文件铅歼,其中包含我們測(cè)試的各種參數(shù),需要可靠的傳輸厦幅,采用TCP服務(wù)

2确憨、TCP連接原理分析

TCP通信是通過(guò)網(wǎng)絡(luò)完成服務(wù)器與客戶端的通信休弃,支持可靠傳輸塔猾。TCP/IP協(xié)議存在于OS中丈甸,網(wǎng)絡(luò)服務(wù)通過(guò)OS提供睦擂,在OS中增加支持TCP/IP的系統(tǒng)調(diào)用——Berkeley套接字顿仇,如Socket,Connect囤采,Send斑唬,Recv等

TCP/IP協(xié)議族包括運(yùn)輸層恕刘、網(wǎng)絡(luò)層褐着、鏈路層含蓉,而socket所在位置如圖项郊,Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層着降。

形象的比喻就是:socket就是一個(gè)口袋,一個(gè)洞蓄喇,用戶可以通過(guò)這個(gè)洞直接與網(wǎng)絡(luò)協(xié)議棧打交道妆偏,完成網(wǎng)絡(luò)通信钱骂,基于它就是因?yàn)槌橄蠼涌诜庋b罐柳,簡(jiǎn)單张吉。

簡(jiǎn)單介紹下socket的數(shù)據(jù)結(jié)構(gòu):

1肮蛹、 socket套接字

socket起源于Unix,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”省核,都可以用“打開(kāi)open –> 讀寫(xiě)write/read –>關(guān)閉close”模式來(lái)操作气忠。Socket就是該模式的一個(gè)實(shí)現(xiàn)旧噪,socket即是一種特殊的文件淘钟,一些socket函數(shù)就是對(duì)其進(jìn)行的操作(讀/寫(xiě)IO米母、打開(kāi)铁瞒、關(guān)閉).

說(shuō)白了Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層精拟,它是一組接口。在設(shè)計(jì)模式中笋鄙,Socket其實(shí)就是一個(gè)門(mén)面模式萧落,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面找岖,對(duì)用戶來(lái)說(shuō)许布,一組簡(jiǎn)單的接口就是全部蜜唾,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議擎勘。

注意:其實(shí)socket也沒(méi)有層的概念棚饵,它只是一個(gè)facade設(shè)計(jì)模式的應(yīng)用噪漾,讓編程變的更簡(jiǎn)單怪与。是一個(gè)軟件抽象層分别。在網(wǎng)絡(luò)編程中耘斩,我們大量用的都是通過(guò)socket實(shí)現(xiàn)的括授。

2荚虚、套接字描述符

其實(shí)就是一個(gè)整數(shù)版述,我們最熟悉的句柄是0渴析、1俭茧、2三個(gè)母债,0是標(biāo)準(zhǔn)輸入场斑,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯(cuò)誤輸出喧半。0挺据、1扁耐、2是整數(shù)表示的婉称,對(duì)應(yīng)的FILE *結(jié)構(gòu)的表示就是stdin王暗、stdout俗壹、stderr

套接字API最初是作為UNIX操作系統(tǒng)的一部分而開(kāi)發(fā)的绷雏,所以套接字API與系統(tǒng)的其他I/O設(shè)備集成在一起涎显。特別是棺禾,當(dāng)應(yīng)用程序要為因特網(wǎng)通信而創(chuàng)建一個(gè)套接字(socket)時(shí),操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符(descriptor)來(lái)標(biāo)識(shí)這個(gè)套接字蛀醉。然后拯刁,應(yīng)用程序以該描述符作為傳遞參數(shù)垛玻,通過(guò)調(diào)用函數(shù)來(lái)完成某種操作(例如通過(guò)網(wǎng)絡(luò)傳送數(shù)據(jù)或接收輸入的數(shù)據(jù))帚桩。

在許多操作系統(tǒng)中账嚎,套接字描述符和其他I/O描述符是集成在一起的郭蕉,所以應(yīng)用程序可以對(duì)文件進(jìn)行套接字I/O或I/O讀/寫(xiě)操作召锈。

當(dāng)應(yīng)用程序要?jiǎng)?chuàng)建一個(gè)套接字時(shí)涨岁,操作系統(tǒng)就返回一個(gè)小整數(shù)作為描述符卵惦,應(yīng)用程序則使用這個(gè)描述符來(lái)引用該套接字需要I/O請(qǐng)求的應(yīng)用程序請(qǐng)求操作系統(tǒng)打開(kāi)一個(gè)文件沮尿。操作系統(tǒng)就創(chuàng)建一個(gè)文件描述符提供給應(yīng)用程序訪問(wèn)文件畜疾。從應(yīng)用程序的角度看啡捶,文件描述符是一個(gè)整數(shù)瞎暑,應(yīng)用程序可以用它來(lái)讀寫(xiě)文件了赌。下圖顯示勿她,操作系統(tǒng)如何把文件描述符實(shí)現(xiàn)為一個(gè)指針數(shù)組,這些指針指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)郭卫。

對(duì)于每個(gè)程序系統(tǒng)都有一張單獨(dú)的表贰军。精確地講谓形,系統(tǒng)為每個(gè)運(yùn)行的進(jìn)程維護(hù)一張單獨(dú)的文件描述符表寒跳。當(dāng)進(jìn)程打開(kāi)一個(gè)文件時(shí)童太,系統(tǒng)把一個(gè)指向此文件內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針寫(xiě)入文件描述符表书释,并把該表的索引值返回給調(diào)用者 爆惧。應(yīng)用程序只需記住這個(gè)描述符扯再,并在以后操作該文件時(shí)使用它熄阻。操作系統(tǒng)把該描述符作為索引訪問(wèn)進(jìn)程描述符表秃殉,通過(guò)指針找到保存該文件所有的信息的數(shù)據(jù)結(jié)構(gòu)钾军。

針對(duì)套接字的系統(tǒng)數(shù)據(jù)結(jié)構(gòu):

1)巧颈、套接字API里有個(gè)函數(shù)socket,它就是用來(lái)創(chuàng)建一個(gè)套接字唇礁。套接字設(shè)計(jì)的總體思路是盏筐,單個(gè)系統(tǒng)調(diào)用就可以創(chuàng)建任何套接字琢融,因?yàn)樘捉幼质窍喈?dāng)籠統(tǒng)的漾抬。一旦套接字創(chuàng)建后纳令,應(yīng)用程序還需要調(diào)用其他函數(shù)來(lái)指定具體細(xì)節(jié)平绩。例如調(diào)用socket將創(chuàng)建一個(gè)新的描述符條目:

2)捏雌、雖然套接字的內(nèi)部數(shù)據(jù)結(jié)構(gòu)包含很多字段性湿,但是系統(tǒng)創(chuàng)建套接字后窘奏,大多數(shù)字字段沒(méi)有填寫(xiě)着裹。應(yīng)用程序創(chuàng)建套接字后在該套接字可以使用之前骇扇,必須調(diào)用其他的過(guò)程來(lái)填充這些字段少孝。

3稍走、文件描述符和文件指針的區(qū)別:

文件描述符:在linux系統(tǒng)中打開(kāi)文件就會(huì)獲得文件描述符婿脸,它是個(gè)很小的正整數(shù)狐树。每個(gè)進(jìn)程在PCB(Process Control Block)中保存著一份文件描述符表抑钟,文件描述符就是這個(gè)表的索引在塔,每個(gè)表項(xiàng)都有一個(gè)指向已打開(kāi)文件的指針心俗。

文件指針:C語(yǔ)言中使用文件指針做為I/O的句柄城榛。文件指針指向進(jìn)程用戶區(qū)中的一個(gè)被稱為FILE結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)狠持。FILE結(jié)構(gòu)包括一個(gè)緩沖區(qū)和一個(gè)文件描述符喘垂。而文件描述符是文件描述符表的一個(gè)索引正勒,因此從某種意義上說(shuō)文件指針就是句柄的句柄(在Windows系統(tǒng)上章贞,文件描述符被稱作文件句柄)鸭限。

下面講述下利用socket通信的服務(wù)器端和客戶端的基本結(jié)構(gòu)框圖:

服務(wù)器端先初始化Socket败京,然后與端口綁定(bind)赡麦,對(duì)端口進(jìn)行監(jiān)聽(tīng)(listen),調(diào)用accept阻塞渡冻,等待客戶端連接。在這時(shí)如果有個(gè)客戶端初始化一個(gè)Socket帽借,然后連接服務(wù)器(connect)砍艾,如果連接成功脆荷,這時(shí)客戶端與服務(wù)器端的連接就建立了蜓谋√一溃客戶端發(fā)送數(shù)據(jù)請(qǐng)求观堂,服務(wù)器端接收請(qǐng)求并處理請(qǐng)求师痕,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶端胰坟,客戶端讀取數(shù)據(jù)腕铸,最后關(guān)閉連接狠裹,一次交互結(jié)束涛菠。

3、TCP連接代碼分析(需要說(shuō)明的是:這里以項(xiàng)目的daemon.c代碼為例 )

首先給出定義的一些buf和一些文件數(shù)據(jù)變量:

按照上述紅色框框的程序流程圖牍颈,一步步分析煮岁。

(1)定義socket套接字

socket函數(shù)具體參數(shù)及返回值分析如下:

socket()函數(shù)

int socket(int protofamily, int type, int protocol);//返回sockfd

sockfd是描述符画机。

socket函數(shù)對(duì)應(yīng)于普通文件的打開(kāi)操作步氏。普通文件的打開(kāi)操作返回一個(gè)文件描述字荚醒,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor)界阁,它唯一標(biāo)識(shí)一個(gè)socket铺董。這個(gè)socket描述字跟文件描述字一樣精续,后續(xù)的操作都有用到它重付,把它作為參數(shù)确垫,通過(guò)它來(lái)進(jìn)行一些讀寫(xiě)操作删掀。

正如可以給fopen的傳入不同參數(shù)值披泪,以打開(kāi)不同的文件款票。創(chuàng)建socket的時(shí)候,也可以指定不同的參數(shù)創(chuàng)建不同的socket描述符卡乾,socket函數(shù)的三個(gè)參數(shù)分別為:

  • protofamily:即協(xié)議域幔妨,又稱為協(xié)議族(family)陶冷。常用的協(xié)議族有维蒙,AF_INET(IPV4)、AF_INET6(IPV6)膊毁、AF_LOCAL(或稱AF_UNIX婚温,Unix域socket)栅螟、AF_ROUTE等等力图。協(xié)議族決定了socket的地址類(lèi)型吃媒,在通信中必須采用對(duì)應(yīng)的地址赘那,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合募舟、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址胃珍。
  • type:指定socket類(lèi)型。常用的socket類(lèi)型有钮热,SOCK_STREAM隧期、SOCK_DGRAM仆潮、SOCK_RAW性置、SOCK_PACKET鹏浅、SOCK_SEQPACKET等等(socket的類(lèi)型有哪些隐砸?)季希。
  • protocol:故名思意式塌,就是指定協(xié)議友浸。常用的協(xié)議有尾菇,IPPROTO_TCP派诬、IPPTOTO_UDP默赂、IPPROTO_SCTP、IPPROTO_TIPC等疾捍,它們分別對(duì)應(yīng)TCP傳輸協(xié)議乱豆、UDP傳輸協(xié)議宛裕、STCP傳輸協(xié)議揩尸、TIPC傳輸協(xié)議(這個(gè)協(xié)議我將會(huì)單獨(dú)開(kāi)篇討論Q矣堋)朗恳。

注意:并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合崭庸。當(dāng)protocol為0時(shí)怕享,會(huì)自動(dòng)選擇type類(lèi)型對(duì)應(yīng)的默認(rèn)協(xié)議函筋。

當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí)跌帐,返回的socket描述字它存在于協(xié)議族(address family谨敛,AF_XXX)空間中脸狸,但沒(méi)有一個(gè)具體的地址炊甲。如果想要給它賦值一個(gè)地址,就必須調(diào)用bind()函數(shù)吟吝,否則就當(dāng)調(diào)用connect()爸黄、listen()時(shí)系統(tǒng)會(huì)自動(dòng)隨機(jī)分配一個(gè)端口炕贵。

給出代碼中的實(shí)例

返回的即是套接字的名稱称开,套接字的數(shù)據(jù)結(jié)構(gòu)鳖轰,作為后面函數(shù)的參數(shù)蕴侣。

(2)bind()函數(shù)綁定定義socket的IP地址和端口號(hào)

bind函數(shù)具體參數(shù)及返回值分析如下:

bind()函數(shù)

正如上面所說(shuō)bind()函數(shù)把一個(gè)地址族中的特定地址賦給socket昆雀。例如對(duì)應(yīng)AF_INET狞膘、AF_INET6就是把一個(gè)ipv4或ipv6地址和端口號(hào)組合賦給socket挽封。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函數(shù)的三個(gè)參數(shù)分別為:

  • sockfd:即socket描述字臣镣,它是通過(guò)socket()函數(shù)創(chuàng)建了渠缕,唯一標(biāo)識(shí)一個(gè)socket亦鳞。bind()函數(shù)就是將給這個(gè)描述字綁定一個(gè)名字。
  • addr:一個(gè)const struct sockaddr *指針遭笋,指向要綁定給sockfd的協(xié)議地址瓦呼。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同央串,如ipv4對(duì)應(yīng)的是:
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};
/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

ipv6對(duì)應(yīng)的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

Unix域?qū)?yīng)的是:

#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

addrlen:對(duì)應(yīng)的是地址的長(zhǎng)度。

通常服務(wù)器在啟動(dòng)的時(shí)候都會(huì)綁定一個(gè)眾所周知的地址(如ip地址+端口號(hào))饲宿,用于提供服務(wù)瘫想,客戶就可以通過(guò)它來(lái)接連服務(wù)器昌讲;而客戶端就不用指定短绸,有系統(tǒng)自動(dòng)分配一個(gè)端口號(hào)和自身的ip地址組合鸠按。這就是為什么通常服務(wù)器端在listen之前會(huì)調(diào)用bind()目尖,而客戶端就不會(huì)調(diào)用瑟曲,而是在connect()時(shí)由系統(tǒng)隨機(jī)生成一個(gè)洞拨。

給出代碼中實(shí)例:

從代碼上分析可以知道烦衣,給三個(gè)socket通過(guò)bind綁定了不同的地址和端口號(hào)花吟,需要注意的是還有assert()函數(shù)衅澈,該函數(shù)是檢測(cè)異常今布,終止并打印相應(yīng)信息的函數(shù)部默,具體函數(shù)說(shuō)明如下:

assert()函數(shù)用法總結(jié)

assert宏的原型定義在<assert.h>中甩牺,其作用是如果它的條件返回錯(cuò)誤急但,則終止程序執(zhí)行搞乏,原型定義:

#include <assert.h>
void assert( int expression );

assert的作用是現(xiàn)計(jì)算表達(dá)式 expression 镐躲,如果其值為假(即為0)萤皂,那么它先向stderr打印一條出錯(cuò)信息裆熙,然后通過(guò)調(diào)用 abort 來(lái)終止程序運(yùn)行入录。請(qǐng)看下面的程序清單badptr.c:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
int main( void )
{
FILE *fp;
fp = fopen( "test.txt", "w" );//以可寫(xiě)的方式打開(kāi)一個(gè)文件僚稿,如果不存在就創(chuàng)建一個(gè)同名文件
assert( fp ); //所以這里不會(huì)出錯(cuò)
fclose( fp );
fp = fopen( "noexitfile.txt", "r" );//以只讀的方式打開(kāi)一個(gè)文件蚀同,如果不存在就打開(kāi)文件失敗
assert( fp ); //所以這里出錯(cuò)
fclose( fp ); //程序永遠(yuǎn)都執(zhí)行不到這里來(lái)
return0;
}

[root@localhost error_process]# gcc badptr.c 
[root@localhost error_process]# ./a.out 
a.out: badptr.c:14: main: Assertion `fp' failed.

(3)listen監(jiān)聽(tīng)函數(shù),監(jiān)聽(tīng)端口是否有TCP握手請(qǐng)求

listen()拷恨、connect()函數(shù)

如果作為一個(gè)服務(wù)器,在調(diào)用socket()芦疏、bind()之后就會(huì)調(diào)用listen()來(lái)監(jiān)聽(tīng)這個(gè)socket,如果客戶端這時(shí)調(diào)用connect()發(fā)出連接請(qǐng)求分预,服務(wù)器端就會(huì)接收到這個(gè)請(qǐng)求薪捍。

int listen(int sockfd, int backlog); 
int connect(int sockfd, conststruct sockaddr *addr, socklen_t addrlen);

listen函數(shù)的第一個(gè)參數(shù)即為要監(jiān)聽(tīng)的socket描述字笼痹,第二個(gè)參數(shù)為相應(yīng)socket可以排隊(duì)的最大連接個(gè)數(shù)。socket()函數(shù)創(chuàng)建的socket默認(rèn)是一個(gè)主動(dòng)類(lèi)型的酪穿,listen函數(shù)將socket變?yōu)楸粍?dòng)類(lèi)型的凳干,等待客戶的連接請(qǐng)求。

connect函數(shù)的第一個(gè)參數(shù)即為客戶端的socket描述字被济,第二參數(shù)為服務(wù)器的socket地址救赐,第三個(gè)參數(shù)為socket地址的長(zhǎng)度≈涣祝客戶端通過(guò)調(diào)用connect函數(shù)來(lái)建立與TCP服務(wù)器的連接。

listen函數(shù)較為簡(jiǎn)單轧叽,而且僅僅針對(duì)TCP連接的服務(wù)器端鞠绰,實(shí)例代碼如下:

從上面看出,MAXPENDING表示最大的連接數(shù)驴一,定義的是MAXPENDING=5。

(4)accpet接收函數(shù),接收客戶端的tcp連接

accept()函數(shù)

TCP服務(wù)器端依次調(diào)用socket()趣钱、bind()井联、listen()之后,就會(huì)監(jiān)聽(tīng)指定的socket地址了军掂。TCP客戶端依次調(diào)用socket()率触、connect()之后就向TCP服務(wù)器發(fā)送了一個(gè)連接請(qǐng)求。TCP服務(wù)器監(jiān)聽(tīng)到這個(gè)請(qǐng)求之后皂甘,就會(huì)調(diào)用accept()函數(shù)取接收請(qǐng)求,這樣連接就建立好了嗤锉。之后就可以開(kāi)始網(wǎng)絡(luò)I/O操作了苫幢,即類(lèi)同于普通文件的讀寫(xiě)I/O操作盐数。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回連接connect_fd

參數(shù)sockfd

參數(shù)sockfd就是上面解釋中的監(jiān)聽(tīng)套接字漾峡,這個(gè)套接字用來(lái)監(jiān)聽(tīng)一個(gè)端口且预,當(dāng)有一個(gè)客戶與服務(wù)器連接時(shí),它使用這個(gè)一個(gè)端口號(hào),而此時(shí)這個(gè)端口號(hào)正與這個(gè)套接字關(guān)聯(lián)鼓择。當(dāng)然客戶不知道套接字這些細(xì)節(jié)催跪,它只知道一個(gè)地址和一個(gè)端口號(hào)。

參數(shù)addr

這是一個(gè)結(jié)果參數(shù),它用來(lái)接受一個(gè)返回值,這返回值指定客戶端的地址,當(dāng)然這個(gè)地址是通過(guò)某個(gè)地址結(jié)構(gòu)來(lái)描述的,用戶應(yīng)該知道這一個(gè)什么樣的地址結(jié)構(gòu)魄幕。如果對(duì)客戶的地址不感興趣,那么可以把這個(gè)值設(shè)置為NULL。

參數(shù)len

如同大家所認(rèn)為的,它也是結(jié)果的參數(shù),用來(lái)接受上述addr的結(jié)構(gòu)的大小的,它指明addr結(jié)構(gòu)所占有的字節(jié)個(gè)數(shù)尤勋。同樣的,它也可以被設(shè)置為NULL。

如果accept成功返回,則服務(wù)器與客戶已經(jīng)正確建立連接了,此時(shí)服務(wù)器通過(guò)accept返回的套接字來(lái)完成與客戶的通信。

注意:

accept默認(rèn)會(huì)阻塞進(jìn)程集乔,直到有一個(gè)客戶連接建立后返回汗唱,它返回的是一個(gè)新可用的套接字际插,這個(gè)套接字是連接套接字。此時(shí)我們需要區(qū)分兩種套接字

  1. 監(jiān)聽(tīng)套接字: 監(jiān)聽(tīng)套接字正如accept的參數(shù)sockfd僻焚,它是監(jiān)聽(tīng)套接字咐旧,在調(diào)用listen函數(shù)之后,是服務(wù)器開(kāi)始調(diào)用socket()函數(shù)生成的,稱為監(jiān)聽(tīng)socket描述字(監(jiān)聽(tīng)套接字)

  2. 連接套接字:一個(gè)套接字會(huì)從主動(dòng)連接的套接字變身為一個(gè)監(jiān)聽(tīng)套接字;而accept函數(shù)返回的是已連接socket描述字(一個(gè)連接套接字),它代表著一個(gè)網(wǎng)絡(luò)已經(jīng)存在的點(diǎn)點(diǎn)連接穿挨。

  3. 一個(gè)服務(wù)器通常通常僅僅只創(chuàng)建一個(gè)監(jiān)聽(tīng)socket描述字佛寿,它在該服務(wù)器的生命周期內(nèi)一直存在弹渔。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶連接創(chuàng)建了一個(gè)已連接socket描述字椿胯,當(dāng)服務(wù)器完成了對(duì)某個(gè)客戶的服務(wù)廉油,相應(yīng)的已連接socket描述字就被關(guān)閉十兢。

  4. 自然要問(wèn)的是:為什么要有兩種套接字?原因很簡(jiǎn)單,如果使用一個(gè)描述字的話,那么它的功能太多,使得使用很不直觀,同時(shí)在內(nèi)核確實(shí)產(chǎn)生了一個(gè)這樣的新的描述字。

連接套接字socketfd_new 并沒(méi)有占用新的端口與客戶端通信只洒,依然使用的是與監(jiān)聽(tīng)套接字socketfd一樣的端口號(hào)

client_sd即使返回的新的套接字蔑穴,連接套接字纵朋,通過(guò)連接套接字進(jìn)行數(shù)據(jù)的傳輸聂薪。

但是上述這種方式存在i一個(gè)很大的問(wèn)題!!!!——即如果沒(méi)有tcp請(qǐng)求會(huì)一直阻塞剑刑,而且用戶無(wú)法得知素挽,效率極低。

為了解決上述的辦法,引進(jìn)了套接字監(jiān)控宏函數(shù)扇丛。

直接給出實(shí)例是如何調(diào)用的:

上述用的4個(gè)操作宏到底有什么用购啄,如何定義的呢几迄??、下面進(jìn)行詳細(xì)的介紹:

FD_ZERO盅惜,F(xiàn)D_ISSET這些都是套節(jié)字結(jié)合操作宏 ,看看MSDN上的select函數(shù), 這是在select io模型中的核心,用來(lái)管理套節(jié)字IO的,避免出現(xiàn)無(wú)辜鎖定.

int  select(int nfds,fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout); 

第一個(gè)參數(shù)在windows下可以忽略眠寿,但在linux下必須設(shè)為最大文件描述符加1宁舰;二是結(jié)構(gòu)fd_set在兩個(gè)系統(tǒng)里定義不一樣)低葫。

所以代碼中對(duì)這個(gè)位置也有特別的注意,為什么對(duì)三個(gè)套接字取了最大值:

max_sd表示正在被使用的socket類(lèi)

最后的是超時(shí)標(biāo)準(zhǔn),select是阻塞操作當(dāng)然要設(shè)置超時(shí)事件. 接著的三個(gè)類(lèi)型為fd_set的參數(shù)分別是用于檢查套節(jié)字的可讀性,可寫(xiě)性,和列外數(shù)據(jù)性質(zhì).

我舉個(gè)例子 :比如recv(), 在沒(méi)有數(shù)據(jù)到來(lái)調(diào)用它的時(shí)候,你的線程將被阻塞如果數(shù)據(jù)一直不來(lái),你的線程就要阻塞很久.這樣顯然不好.所以采用select來(lái)查看套節(jié)字是否可讀(也就是是否有數(shù)據(jù)讀了)

步驟如下

socket   s; 
..... 
fd_set   set; 
while(1) 
{     
    FD_ZERO(&set);//將你的套節(jié)字集合清空 
    FD_SET(s,   &set);//加入你感興趣的套節(jié)字到集合,這里是一個(gè)讀數(shù)據(jù)的套節(jié)字s 
    select(0,&set,NULL,NULL,NULL);//檢查套節(jié)字是否可讀, 
                                                      //很多情況下就是是否有數(shù)據(jù)(注意,只是說(shuō)很多情況)
                                                      //這里select是否出錯(cuò)沒(méi)有寫(xiě) 
    if(FD_ISSET(s,   &set)   //檢查s是否在這個(gè)集合里面, 
    {                                           //select將更新這個(gè)集合,把其中不可讀的套節(jié)字去掉 
                                                //只保留符合條件的套節(jié)字在這個(gè)集合里面               
            recv(s,...); 
    } 
    //do   something   here 
}

也就是說(shuō),我們得檢測(cè)我們選定的套接字中此時(shí)可不可讀拾枣,如果不可讀我們就將套接字刷新掉俊啼,不比一直進(jìn)行阻塞等待,等中心建立新的套接字,在進(jìn)行下一輪霎苗,提高之前說(shuō)的阻塞效率昆淡。

(5)recv()函數(shù)接收客戶端傳送的數(shù)據(jù)

先看下recv函數(shù)的原型:

int recv( SOCKET s, char FAR *buf, int len, int flags);   

不論是客戶還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù)鹏氧。該函數(shù)的第一個(gè)參數(shù)指定接收端套接字描述符;
第二個(gè)參數(shù)指明一個(gè)緩沖區(qū)腾窝,該緩沖區(qū)用來(lái)存放recv函數(shù)接收到的數(shù)據(jù)蔗草;
第三個(gè)參數(shù)指明buf的長(zhǎng)度;
第四個(gè)參數(shù)一般置0。

這里只描述同步Socket的recv函數(shù)的執(zhí)行流程昭抒。當(dāng)應(yīng)用程序調(diào)用recv函數(shù)時(shí),

(1)recv先等待s的發(fā)送緩沖中的數(shù)據(jù)被協(xié)議傳送完畢蚓聘,如果協(xié)議在傳送s的發(fā)送緩沖中的數(shù)據(jù)時(shí)出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤急迂,那么recv函數(shù)返回SOCKET_ERROR,

(2)如果s的發(fā)送緩沖中沒(méi)有數(shù)據(jù)或者數(shù)據(jù)被協(xié)議成功發(fā)送完畢后腰吟,recv先檢查套接字s的接收緩沖區(qū),如果s接收緩沖區(qū)中沒(méi)有數(shù)據(jù)或者協(xié)議正在接收數(shù)據(jù)蒿赢,那么recv就一直等待,直到協(xié)議把數(shù)據(jù)接收完畢觉至。當(dāng)協(xié)議把數(shù)據(jù)接收完畢,recv函數(shù)就把s的接收緩沖中的數(shù)據(jù)copy到buf中(注意協(xié)議接收到的數(shù)據(jù)可能大于buf的長(zhǎng)度耿导,所以 在這種情況下要調(diào)用幾次recv函數(shù)才能把s的接收緩沖中的數(shù)據(jù)copy完柿冲。recv函數(shù)僅僅是copy數(shù)據(jù),真正的接收數(shù)據(jù)是協(xié)議來(lái)完成的),

recv函數(shù)返回其實(shí)際copy的字節(jié)數(shù)柠座。如果recv在copy時(shí)出錯(cuò)爆哑,那么它返回SOCKET_ERROR;如果recv函數(shù)在等待協(xié)議接收數(shù)據(jù)時(shí)網(wǎng)絡(luò)中斷了瘪吏,那么它返回0渺尘。

給出代碼中的實(shí)例:

上面說(shuō)了我們可能 一次copy沒(méi)有說(shuō)有的數(shù)據(jù),這時(shí)候我們需要做的就是多次調(diào)用recv函數(shù),很明顯争涌,這里我們不是簡(jiǎn)單的用這一句話完成继蜡,需要對(duì)數(shù)據(jù)流進(jìn)行控制輸出引颈,控制代碼如下:

代碼也不難,分析來(lái)看吉捶,就是一個(gè)while循環(huán),完成這個(gè)buffer(4KB)緩存池的寫(xiě)滿干旧,通過(guò)buffer_len和recv返回copy字節(jié)數(shù)ret進(jìn)行控制闹击,用buffer_len-ret當(dāng)?shù)扔?的時(shí)候也就是填滿了仲义,可以跳出循環(huán)了谣拣。

(6)close()函數(shù)關(guān)閉連接套接字

當(dāng)數(shù)據(jù)傳輸完成后例书,關(guān)閉對(duì)應(yīng)的連接套接字爱谁,則緩存中不會(huì)有其他的數(shù)據(jù)接收,避免下次調(diào)用的時(shí)候復(fù)寫(xiě)走搁。

close()函數(shù)

在服務(wù)器與客戶端建立連接之后躯肌,會(huì)進(jìn)行一些讀寫(xiě)操作拥刻,完成了讀寫(xiě)操作就要關(guān)閉相應(yīng)的socket描述字,好比操作完打開(kāi)的文件要調(diào)用fclose關(guān)閉打開(kāi)的文件徽惋。

#include <unistd.h> 
int close(int fd);

close一個(gè)TCP socket的缺省行為時(shí)把該socket標(biāo)記為以關(guān)閉成黄,然后立即返回到調(diào)用進(jìn)程光绕。該描述字不能再由調(diào)用進(jìn)程使用驯绎,也就是說(shuō)不能再作為read或write的第一個(gè)參數(shù)鞭执。

注意:close操作只是使相應(yīng)socket描述字的引用計(jì)數(shù)-1,只有當(dāng)引用計(jì)數(shù)為0的時(shí)候葵诈,才會(huì)觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請(qǐng)求豌熄。

代碼實(shí)例調(diào)用如下:

很簡(jiǎn)單,沒(méi)有什么好分析的压鉴。

完成以上的6個(gè)動(dòng)作上鞠,最后即將客戶端發(fā)送的4KB數(shù)據(jù)放到buf中骗露,之后的程序就是根據(jù)這4KB的腳本數(shù)據(jù)波附,進(jìn)行后面的測(cè)試工具沾瓦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末满着,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贯莺,更是在濱河造成了極大的恐慌,老刑警劉巖宁改,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缕探,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡还蹲,警方通過(guò)查閱死者的電腦和手機(jī)爹耗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谜喊,“玉大人潭兽,你說(shuō)我怎么就攤上這事《范簦” “怎么了山卦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)诵次。 經(jīng)常有香客問(wèn)我账蓉,道長(zhǎng)枚碗,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任铸本,我火速辦了婚禮肮雨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘箱玷。我一直安慰自己怨规,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布锡足。 她就那樣靜靜地躺著波丰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舱污。 梳的紋絲不亂的頭發(fā)上呀舔,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音扩灯,去河邊找鬼媚赖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛珠插,可吹牛的內(nèi)容都是我干的惧磺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捻撑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼磨隘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起顾患,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤番捂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后江解,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體设预,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年犁河,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳖枕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桨螺,死狀恐怖宾符,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灭翔,我是刑警寧澤魏烫,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響则奥,放射性物質(zhì)發(fā)生泄漏考润。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一读处、第九天 我趴在偏房一處隱蔽的房頂上張望糊治。 院中可真熱鬧,春花似錦罚舱、人聲如沸井辜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粥脚。三九已至,卻和暖如春包个,著一層夾襖步出監(jiān)牢的瞬間刷允,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工碧囊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留树灶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓糯而,卻偏偏與公主長(zhǎng)得像天通,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熄驼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容