小猿圈linux之網路程式設計--socket建立
網路程式設計離不開socket,小猿圈這篇詳解一下socket建立,仔細學完這篇對你認識網路底層的東西有著很重要的作用,同時即便有已經寫好的模組用,但是這個東西一定要掌握的,大家一定要認真看。
TCP通訊
一個程式使用套接字需要執行4個步驟。
--分配套介面和初始化
--連線
--傳送或接收資料
--關閉套接字
涉及到的呼叫包括socket、bind、listen、connect(阻塞執行緒)、accept(阻塞執行緒)、recv(阻塞執行緒)、send(阻塞執行緒)。
分配套介面和初始化
--我們需要做的第一件工作就是分配套介面。
--套介面可以看作是檔案描述符
--不論server端,還是client端,第一步都是一樣的
每個套介面都是一個通訊的通道
兩個程序通過套介面建立連線後就可以傳送和接收資料了。
socket()
int socket(int domain.int tyoe,int protocol);
系統呼叫socket帶有以下引數
--int domain
--int tyoe
--int protocol(這個值一般都取0)
--成功返回套接字描述符,失敗返回-1,並設定errno
socket引數
domain說明
AF_UNIX UNIX內部使用
AF_INET TCP/IP協議
AF_ISO 國際標準組織協議
AF_NS Xerox網路協議
type說明
SOCK_STREAM 使用TCP可靠連線
SOCK_DGRAM 使用UDP不可靠連線
bind()函式
int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen);
bind將程序和一個套介面聯絡起來,bind通常用於伺服器程序為接入客戶連線建立一個套介面(簡單而言,就是把程式和一個IP地址埠號繫結在一起)。
引數sockfd是函式socket呼叫返回的套介面。
引數my_addr是結構sockaddr的地址(用來描述IP地址的一個結構)。
引數addrlen設定了my_addr能容納的最大位元組數。
成功返回0,失敗返回-1,並設定errno。
一個埠號只能繫結一個程式,1對1關係
socklen_t本質上是unsigned int,並不是int,在windows作業系統下才是int。
對於客戶端程式,下一步是要與之通訊的伺服器建立連線。
--客戶端只需使用connect即可
對於服務端程式,就是要建立自己的套介面等待來自客戶端的連線。
--伺服器需要呼叫listen和accept兩個函式。
listen()函式
int listen(int sockfd,int backlog);
建立了套介面並且使用bind將它和一個程序關聯起來以後,服務端就需要呼叫listen來監聽指定埠的客戶端連線。
引數sockfd是呼叫socket返回的套介面描述符
引數backlog設定接入佇列的大小,通常把這個值設定的足夠大就可以了。
引數backlog一般用來設定服務端可以併發的接收客戶端的最大連線數目
listen也是從TCP快取中讀取連線,並非來一個接收一個。
成功返回0,失敗返回-1,並設定errno
listen是將連線直接放到快取區裡,等待accept函式來接收
accept()函式
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
當有客戶端連線到服務端,他們會排入佇列,知道服務端準備好處理他們位置為止,accept會返回一個新的套介面,同時原來的套介面繼續listen指定埠號
引數sockfd是呼叫socket返回的套介面描述符
引數addr指向結構sockaddr地址,表示客戶端的IP地址。
引數addrlen設定了addr能容納的最大位元組數。
成功返回新的套接字,失敗返回-1,並設定errno
connect()函式
int connect(int sockfd,const struct sockaddr * serv_addr,socklen_t addrlen);
客戶端呼叫connect與服務端進行連線。
引數sockfd是呼叫socket返回的套介面描述符。
引數addr指向結構sockaddr地址。
引數addrlen設定了addr能容納的最大位元組數。
成功返回0,失敗返回-1,並設定errno。
connect()函式也是阻塞的,它必須完成三次握手機制才能返回。
客戶端和服務端建立了連線就可以在客戶端和服務端之間傳輸資料了,需要兩個系統呼叫。
--send 傳送資料。
--recv 接收資料。
一個套介面既可以傳送資料,也可以接收資料,網路是一個雙向管道。
send()函式
ssize_t send(int s,const void *buf,size_t len,int flags);
send函式用來發送資料。
引數s是已經建立連線的套介面。
引數buf是傳送資料記憶體buffer地址指標。
引數len指明buffer的大小,單位位元組。
引數flags一般填0.
成功返回傳送的位元組數,失敗返回-1,並設定errno。
send()函式注意點
1.send返回值理解
send在阻塞場景下,返回值要麼是指定長度(傳送成功),要麼是-1,傳送失敗,但是在非阻塞場景下,
返回值可能小於指定長度,這是因為傳送資料超過傳送緩衝區(視窗),所以只能傳送緩衝區大小的資料,剩下的資料無法傳送
2.errno=11的理解
send在非阻塞場景可能返回-1,並且更新errno為11,11表示資源臨時不可用,當傳送緩衝區滿了,
而程式不斷在呼叫send(0函式傳送資料就會出現這個錯誤,此時收到返回值為-1,並且errno=11時,需要停止傳送資料,等待套接字下次可寫的時候再發送資料。
recv()函式
ssize_t recv(int s,void *buf,size_t len,int flags);
recv函式用來接收資料。
引數s是已經建立連線的套介面。
引數buf是接收資料記憶體buffer地址指標。
引數len指明buffer的大小,單位位元組。
引數flags一般填0.
成功返回接收到的位元組數,失敗返回-1,並且設定errno,如果對端套接字已經關閉,返回0.
recv函式只是從TCP快取中讀資料(此時資料已經在自己的電腦上了),不是直接從網路中讀資料,什麼時候TCP快取區滿了,另一邊的send函式才會停止傳送資料。
recv()函式會阻塞執行緒,直到收到訊息或者客戶端關閉。
最後當你用完套介面以後,就該釋放套介面所佔用的資源了,通過close做到這一點
當試圖向一個已經關閉的套介面寫或者讀資料就會出錯。
setsockopt()函式
int setsockopt(int s,int level,int optname,const void *optval,socklen optlen);
setsockopt函式設定套介面
函式成功返回0,失敗返回-1,並設定errno
常見用法為:
int on=1;
setsockopt(st,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
由於TCP套接字狀態TIME_WAIT引起該套接字關閉後約保留2到4分鐘。在此期間bind繫結該埠失敗。
SO_REUSEADDR指示系統地址可重用。
當伺服器在listen()函式後直接退出,如果這時候有一個客戶端來連線,這個連線會被放在快取中,如果這時候啟動了一個新的程式繫結這個IP地址和埠號,
那麼這新的程式就會接收到上一個客戶端的請求,這是有問題,因為上一個客戶端請求訪問的是原始程式。但是如果我們就一直一個程式繫結這個IP地址,就用不著這個TIME_WAIT訊號了,
我們伺服器上就一個server繫結這個IP地址和埠號,所以用不著這個機制,因此呼叫setsockopt()函式。
IP地址的結構
ip地址在記憶體中用int表示,int在記憶體中佔有4個位元組的空間
第一個位元組:192
第二個位元組:168
第三個位元組:1
第四個位元組:2
send()、recv()函式原理解析
通過上一章網路程式設計一中的主機之間的通訊圖可以得知,兩主機之間通訊,主機A先將訊息打包成TCP包,TCP包再打包成IP包,然後形成乙太網包傳送,
另一臺主機的接收順序恰好相反,先接受乙太網包,再接收IP包,最後接收TCP包,通過程式中測試,recv()函式一次能夠接受的資料(red hat中最大能接收64K資料)要
比send()函式傳送的資料(red hat中最大發送的資料超過128K),send()函式是將資料打包成TCP包再發送,而接收資料的時候是接收的IP包,再將IP包還原成TCP包,
這說明TCP包的容量實際上會大於或者等於IP包,事實上計算機在傳送資料時,如果TCP包過大,會把TCP拆解成多個IP包,並將這些IP包儲存在網絡卡的快取區裡(如果send
傳送資料超過緩衝區,那麼sned()所在的執行緒就會被掛起),recv在網絡卡上也有快取區,網路傳送過來的資料會先存放在快取區中,直到快取區被填滿,此時傳送方就會停
止傳送,但是此時不意味這send函式所在的執行緒就會被掛起,send函式還可以往自己的快取區中發資料,直到快取區填滿。
在linux進行非阻塞的socket接收資料時經常出現Resource temporarily unavailable,errno程式碼為11(EAGAIN),
這表明你在非阻塞模式下呼叫了阻塞操作,在該操作沒有完成就返回這個錯誤,
這個錯誤不會破壞socket的同步,不用管它,下次迴圈接著recv就可以。
對非阻塞socket而言,EAGAIN不是一種錯誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。
另外,如果出現EINTR即errno為4,錯誤描述Interrupted system call,該錯誤是由於操作被訊號打算,操作也應該繼續。
最後,如果recv的返回值為0,那表明連線已經斷開,我們的接收操作也應該結束。
看到這對大概流程以及概念的東西有了一個深刻了解吧,下一節小編可以詳細描述一下里面的運作過程,就是通訊過程,一接一收,如果有點疑惑的朋友可以去小猿圈尋找一下答案,看懂的朋友可以期待下一次小