小猿圈linux之網(wǎng)絡編程--socket創(chuàng)建

網(wǎng)絡編程離不開socket,小猿圈這篇詳解一下socket創(chuàng)建,仔細學完這篇對你認識網(wǎng)絡底層的東西有著很重要的作用,同時即便有已經(jīng)寫好的模塊用,但是這個東西一定要掌握的,大家一定要認真看。

TCP通信

一個程序使用套接字需要執(zhí)行4個步驟。

--分配套接口和初始化

--連接

--發(fā)送或接收數(shù)據(jù)

--關閉套接字

涉及到的調(diào)用包括socket、bind、listen、connect(阻塞線程)、accept(阻塞線程)、recv(阻塞線程)、send(阻塞線程)。


分配套接口和初始化

--我們需要做的第一件工作就是分配套接口。

--套接口可以看作是文件描述符

--不論server端,還是client端,第一步都是一樣的

每個套接口都是一個通信的通道

兩個進程通過套接口建立連接后就可以發(fā)送和接收數(shù)據(jù)了。


socket()

int socket(int domain.int tyoe,int protocol);

系統(tǒng)調(diào)用socket帶有以下參數(shù)

--int domain

--int tyoe

--int protocol(這個值一般都取0)

--成功返回套接字描述符,失敗返回-1,并設置errno

socket參數(shù)

domain說明

AF_UNIX? ? ? ? UNIX內(nèi)部使用

AF_INET? ? ? ? TCP/IP協(xié)議

AF_ISO? ? ? ? 國際標準組織協(xié)議

AF_NS? ? ? ? Xerox網(wǎng)絡協(xié)議

type說明

SOCK_STREAM? ? 使用TCP可靠連接

SOCK_DGRAM? ? 使用UDP不可靠連接


bind()函數(shù)

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

bind將進程和一個套接口聯(lián)系起來,bind通常用于服務器進程為接入客戶連接建立一個套接口(簡單而言,就是把程序和一個IP地址端口號綁定在一起)。

參數(shù)sockfd是函數(shù)socket調(diào)用返回的套接口。

參數(shù)my_addr是結構sockaddr的地址(用來描述IP地址的一個結構)。

參數(shù)addrlen設置了my_addr能容納的最大字節(jié)數(shù)。

成功返回0,失敗返回-1,并設置errno。

一個端口號只能綁定一個程序,1對1關系

socklen_t本質(zhì)上是unsigned int,并不是int,在windows操作系統(tǒng)下才是int。

對于客戶端程序,下一步是要與之通信的服務器建立連接。

--客戶端只需使用connect即可

對于服務端程序,就是要建立自己的套接口等待來自客戶端的連接。

--服務器需要調(diào)用listen和accept兩個函數(shù)。


listen()函數(shù)

int listen(int sockfd,int backlog);

創(chuàng)建了套接口并且使用bind將它和一個進程關聯(lián)起來以后,服務端就需要調(diào)用listen來監(jiān)聽指定端口的客戶端連接。

參數(shù)sockfd是調(diào)用socket返回的套接口描述符

參數(shù)backlog設置接入隊列的大小,通常把這個值設置的足夠大就可以了。

參數(shù)backlog一般用來設置服務端可以并發(fā)的接收客戶端的最大連接數(shù)目

listen也是從TCP緩存中讀取連接,并非來一個接收一個。

成功返回0,失敗返回-1,并設置errno

listen是將連接直接放到緩存區(qū)里,等待accept函數(shù)來接收


accept()函數(shù)

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

當有客戶端連接到服務端,他們會排入隊列,知道服務端準備好處理他們位置為止,accept會返回一個新的套接口,同時原來的套接口繼續(xù)listen指定端口號

參數(shù)sockfd是調(diào)用socket返回的套接口描述符

參數(shù)addr指向結構sockaddr地址,表示客戶端的IP地址。

