Linux SOCKET編程詳解

網(wǎng)絡(luò)中進(jìn)程之間如何通信

為了方便大家獲取源代碼,可以移步這里,?GitHub源代碼

進(jìn)程通信的概念最初來(lái)源于單機(jī)系統(tǒng)。由于每個(gè)進(jìn)程都在自己的地址范圍內(nèi)運(yùn)行,為保證兩個(gè)相互通信的進(jìn)程之間既互不干擾又協(xié)調(diào)一致工作沾谓,操作系統(tǒng)為進(jìn)程通信提供了相應(yīng)設(shè)施委造,如UNIX BSD有:管道(pipe)、命名管道(named pipe)軟中斷信號(hào)(signal)UNIX system V有:消息(message)均驶、共享存儲(chǔ)區(qū)(shared memory)和信號(hào)量(semaphore)等昏兆。

他們都僅限于用在本機(jī)進(jìn)程之間通信。網(wǎng)間進(jìn)程通信要解決的是不同主機(jī)進(jìn)程間的相互通信問(wèn)題(可把同機(jī)進(jìn)程通信看作是其中的特例)妇穴。為此爬虱,首先要解決的是網(wǎng)間進(jìn)程標(biāo)識(shí)問(wèn)題隶债。同一主機(jī)上,不同進(jìn)程可用進(jìn)程號(hào)(process ID)唯一標(biāo)識(shí)跑筝。但在網(wǎng)絡(luò)環(huán)境下死讹,各主機(jī)獨(dú)立分配的進(jìn)程號(hào)不能唯一標(biāo)識(shí)該進(jìn)程。例如曲梗,主機(jī)A賦于某進(jìn)程號(hào)5赞警,在B機(jī)中也可以存在5號(hào)進(jìn)程,因此虏两,“5號(hào)進(jìn)程”這句話就沒(méi)有意義了愧旦。 其次,操作系統(tǒng)支持的網(wǎng)絡(luò)協(xié)議眾多定罢,不同協(xié)議的工作方式不同笤虫,地址格式也不同。因此祖凫,網(wǎng)間進(jìn)程通信還要解決多重協(xié)議的識(shí)別問(wèn)題琼蚯。

其實(shí)TCP/IP協(xié)議族已經(jīng)幫我們解決了這個(gè)問(wèn)題,網(wǎng)絡(luò)層的“ip地址”可以唯一標(biāo)識(shí)網(wǎng)絡(luò)中的主機(jī)蝙场,而傳輸層的“協(xié)議+端口”可以唯一標(biāo)識(shí)主機(jī)中的應(yīng)用程序(進(jìn)程)凌停。這樣利用三元組(ip地址,協(xié)議售滤,端口)就可以標(biāo)識(shí)網(wǎng)絡(luò)的進(jìn)程了罚拟,網(wǎng)絡(luò)中的進(jìn)程通信就可以利用這個(gè)標(biāo)志與其它進(jìn)程進(jìn)行交互。

使用TCP/IP協(xié)議的應(yīng)用程序通常采用應(yīng)用編程接口:UNIX? BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰)完箩,來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)進(jìn)程之間的通信赐俗。就目前而言,幾乎所有的應(yīng)用程序都是采用socket弊知,而現(xiàn)在又是網(wǎng)絡(luò)時(shí)代阻逮,網(wǎng)絡(luò)中進(jìn)程通信是無(wú)處不在,這就是我為什么說(shuō)“一切皆socket”秩彤。

什么是TCP/IP叔扼、UDP

TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協(xié)議/網(wǎng)間協(xié)議,是一個(gè)工業(yè)標(biāo)準(zhǔn)的協(xié)議集漫雷,它是為廣域網(wǎng)(WANs)設(shè)計(jì)的瓜富。

TCP/IP協(xié)議存在于OS中,網(wǎng)絡(luò)服務(wù)通過(guò)OS提供降盹,在OS中增加支持TCP/IP的系統(tǒng)調(diào)用——Berkeley套接字与柑,如Socket,Connect,Send价捧,Recv等

