一、基於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、已完 成連接的隊列
如下圖所示:
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