linux網絡編程之socket(二):C/S程序的一般流程和基本socket函數

一、基於TCP協議的網絡程序

下圖是基於TCP協議的客戶端/服務器程序的一般流程:


服務器調用socket()、bind()、listen()完成初始化後,調用accept()阻塞等待,處於監聽端口的狀態,客戶端調用socket()初始化後,調用connect()發出SYN段並阻塞等待服務器應答,服務器應答一個SYN-ACK段,客戶端收到後從connect()返回,同時應答一個ACK段,服務器收到後從accept()返回。


數據傳輸的過程:

建立連接後,TCP協議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主動發起請求,服務器被動處理請求,一問一答的方式。因此,服務器從accept()返回後立刻調用read(),讀socket就像讀管道一樣,如果沒有數據到達就阻塞等待,這時客戶端調用write()發送請求給服務器,服務器收到後從read()返回,對客戶端的請求進行處理,在此期間客戶端調用read()阻塞等待服務器的應答,服務器調用write()將處理結果發回給客戶端,再次調用read()阻塞等待下一條請求,客戶端收到後從read()返回,發送下一條請求,如此循環下去。


如果客戶端沒有更多的請求了,就調用close()關閉連接,就像寫端關閉的管道一樣,服務器的read()返回0,這樣服務器就知道客戶端關閉了連接,也調用close()關閉連接。注意,任何一方調用close()後,連接的兩個傳輸方向都關閉,不能再發送數據了。如果一方調用shutdown()則連接處於半關閉狀態,仍可接收對方發來的數據。


在學習socket API時要注意應用程序和TCP協議層是如何交互的: 

*應用程序調用某個socket函數時TCP協議層完成什麼動作,比如調用connect()會發出SYN段

 *應用程序如何知道TCP協議層的狀態變化,比如從某個阻塞的socket函數返回就表明TCP協議收到了某些段,再比如read()返回0就表明收到了FIN段


補充一下,其實TCP 共有11種狀態,上圖沒有出現的CLOSING 狀態,當雙方同時關閉連接時會出現此狀態,替換掉FIN_WAIT2狀態。


二、基本socket函數

1、socket函數

包含頭文件<sys/socket.h>
功能:創建一個套接字用於通信
原型:int socket(int domain, int type, int protocol);
參數
domain :指定通信協議族(protocol family),AF_INET、AF_INET6、AF_UNIX等
type:指定socket類型,流式套接字SOCK_STREAM,數據報套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol :協議類型,IPPROTO_TCP等;一般由前兩個參數就決定了協議類型,設置爲0即可。
返回值:成功返回非負整數, 它與文件描述符類似,我們把它稱爲套接口描述字,簡稱套接字。失敗返回-1


2、bind函數

包含頭文件<sys/socket.h>
功能:綁定一個本地地址到套接字
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數
sockfd:socket函數返回的套接字
addr:要綁定的地址
addrlen:地址長度
返回值:成功返回0,失敗返回-1


3、listen函數

包含頭文件<sys/socket.h>
功能:將套接字用於監聽進入的連接
原型:int listen(int sockfd, int backlog);
參數
sockfd:socket函數返回的套接字
backlog:已完成三次握手的最大連接個數
返回值:成功返回0,失敗返回-1

一般來說,listen函數應該在調用socket和bind函數之後,調用函數accept之前調用。
對於給定的監聽套接口,內核要維護兩個隊列:
1、已由客戶發出併到達服務器,服務器正在等待完成相應的TCP三路握手過程
2、已完成連接的隊列

如下圖所示:

Be careful to differentiate between TCP accepting a connection and placing it on this queue, and the application taking the accepted connection off this queue.

Keep in mind that this backlog value specifies only the maximum number of queued connections for one listening end point, all of which have already been accepted by TCP and are waiting to be accepted by the application. This backlog has no effect whatsoever on the maximum number of established connections allowed by the system, or on the number of clients that a concurrent server can handle concurrently.

If there is not room on the queue for the new connection, TCP just ignores the received SYN. Nothing is sent back (i.e., no RST segment). If the listening server doesn't get around to accepting some of the already accepted connections that have filled its queue to the limit, the client's active open will eventually time out.

In the attempted connection to a nonexistent host,  how frequently the client’s TCP sends a SYN to try to establish the connection. The second segment is sent 3s after the first, the third is sent 6s after the second, the fourth is sent 12s after the third, and soon. This behavior is called exponential backoff.





4、accept函數

包含頭文件<sys/socket.h>
功能:從已完成連接隊列返回第一個連接,如果已完成連接隊列爲空,則阻塞。
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數
sockfd:服務器套接字
addr:將返回對等方的套接字地址
addrlen:返回對等方的套接字地址長度
返回值:成功返回非負整數,失敗返回-1


5、connect函數

包含頭文件<sys/socket.h>
功能:建立一個連接至addr所指定的套接字
原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數
sockfd:未連接套接字
addr:要連接的套接字地址
addrlen:第二個參數addr長度
返回值:成功返回0,失敗返回-1


參考:

《Linux C 編程一站式學習》

《TCP/IP詳解 卷一》

《UNP》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章