UDP(User Data Protocol丑念,用戶(hù)數(shù)據(jù)報(bào)協(xié)議)是與TCP相對(duì)應(yīng)的協(xié)議。它是屬于TCP/IP協(xié)議族中的一種结蟋。如圖:

網(wǎng)絡(luò)傳輸協(xié)議概貌
Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層

Socket是什么

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ì)用戶(hù)來(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)程用戶(hù)區(qū)中的一個(gè)被稱(chēng)為FILE結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)霜运。FILE結(jié)構(gòu)包括一個(gè)緩沖區(qū)和一個(gè)文件描述符。而文件描述符是文件描述符表的一個(gè)索引蒋腮,因此從某種意義上說(shuō)文件指針就是句柄的句柄(在Windows系統(tǒng)上淘捡,文件描述符被稱(chēng)作文件句柄)。

基本的SOCKET接口函數(shù)

在生活中池摧,A要電話給B焦除,A撥號(hào),B聽(tīng)到電話鈴聲后提起電話作彤,這時(shí)A和B就建立起了連接膘魄,A和B就可以講話了。等交流結(jié)束竭讳,掛斷電話結(jié)束此次交談创葡。? 打電話很簡(jiǎn)單解釋了這工作原理:“open—write/read—close”模式。

服務(wù)器端先初始化Socket绢慢,然后與端口綁定(bind)灿渴,對(duì)端口進(jìn)行監(jiān)聽(tīng)(listen),調(diào)用accept阻塞胰舆,等待客戶(hù)端連接骚露。在這時(shí)如果有個(gè)客戶(hù)端初始化一個(gè)Socket,然后連接服務(wù)器(connect)缚窿,如果連接成功棘幸,這時(shí)客戶(hù)端與服務(wù)器端的連接就建立了【肓悖客戶(hù)端發(fā)送數(shù)據(jù)請(qǐng)求误续,服務(wù)器端接收請(qǐng)求并處理請(qǐng)求吨悍,然后把回應(yīng)數(shù)據(jù)發(fā)送給客戶(hù)端,客戶(hù)端讀取數(shù)據(jù)蹋嵌,最后關(guān)閉連接畜份,一次交互結(jié)束。? **這些接口的實(shí)現(xiàn)都是內(nèi)核來(lái)完成欣尼。具體如何實(shí)現(xiàn),可以看看linux的內(nèi)核**

4.1停蕉、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é)議域,又稱(chēng)為協(xié)議族(family)荒辕。常用的協(xié)議族有汗销,AF_INET(IPV4)、AF_INET6(IPV6)抵窒、AF_LOCAL(或稱(chēng)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)篇討論G裳肌)。

注意:并不是上面的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è)端口。

4.2歌豺、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)的是:

structsockaddr_in{sa_family_tsin_family;/* address family: AF_INET */in_port_tsin_port;/* port in network byte order */structin_addrsin_addr;/* internet address */};/* Internet address. */structin_addr{uint32_ts_addr;/* address in network byte order */};ipv6對(duì)應(yīng)的是:structsockaddr_in6{sa_family_tsin6_family;/* AF_INET6 */in_port_tsin6_port;/* port number */uint32_tsin6_flowinfo;/* IPv6 flow information */structin6_addrsin6_addr;/* IPv6 address */uint32_tsin6_scope_id;/* Scope ID (new in 2.4) */};structin6_addr{unsignedchars6_addr[16];/* IPv6 address */};Unix域?qū)?yīng)的是:#defineUNIX_PATH_MAX? ? 108structsockaddr_un{sa_family_tsun_family;/* AF_UNIX */charsun_path[UNIX_PATH_MAX];/* pathname */};

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

通常服務(wù)器在啟動(dòng)的時(shí)候都會(huì)綁定一個(gè)眾所周知的地址(如ip地址+端口號(hào))堕虹,用于提供服務(wù)卧晓,客戶(hù)就可以通過(guò)它來(lái)接連服務(wù)器芬首;而客戶(hù)端就不用指定,有系統(tǒng)自動(dòng)分配一個(gè)端口號(hào)和自身的ip地址組合逼裆。這就是為什么通常服務(wù)器端在listen之前會(huì)調(diào)用bind()郁稍,而客戶(hù)端就不會(huì)調(diào)用,而是在connect()時(shí)由系統(tǒng)隨機(jī)生成一個(gè)胜宇。

