socket編程
socket這個詞可以表示很多概念: 在TCP/IP協議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程,“IP地址+端口號”就稱爲socket。
在TCP協議中,建立連接的兩個進程各自有一個socket來標識,那麼這兩個socket組成的socket pair就唯一標識一個連接。socket本身有“插座”的意思,因此用來描述網絡連接的一 對一關 系。 TCP/IP協議最早在BSD UNIX上實現,爲TCP/IP協議設計的應用層編程接口稱爲socket API。
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協議的網絡協議的一般流程:
雖然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));