參數(shù)addrlen設置了addr能容納的最大字節(jié)數(shù)。

成功返回新的套接字,失敗返回-1,并設置errno


connect()函數(shù)

int connect(int sockfd,const struct sockaddr * serv_addr,socklen_t addrlen);

客戶端調(diào)用connect與服務端進行連接。

參數(shù)sockfd是調(diào)用socket返回的套接口描述符。

參數(shù)addr指向結構sockaddr地址。

參數(shù)addrlen設置了addr能容納的最大字節(jié)數(shù)。

成功返回0,失敗返回-1,并設置errno。

connect()函數(shù)也是阻塞的,它必須完成三次握手機制才能返回。

客戶端和服務端建立了連接就可以在客戶端和服務端之間傳輸數(shù)據(jù)了,需要兩個系統(tǒng)調(diào)用。

--send? ? ? ? 發(fā)送數(shù)據(jù)。

--recv? ? ? ? 接收數(shù)據(jù)。

一個套接口既可以發(fā)送數(shù)據(jù),也可以接收數(shù)據(jù),網(wǎng)絡是一個雙向管道。


send()函數(shù)

ssize_t send(int s,const void *buf,size_t len,int flags);

send函數(shù)用來發(fā)送數(shù)據(jù)。

參數(shù)s是已經(jīng)建立連接的套接口。

參數(shù)buf是發(fā)送數(shù)據(jù)內(nèi)存buffer地址指針。

參數(shù)len指明buffer的大小,單位字節(jié)。

參數(shù)flags一般填0.

成功返回發(fā)送的字節(jié)數(shù),失敗返回-1,并設置errno。


send()函數(shù)注意點

1.send返回值理解

? ? send在阻塞場景下,返回值要么是指定長度(發(fā)送成功),要么是-1,發(fā)送失敗,但是在非阻塞場景下,

返回值可能小于指定長度,這是因為發(fā)送數(shù)據(jù)超過發(fā)送緩沖區(qū)(窗口),所以只能發(fā)送緩沖區(qū)大小的數(shù)據(jù),剩下的數(shù)據(jù)無法發(fā)送

2.errno=11的理解

? ? send在非阻塞場景可能返回-1,并且更新errno為11,11表示資源臨時不可用,當發(fā)送緩沖區(qū)滿了,