網(wǎng)絡(luò)字節(jié)序與主機(jī)字節(jié)序

主機(jī)字節(jié)序就是我們平常說(shuō)的大端和小端模式:不同的CPU有不同的字節(jié)序類(lèi)型耀怜,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序,這個(gè)叫做主機(jī)序桐愉。引用標(biāo)準(zhǔn)的Big-Endian和Little-Endian的定義如下:

a) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端财破,高位字節(jié)排放在內(nèi)存的高地址端。

b) Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端仅财,低位字節(jié)排放在內(nèi)存的高地址端。

網(wǎng)絡(luò)字節(jié)序:4個(gè)字節(jié)的32 bit值以下面的次序傳輸:首先是0~7bit碗淌,其次8~15bit盏求,然后16~23bit,最后是24~31bit亿眠。這種傳輸次序稱(chēng)作大端字節(jié)序碎罚。由于TCP/IP首部中所有的二進(jìn)制整數(shù)在網(wǎng)絡(luò)中傳輸時(shí)都要求以這種次序,因此它又稱(chēng)作網(wǎng)絡(luò)字節(jié)序纳像。字節(jié)序荆烈,顧名思義字節(jié)的順序,就是大于一個(gè)字節(jié)類(lèi)型的數(shù)據(jù)在內(nèi)存中的存放順序,一個(gè)字節(jié)的數(shù)據(jù)沒(méi)有順序的問(wèn)題了。

所以:在將一個(gè)地址綁定到socket的時(shí)候阶冈,請(qǐng)先將主機(jī)字節(jié)序轉(zhuǎn)換成為網(wǎng)絡(luò)字節(jié)序冕房,而不要假定主機(jī)字節(jié)序跟網(wǎng)絡(luò)字節(jié)序一樣使用的是Big-Endian。由于這個(gè)問(wèn)題曾引發(fā)過(guò)血案轿曙!公司項(xiàng)目代碼中由于存在這個(gè)問(wèn)題,導(dǎo)致了很多莫名其妙的問(wèn)題,所以請(qǐng)謹(jǐn)記對(duì)主機(jī)字節(jié)序不要做任何假定屎飘,務(wù)必將其轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序再賦給socket。

4.3贾费、listen()钦购、connect()函數(shù)

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

int listen(int sockfd, int backlog);int connect(int sockfd, const struct 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)型的波丰,等待客戶(hù)的連接請(qǐng)求壳坪。

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

4.4蝎亚、accept()函數(shù)

TCP服務(wù)器端依次調(diào)用socket()、bind()先馆、listen()之后发框,就會(huì)監(jiān)聽(tīng)指定的socket地址了。TCP客戶(hù)端依次調(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è)客戶(hù)與服務(wù)器連接時(shí)球涛,它使用這個(gè)一個(gè)端口號(hào)劣针,而此時(shí)這個(gè)端口號(hào)正與這個(gè)套接字關(guān)聯(lián)。當(dāng)然客戶(hù)不知道套接字這些細(xì)節(jié)亿扁,它只知道一個(gè)地址和一個(gè)端口號(hào)酿秸。

參數(shù)addr

這是一個(gè)結(jié)果參數(shù),它用來(lái)接受一個(gè)返回值魏烫,這返回值指定客戶(hù)端的地址辣苏,當(dāng)然這個(gè)地址是通過(guò)某個(gè)地址結(jié)構(gòu)來(lái)描述的,用戶(hù)應(yīng)該知道這一個(gè)什么樣的地址結(jié)構(gòu)哄褒。如果對(duì)客戶(hù)的地址不感興趣稀蟋,那么可以把這個(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ù)器與客戶(hù)已經(jīng)正確建立連接了茫藏,此時(shí)服務(wù)器通過(guò)accept返回的套接字來(lái)完成與客戶(hù)的通信误趴。

注意:

accept默認(rèn)會(huì)阻塞進(jìn)程,直到有一個(gè)客戶(hù)連接建立后返回务傲,它返回的是一個(gè)新可用的套接字凉当,這個(gè)套接字是連接套接字。

