網絡編程-套接字(scoket)

socket編程 

socket這個詞可以表示很多概念: 在TCP/IP協議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程,“IP地址+端口號”就稱爲socket。  

在TCP協議中,建立連接的兩個進程各自有一個socket來標識,那麼這兩個socket組成的socket pair就唯一標識一個連接。socket本身有“插座”的意思,因此用來描述網絡連接的一 對一關 系。  TCP/IP協議最早在BSD UNIX上實現,爲TCP/IP協議設計的應用層編程接口稱爲socket API。


在網絡中,必須保證數據是大端存儲。必須從低地址處發送,數據是高位的。
TCP/IP協議規定,網絡數據流應採用大端字節序,即低地址高字節。

socket API是一層抽象的網絡編程接口,適用於各種底層網絡協議,如IPv4、IPv6、UNIX Domain Socket。然而,各種網絡協議的地址格式並不相同,如下圖所⽰示:  sockaddr數據結構  


IPv4和IPv6的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結構體表示,包括16位端口號和32位IP地址,IPv6地址用sockaddr_in6結構體表示,包括16位端口號、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定義在sys/un.h中,用sockaddr_un結構體表示。各種socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(並不是所有 UNIX的實現 都有長度字段,如Linux就沒有),後16位表示地址類型。IPv4、IPv6和UNIX Domain Socket的地址類型分別定義爲常數AF_INET、AF_INET6、AF_UNIX。這樣,只要取得某種sockaddr結構體的首地址,不需要知道具體是哪種類型的sockaddr結構體,就可以根據地址類型字段確定結構體中的內容。因此,socket API可以接受各種類型的sockaddr結構體指針做參數,例如bind、accept、connect等函數,這些函數的參數應該設計成void *類型以便接受各種類型的指針,但是sock API的實現早於ANSI C標準化,那時還沒有void *類型,因此這些函數的參數都用struct sockaddr *類型表示,在傳遞參數之前要強制類型轉換一下。


基於TCP協議的網絡協議的一般流程:

創建套接字(scoket)
int scoket(AF_INET,SOCK_STREAM,0)
     返回值: 失敗返回-1;
                    成功返回文件描述符;
綁定套接字
int bind(listen_sock,(struct sockaddr*)&local,sizeof(local));
返回值:
              失敗返回-1;
設置套接字的狀態(監聽狀態)
 int listen(int scokfd,5);
返回值:    
                失敗返回-1;
 
accept鏈接
成功就返回一個新建的scoket的描述符;

服務器調用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() 則連接處於半關閉狀態,仍可接收對方發來的數據。 

基於TCP協議的單進程客戶端/服務器的程序:




運行結果:



基於TCP協議的多進程客戶端/服務器的程序:





運行結果:


基於TCP協議的多線程客戶端/服務器的程序:




運行結果:


下面來看一個錯誤:


這是爲什麼呢?

雖然server的應用程序終止了,但TCP協議層的連接並沒有 完全斷開,因此不能再次監聽同樣的server端口。

server的TCP連接收到client發的FIN段後處於TIME_WAIT狀 態。TCP協議規定,主動關閉連接的一方要處於TIME_WAIT狀態,等待兩個MSL(maximum segment lifetime) 的時間後才能回到CLOSED狀態,因爲我們先Ctrl-C終止了server,所 以server是主動關閉連接的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server端 口。MSL在RFC1122中規定爲兩分鐘,但是各操作系統的實現不同,在Linux上一般經過半分鐘後 就可以再次啓動server 了。


如何解決:

使用setsockopt()設置socket描述符的選項SO_REUSEADDR爲1, 表⽰允許創建端口號相同但IP地址不同的多個socket描述符。 


int opt = 1;

setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));


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