Unix網絡編程學習筆記之基于TCP套接字編程
1. socket函數
int socket(int family, int type,int protocol)
成返回一個套接字描述符。錯誤返回-1
其中family指定協議族,一般IPv4為AF_INET, IPv6為AF_INET6。
其中type指定套接字類型,字節流:SOCK_STREAM. 數據報:SOCK_DGRAM。
一般情況下通過family和type的組合都可以唯一確定一個套接字類型。所以一般我們就把protocol設為0就可以了。
有時在某些特殊情況下,family和type的組合不是都是有效的,這時我們就要給protocol指定一些特殊的值了。
2. connect函數
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
連接服務器,其中servaddr是服務器的地址。
如果是TCP套接字,connect會觸發三次握手。
從前文可以知道,當客戶端接收到服務器端的對SYN的響應的時候,connect函數就返回,若客戶端發送的SYN出錯,或者響應的ACK出錯都會引起connect函數出錯。成功返回0,出錯返回-1且errno被設置。
注意:如果connect出錯,不能直接重新connect。必須要先關閉這個套接字,然后重新socket-connect。
3. bind函數
int bind(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen)
成功返回0,錯誤返回-1
為指定的套接字綁定一個本地的套接字地址。
(1) 一般服務器端需要綁定一個公開的端口號,而服務器端一般綁定Ip時是INADDR_ANY,意為當accept時,內核會從本地IP地址中選擇一個本地IP賦值。這對于一臺機器上有多個網絡接口時,是很有影響的。
而通常機器只有一個網絡接口,則我們也使用這種方式,是因為我們不必要寫服務器本地的IP(硬編碼),這樣寫使得我們的程序有好的移植性。
(2) 一般客戶端socket函數之后就直接connect了,不進行bind,因為我們通常不需指定客戶端的Ip和端口號。讓內核自動賦值就可以了。
(3) IPv4中的INADDR_ANY通常為0,所以我們為其賦值時,是使用如下格式:
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
因為sin_addr是一個結構體,所以我們使用sin_addr.s_addr來使用其整數形式賦值。
4. listen函數
int listen(int sockfd, intbacklog)
成功返回0,錯誤返回-1
注意這里的listen并不是我們通常理解的監聽的意思,因為套接字不是在這里阻塞的,而是在accept阻塞的。
Listen只做兩件事:
(1) 把socket函數創建的套接字,設為被動套接字。因為socket函數默認創建主動套接字,主動套接字:是需要connect去主動連接的。
(2) 規定了內核應該為連接套接字排隊的***個數。
內核是如何進行連接排隊的?
內核維護兩個隊列,未完成連接隊列,已完成連接隊列。
未完成連接隊列:客戶SYN到達后,就被放入未完成連接隊列隊尾。
已完成連接隊列:客戶完成了三次握手之后,就把它放入已完成隊列隊尾。
然后進程調用accept,就從已完成隊列隊首項返回給進程。
這里的疑問?服務器端不是一直在accept阻塞嗎,怎么這里還提到進程調用accept這個說法?
因為這里的情況是在多個客戶端幾乎同時達到連接時,其中某一個連接發生的情況,因為我們寫服務器端程序時,都是把accept寫在一個循環內的,所以某個客戶的SYN到達,可能這時并沒有執行到accept,所以這里說等到進程調用accept時。也就是說,系統在已完成連接隊列為空時,accept才會阻塞。
注意這里所說的backlog是兩個隊列之和,但實際情況下,一般內核允許排隊的個數都要略大于這個值。
5. accept函數
int accept(int sockfd, struct sockaddr* cliaddr , socklen_t* addrlen)
成功返回描述符,錯誤返回-1
接受客戶連接,如果已完成連接隊列中有數據,則讀取隊頭,返回一個已連接套接字描述符。如果已完成連接隊列為空,則阻塞。
成功返回返回一個已連接套接字描述符,失敗返回負值。
注意:第三個參數為整型的地址。因為accept函數是從內核得到的套接字。如果程序對客戶端的套接字地址不感興趣,則可以把后面兩個參數都設為NULL。
一個服務器通常只有一個監聽套接字,而為每個客戶創建一個已連接套接字。
6. getsockname和getpeername
int getsockname(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
返回和套接字描述符sockfd關聯的本地套接字地址
int getpeername(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
返回和套接字描述符sockfd關聯的對端套接字地址
顯然這兩個函數都是從內核中得到套接字地址,所以第三個參數是整型的地址。
注意:
(1) 一般客戶端沒有bind,所以在connect之后,才可以調用getsockname/getpeername。
(2) 一般服務器端bind端口之后,可以調用getsockname獲取端口號。
一般服務器端bind的是通配地址,所以一般不可以獲取監聽套接字描述符的關聯ip地址,而是獲取已連接套接字描述符關聯的ip地址。
(3) POSIX允許對未bind的套接字調用getsockname,所以該函數適合任何已打開的套接字描述符(即調用socket函數返回的套接字描述符都叫已打開的套接字描述符),只是不一定輸出的是什么。
插入知識:
1. socket這幾個函數都是一樣的,成功返回0/描述符,失敗返回-1。所以它們判斷成功的條件都是一樣的。
2. RST分組,RST分組是TCP在發生錯誤時發送的一種TCP分組。
產生RST的條件:
(1) 一個目的地為某端口的SYN到達,然后本機沒有正在監聽該端口的程序,此時本機就發送一個RST。
(2) TCP想要取消一個連接。
(3) TCP接受到一個不存在的連接的分組。即某個客戶端沒有連接,就往服務器發送數據,這時服務器就會給這個客戶端發送RST。
其實RST的意思就是讓對方重新連接的意思。