此時(shí)我們需要區(qū)分兩種套接字售葡,

監(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ù)生成的楼雹,稱(chēng)為監(jiān)聽(tīng)socket描述字(監(jiān)聽(tīng)套接字)

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

一個(gè)服務(wù)器通常通常僅僅只創(chuàng)建一個(gè)監(jiān)聽(tīng)socket描述字,它在該服務(wù)器的生命周期內(nèi)一直存在诺祸。內(nèi)核為每個(gè)由服務(wù)器進(jìn)程接受的客戶(hù)連接創(chuàng)建了一個(gè)已連接socket描述字携悯,當(dāng)服務(wù)器完成了對(duì)某個(gè)客戶(hù)的服務(wù)祭芦,相應(yīng)的已連接socket描述字就被關(guān)閉筷笨。

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

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

4.5蚕愤、read()答恶、write()等函數(shù)

萬(wàn)事具備只欠東風(fēng),至此服務(wù)器與客戶(hù)已經(jīng)建立好連接了萍诱⌒ぃ可以調(diào)用網(wǎng)絡(luò)I/O進(jìn)行讀寫(xiě)操作了,即實(shí)現(xiàn)了網(wǎng)咯中不同進(jìn)程之間的通信裕坊!網(wǎng)絡(luò)I/O操作有下面幾組:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

我推薦使用recvmsg()/sendmsg()函數(shù)包竹,這兩個(gè)函數(shù)是最通用的I/O函數(shù),實(shí)際上可以把上面的其它函數(shù)都替換成這兩個(gè)函數(shù)。它們的聲明如下:

#includessize_tread(intfd,void*buf,size_tcount);ssize_twrite(intfd,constvoid*buf,size_tcount);#include#includessize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);ssize_trecv(intsockfd,void*buf,size_tlen,intflags);ssize_tsendto(intsockfd,constvoid*buf,size_tlen,intflags,conststruct sockaddr *dest_addr,socklen_taddrlen);ssize_trecvfrom(intsockfd,void*buf,size_tlen,intflags,? ? ? ? ? ? ? ? ? ? ? ? struct sockaddr *src_addr,socklen_t*addrlen);ssize_tsendmsg(intsockfd,conststruct msghdr *msg,intflags);ssize_trecvmsg(intsockfd, struct msghdr *msg,intflags);

read函數(shù)是負(fù)責(zé)從fd中讀取內(nèi)容.當(dāng)讀成功時(shí)周瞎,read返回實(shí)際所讀的字節(jié)數(shù)苗缩,如果返回的值是0表示已經(jīng)讀到文件的結(jié)束了,小于0表示出現(xiàn)了錯(cuò)誤声诸。如果錯(cuò)誤為EINTR說(shuō)明讀是由中斷引起的酱讶,如果是ECONNREST表示網(wǎng)絡(luò)連接出了問(wèn)題。

write函數(shù)將buf中的nbytes字節(jié)內(nèi)容寫(xiě)入文件描述符fd.成功時(shí)返回寫(xiě)的字節(jié)數(shù)双絮。失敗時(shí)返回-1浴麻,并設(shè)置errno變量。 在網(wǎng)絡(luò)程序中囤攀,當(dāng)我們向套接字文件描述符寫(xiě)時(shí)有倆種可能软免。1)write的返回值大于0,表示寫(xiě)了部分或者是全部的數(shù)據(jù)焚挠。2)返回的值小于0膏萧,此時(shí)出現(xiàn)了錯(cuò)誤。我們要根據(jù)錯(cuò)誤類(lèi)型來(lái)處理蝌衔。如果錯(cuò)誤為EINTR表示在寫(xiě)的時(shí)候出現(xiàn)了中斷錯(cuò)誤榛泛。如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問(wèn)題(對(duì)方已經(jīng)關(guān)閉了連接)。

其它的我就不一一介紹這幾對(duì)I/O函數(shù)了噩斟,具體參見(jiàn)man文檔或者baidu曹锨、Google,下面的例子中將使用到send/recv剃允。

4.6沛简、close()函數(shù)

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

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客戶(hù)端向服務(wù)器發(fā)送終止連接請(qǐng)求饲窿。