而程序不斷在調(diào)用send(0函數(shù)發(fā)送數(shù)據(jù)就會出現(xiàn)這個錯誤,此時收到返回值為-1,并且errno=11時,需要停止發(fā)送數(shù)據(jù),等待套接字下次可寫的時候再發(fā)送數(shù)據(jù)。


recv()函數(shù)

ssize_t recv(int s,void *buf,size_t len,int flags);

recv函數(shù)用來接收數(shù)據(jù)。

參數(shù)s是已經(jīng)建立連接的套接口。

參數(shù)buf是接收數(shù)據(jù)內(nèi)存buffer地址指針。

參數(shù)len指明buffer的大小,單位字節(jié)。

參數(shù)flags一般填0.

成功返回接收到的字節(jié)數(shù),失敗返回-1,并且設置errno,如果對端套接字已經(jīng)關閉,返回0.

recv函數(shù)只是從TCP緩存中讀數(shù)據(jù)(此時數(shù)據(jù)已經(jīng)在自己的電腦上了),不是直接從網(wǎng)絡中讀數(shù)據(jù),什么時候TCP緩存區(qū)滿了,另一邊的send函數(shù)才會停止發(fā)送數(shù)據(jù)。

recv()函數(shù)會阻塞線程,直到收到消息或者客戶端關閉。

最后當你用完套接口以后,就該釋放套接口所占用的資源了,通過close做到這一點

當試圖向一個已經(jīng)關閉的套接口寫或者讀數(shù)據(jù)就會出錯。


setsockopt()函數(shù)

int setsockopt(int s,int level,int optname,const void *optval,socklen optlen);

setsockopt函數(shù)設置套接口

函數(shù)成功返回0,失敗返回-1,并設置errno

常見用法為:

int on=1;

setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

由于TCP套接字狀態(tài)TIME_WAIT引起該套接字關閉后約保留2到4分鐘。在此期間bind綁定該端口失敗。

SO_REUSEADDR指示系統(tǒng)地址可重用。

當服務器在listen()函數(shù)后直接退出,如果這時候有一個客戶端來連接,這個連接會被放在緩存中,如果這時候啟動了一個新的程序綁定這個IP地址和端口號,

那么這新的程序就會接收到上一個客戶端的請求,這是有問題,因為上一個客戶端請求訪問的是原始程序。但是如果我們就一直一個程序綁定這個IP地址,就用不著這個TIME_WAIT信號了,

我們服務器上就一個server綁定這個IP地址和端口號,所以用不著這個機制,因此調(diào)用setsockopt()函數(shù)。

IP地址的結構

ip地址在內(nèi)存中用int表示,int在內(nèi)存中占有4個字節(jié)的空間

第一個字節(jié):192

第二個字節(jié):168

第三個字節(jié):1

第四個字節(jié):2


send()、recv()函數(shù)原理解析

? 通過上一章網(wǎng)絡編程一中的主機之間的通訊圖可以得知,兩主機之間通信,主機A先將消息打包成TCP包,TCP包再打包成IP包,然后形成以太網(wǎng)包發(fā)送,

另一臺主機的接收順序恰好相反,先接受以太網(wǎng)包,再接收IP包,最后接收TCP包,通過程序中測試,recv()函數(shù)一次能夠接受的數(shù)據(jù)(red hat中最大能接收64K數(shù)據(jù))要

比send()函數(shù)發(fā)送的數(shù)據(jù)(red hat中最大發(fā)送的數(shù)據(jù)超過128K),send()函數(shù)是將數(shù)據(jù)打包成TCP包再發(fā)送,而接收數(shù)據(jù)的時候是接收的IP包,再將IP包還原成TCP包,

這說明TCP包的容量實際上會大于或者等于IP包,事實上計算機在發(fā)送數(shù)據(jù)時,如果TCP包過大,會把TCP拆解成多個IP包,并將這些IP包存儲在網(wǎng)卡的緩存區(qū)里(如果send

發(fā)送數(shù)據(jù)超過緩沖區(qū),那么sned()所在的線程就會被掛起),recv在網(wǎng)卡上也有緩存區(qū),網(wǎng)絡發(fā)送過來的數(shù)據(jù)會先存放在緩存區(qū)中,直到緩存區(qū)被填滿,此時發(fā)送方就會停

止發(fā)送,但是此時不意味這send函數(shù)所在的線程就會被掛起,send函數(shù)還可以往自己的緩存區(qū)中發(fā)數(shù)據(jù),直到緩存區(qū)填滿。

在linux進行非阻塞的socket接收數(shù)據(jù)時經(jīng)常出現(xiàn)Resource temporarily unavailable,errno代碼為11(EAGAIN),

這表明你在非阻塞模式下調(diào)用了阻塞操作,在該操作沒有完成就返回這個錯誤,

這個錯誤不會破壞socket的同步,不用管它,下次循環(huán)接著recv就可以。

對非阻塞socket而言,EAGAIN不是一種錯誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。

另外,如果出現(xiàn)EINTR即errno為4,錯誤描述Interrupted system call,該錯誤是由于操作被信號打算,操作也應該繼續(xù)。

最后,如果recv的返回值為0,那表明連接已經(jīng)斷開,我們的接收操作也應該結束。

看到這對大概流程以及概念的東西有了一個深刻了解吧,下一節(jié)小編可以詳細描述一下里面的運作過程,就是通信過程,一接一收,如果有點疑惑的朋友可以去小猿圈尋找一下答案,看懂的朋友可以期待下一次小猿圈的更新!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容