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》