Socket編程實(shí)例

服務(wù)器端:一直監(jiān)聽(tīng)本機(jī)的8888號(hào)端口煌寇,如果收到連接請(qǐng)求,將接收請(qǐng)求并接收客戶(hù)端發(fā)來(lái)的消息免绿,并向客戶(hù)端返回消息唧席。

服務(wù)器代碼

/*************************************************************************

> File Name: ser.c

> Author: battle

> Mail: batbattle@163.com?

> Created Time: 2018年06月02日 星期六 11時(shí)21分02秒 ************************************************************************/

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<unistd.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#define PORT 8888

#define BACKLOG 10

#define MAXDATASIZE? 2048

int main(int argc, char *argv[])

{

? ? int listenfd;

? ? //創(chuàng)建一個(gè)socket描述符,此描述符僅是本主機(jī)上的一個(gè)普通文件描述符而已

? ? listenfd = socket(AF_INET, SOCK_STREAM, 0);

? ? printf("listenfd=%d\n", listenfd);

? ? //定義一個(gè)結(jié)構(gòu)體變量servaddr,用來(lái)記錄給定的IP和port信息淌哟,為bind函數(shù)做準(zhǔn)備

? ? struct sockaddr_in serveraddr;

? ? bzero(&serveraddr, sizeof(serveraddr));

? ? serveraddr.sin_family = AF_INET;

? ? serveraddr.sin_port = htons(PORT); //把端口轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序迹卢,即大端模式

? ? serveraddr.sin_addr.s_addr = INADDR_ANY;

? ? //把“本地含義的描述符”綁定到一個(gè)IP和Port上,此時(shí)這個(gè)socket才具備對(duì)外連接的能力

? ? bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

? ? //創(chuàng)建一個(gè)監(jiān)聽(tīng)隊(duì)列徒仓,用來(lái)保存用戶(hù)的請(qǐng)求連接信息(ip腐碱、port、protocol)

? ? listen(listenfd, BACKLOG);

? ? printf("======bind success,waiting for client's request======\n");

? ? //讓操作系統(tǒng)回填client的連接信息(ip掉弛、port症见、protocol)

? ? struct sockaddr_in peeraddr;

? ? socklen_t peer_len = sizeof(peeraddr);

? ? int connfd;

? ? while(1)

? ? {

? ? ? ? //accept函數(shù)從listen函數(shù)維護(hù)的監(jiān)聽(tīng)隊(duì)列里取一個(gè)客戶(hù)連接請(qǐng)求處理

? ? ? ? connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);

? ? ? ? printf("\n=====================客戶(hù)端鏈接成功=====================\n");

? ? ? ? printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

? ? ? ? char buf[MAXDATASIZE];

? ? ? ? while(1)

? ? ? ? {

? ? ? ? ? ? memset(buf, '\0', MAXDATASIZE/sizeof? (char));

? ? ? ? ? ? int recv_length = recv(connfd, buf, MAXDATASIZE/sizeof (char), 0);

? ? ? ? ? ? if(recv_length == 0)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? printf("client has closed!\n");

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }

? ? ? ? ? ? printf("client say: ");

? ? ? ? ? ? fputs(buf, stdout);

? ? ? ? ? ? memset(buf, '\0', MAXDATASIZE/sizeof (char));

? ? ? ? ? ? printf("input: ");

? ? ? ? ? ? fgets(buf, sizeof(buf), stdin);

? ? ? ? ? ? send(connfd, buf, recv_length, 0);

? ? ? ? }

? ? ? ? close(connfd);

? ? ? ? close(listenfd);

? ? ? ? return 0;

}}


客戶(hù)端代碼實(shí)例

/*************************************************************************

> File Name: cli.c

> Author: battle

> Mail: batbattle@163.com?

> Created Time: 2018年06月02日 星期六 10時(shí)55分37秒

?************************************************************************/

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<unistd.h>

#include<netinet/in.h>

#define PORT 8888

#define MAXDATASIZE 2048

int main(int argc, char *argv[])

{

? ? if(argc != 2)

? ? {

? ? ? ? fprintf(stderr, "老鐵,請(qǐng)輸入想連接的服務(wù)器IP地址!\n");

? ? ? ? exit(1);

? ? }

? ? int sockfd;

? ? sockfd = socket(AF_INET, SOCK_STREAM, 0);

? ? const char *server_ip = argv[1]; //從命令行獲取輸入的ip地址

? ? struct sockaddr_in serveraddr;

? ? bzero(&serveraddr, sizeof(serveraddr));

? ? serveraddr.sin_family = AF_INET;

? ? serveraddr.sin_port = htons(PORT);

? ? inet_pton(AF_INET, server_ip, &serveraddr.sin_addr);

? ? connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

? ? printf("=====================服務(wù)器鏈接成功=====================\n");

? ? char buf[MAXDATASIZE];

? ? memset(buf, 0 ,? sizeof(buf));

? ??printf("input: ");

? ? while(fgets(buf, sizeof(buf), stdin) != NULL && (strcmp(buf, "quit")))

? ? {

? ? ? ? send(sockfd, buf, sizeof(buf), 0);

? ? ? ? memset(buf, 0, sizeof(buf));

? ? ? ? recv(sockfd, buf, sizeof(buf), 0);

? ? ? ? printf("server say: ");

? ? ? ? fputs(buf, stdout);

? ? ? ? memset(buf, 0, sizeof(buf));

? ? ? ? printf("input: ");

? ? }

? ? printf("client will be closed, see you next time.\n");

? ? close(sockfd);

? ? return 0;

}


編譯連接

gcc -o cli cli.c &&?gcc -o ser ser.c

兩個(gè)終端上先運(yùn)行服務(wù)器再運(yùn)行客戶(hù)端

./ser

./cli 127.0.0.1


運(yùn)行結(jié)果

cli:同學(xué)殃饿,走谋作,一起去克利夫蘭支持詹姆斯干掉勇士隊(duì)!

ser:算了算了乎芳,我還要準(zhǔn)備去準(zhǔn)備網(wǎng)易云課堂上的【劍指BAT】安排的下一次網(wǎng)絡(luò)編程課程呢遵蚜,等我加入了一線企業(yè),賺高薪了奈惑,哥帶你飛

cli:醬紫啊吭净,哥哥要繼續(xù)加油哦,看好你肴甸,拜拜

ser:好滴寂殉,等我閉關(guān)修煉5個(gè)月,我們?cè)僖?jiàn)原在,拜拜

服務(wù)器運(yùn)行結(jié)果
客戶(hù)端運(yùn)行結(jié)果

為了方便大家獲取源代碼友扰,可以移步這里,?GitHub源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晤斩,一起剝皮案震驚了整個(gè)濱河市焕檬,隨后出現(xiàn)的幾起案子姆坚,更是在濱河造成了極大的恐慌澳泵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兼呵,死亡現(xiàn)場(chǎng)離奇詭異兔辅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)击喂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)维苔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人懂昂,你說(shuō)我怎么就攤上這事介时。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵沸柔,是天一觀的道長(zhǎng)循衰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褐澎,這世上最難降的妖魔是什么会钝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮工三,結(jié)果婚禮上迁酸,老公的妹妹穿的比我還像新娘。我一直安慰自己俭正,他們只是感情好奸鬓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著掸读,像睡著了一般全蝶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寺枉,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天抑淫,我揣著相機(jī)與錄音,去河邊找鬼姥闪。 笑死始苇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的筐喳。 我是一名探鬼主播催式,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼避归!你這毒婦竟也來(lái)了荣月?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梳毙,失蹤者是張志新(化名)和其女友劉穎哺窄,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體账锹,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萌业,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奸柬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片生年。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖廓奕,靈堂內(nèi)的尸體忽然破棺而出抱婉,到底是詐尸還是另有隱情档叔,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布蒸绩,位于F島的核電站蹲蒲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侵贵。R本人自食惡果不足惜届搁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窍育。 院中可真熱鬧卡睦,春花似錦、人聲如沸漱抓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乞娄。三九已至瞬逊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仪或,已是汗流浹背确镊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留范删,地道東北人蕾域。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像到旦,于是被迫代替她去往敵國(guó)和親旨巷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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