UNP筆記-基本的套接字程式設計
基本的套接字程式設計
socket函式
想要執行網路I/O,首先需要呼叫socket函式建立套接字,需要標頭檔案#include <sys/socket.h>
int socket (int domain, int type, int protocol);
引數:
domain
: 執行協議域,取值如下:
domain | 說明 |
---|---|
AF_INET | IPV4協議 |
AF_INET6 | IPV6協議 |
AF_LOCAL | Unix 域協議 |
AF_ROUTE | 路由套接字 |
AF_KEY | 金鑰套接字 |
type
: 套接字型別,取值如下
type | 說明 |
---|---|
SOCK_STREAM | 位元組流套接字 |
SOCK_DGRAM | 資料報套接字 |
SOCK_SEQPACKET | 有序分組套接字 |
SOCK_RAW | 原始套接字 |
protocol
: 某個協議型別常值,或者設定為0。
protocal | 說明 |
---|---|
IPPROTO_TCP | TCP傳輸協議 |
IPPROTO_UDP | UDP傳輸協議 |
IPPROTO_SCTP | SCTP傳輸協議 |
如果使用預設,protocol
可以填0。
返回值:
成功時返回一個int
型整數,是一個類似於檔案描述符的網路套接字描述符。
出錯返回-1。
注:在本函式中,domain
和 type
的組合不一定有效,有效的組合如下所示:
AF_INET | AF_INET6 | AF_LOCAL | AF_ROUTE | AF_KEY | |
---|---|---|---|---|---|
SOCK_STREAM | TCP / SCTP | TCP / SCTP | 是 | ||
SOCK_DGRAM | UDP | UDP | 是 | ||
SOCK_SEQPACKET | SCTP | SCTP | 是 | ||
SOCK_RAW | IPV4 | IPV6 | 是 | 是 |
其中domain
還有以PF_開頭的,還有其他BSD支援的協議域,不過多介紹。表格中“是”表示可以組合,但是沒有有效的名稱,空白是無效組合。
connect 函式(客戶端)
如果客戶端想要連線伺服器,必須使用connect函式完成,需要引入標頭檔案#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
引數:
sockfd
: 是之前socket函式返回的套接字描述符。servaddr
: 指向伺服器套接字地址結構體的指標,必須含有伺服器的IP地址和埠。addrlen
: 指向伺服器套接字地址結構體的大小。
返回值:
成功,返回0;
失敗,返回-1,並設定error值。
注意:
- 客戶端在呼叫connect前不一定非要呼叫bind函式,因為核心會確定源IP地址,並選擇一個臨時埠作為源埠。
- 如果是TCP套接字,呼叫connect函式將激發三次握手過程。在連線建立成功或出錯時返回。出錯情況如下:
ETIMEDOUT
: 客戶端沒有收到SYN分節的響應。
ECONNREFUSED
: 伺服器對客戶端SYN的響應是RST(復位),說明伺服器在指定的埠上沒有程序在等待與之連線(硬體錯)。
EHOSTUNREACH / ENETUNREACH
: 客戶端傳送的SYN在某個路由器引發了目的地不可達的ICMP錯誤(軟錯誤)。
bind函式
如果想要將socket函式建立的套接字與一個本地協議地址捆綁起來,需要使用bind函式,標頭檔案#include <sys/types.h>
、 #include <sys/socket.h>
。
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
引數:
sockfd
: 呼叫socket函式返回的套接字。myaddr
: 指向特定協議地址結構體的指標。addrlen
: 特定協議地址結構體的大小。
返回值:
成功,返回0;
失敗,返回-1,並設定error值。
出錯情況:
EADDRINUSE
: 地址已經使用
注意:
-
bind函式捆綁時,可以指定IP和埠,也可以不指定,不指定的話核心會要為相應的套接字選擇一個臨時埠,但是如果是伺服器創建出來的套接字地址資訊客戶端不會知道。
-
bind指定IP埠和不指定產生的結果如下:
IP 埠 結果 非指定 0 核心選擇IP和埠 非指定 非0 核心選擇IP,程序指定埠 指定本機IP 0 程序指定IP,核心選擇埠 指定本機IP 非0 程序指定IP和埠 -
如果想指定地址為通配地址,IPV4為
INADDR_ANY
,IPV6為in6addr_any
,標頭檔案為#include <netinet/in.h>
。servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //ipv4 servaddr.sin6_addr = in6addr_any; //ipv6
-
bind並不返回核心所選擇的臨時埠的值。因為const限定,如果想要得到核心選擇的臨時埠,需要使用
getsockname
來返回協議地址。
listen函式(伺服器)
伺服器如果需要監聽客戶端連線,需要使用listen函式,標頭檔案#include <sys/socket.h>
。
int listen(int sockfd, int backlog);
引數:
sockfd
: 之前socket函式建立的套接字描述符backlog
: 規定了核心應該為該套接字排隊的最大連線個數。核心為監聽套接字維護了兩個佇列。一個是未完成連線佇列,主要是客戶端發出SYN到伺服器,伺服器正在等待完成相應的TCP三路握手過程,此時套接字處於SYN_RCVD狀態。另一個是已經完成的佇列,此時套接字處於ESTABLISHED狀態。badklog就是兩個佇列之和的最大值。呼叫listen函式後,將已經完成連線佇列的隊頭返回返回給程序;如果佇列為空,程序將睡眠,直到TCP在該佇列中放入一項才喚醒它。不要把backlog設定為0,因為不同的實現對此有不同的解釋。老核心一般指定為5,但是高併發下,該值偏小不夠用,新核心支援比較大的值。
返回值:
成功,返回0。
失敗,返回-1,並設定error值。
注意:
- 該函式必須在accept之前呼叫。
- 當使用socket函式建立一個套接字時,它預設是一個主動套接字。而listen函式將一個未連線的套接字轉換為被動套接字,指示核心應該接受該套接字的連線請求。
accept函式(伺服器)
上面listen
函式的backlog
引數提到一個已完成的佇列,accept函式就是用於從已完成連線的佇列對頭返回下一個已完成佇列,如果已完成佇列連線為空,那麼進入睡眠(套接字預設為阻塞狀態)。標頭檔案#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
引數:
sockefd
: 監聽套接字,是socket函式建立的套接字,接受成功後,會返回新的套接字。cliaddr
: 是用來返回對端客戶端的地址結構資訊。addrlen
: 是客戶端套接字地址結構的長度,由該長度確定核心中存放客戶端套接字的地址結構的確定位元組數。
返回值:
如果返回成功(值 > 0),返回值是核心生成的一個全新的描述符,代表伺服器與客戶端的連線,代表已連線套接字。一個伺服器通常僅建立一個監聽套接字,它在伺服器的生命週期內會一直存在。但是會建立多個已連線套接字,當伺服器完成某一客戶端的服務,已連線套接字可能會關閉。
如果失敗返回-1,並設定error值。
注意:
如果對接收後的客戶端的地址資訊不感興趣,cliaddr
和addrlen
引數都可以為空指標。
close函式
當處理完成後,關閉套接字描述符,並終止TCP連線,需要使用close函式,標頭檔案#include <unistd.h>
。
int close(int sockfd);
引數:
- sockfd : 是一個套接字描述符或檔案描述符。
返回值:
成功返回0
失敗返回-1,並設定error值。
注意:
呼叫close後,該sockfd不能再被使用,但是TCP會嘗試傳送已排隊傳送到對端的任何資料,傳送完畢後發生的是正常的TCP連線終止序列。
描述符的引用計數
多程序的程式設計中,我們一般在父程序中呼叫listen
後,然後通過accept
接受客戶端 ,我們呼叫fork
函式建立子程序,那麼現在listenfd和connfd在父子程序之間被共享(被複制),然而每個檔案或者套接字都有一個引用計數,引用計數通過檔案表項中維護,就是開啟者著的引用該檔案或套接字描述符的個數。那麼我們fork後,這兩個套接字相關聯的檔案表項的訪問計數都變為2。所以下一步就是,在父程序中關閉已連線套接字connfd,而在子程序關閉監聽套接字listenfd。
那麼如果我們不那麼做,父程序對每個accept返回的已連線套接字都不呼叫close,那麼將會發生以下狀況:
- 父程序最終將耗盡可用描述符。
- 沒有一個客戶端連線被終止。就運算元程序關閉,只是將引用計數值由2變為1,因為父程序不關閉任何已連線套接字connfd,這將會妨礙TCP連線終止序列的發生,連線將一直開啟。
獲得套接字關聯的地址
getsockname
函式獲得某個套接字關聯的本地協議地址,標頭檔案為#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr* localaddr, socklen_t* addrlen);
getpeername
函式獲得某個套接字關聯的外地協議地址
int getpeername(int sockfd,struct sockaddr* peeraddr, socklen_t* addrlen);
返回值:成功均返回0,出錯返回-1。
這兩個函式後兩個引數都是值-結果引數,所以需要自己定義結構然後傳入,結果由這兩個函式進行填充。
這兩個函式的作用如下:
- 客戶端沒有呼叫bind,直接connect,那麼並不知道連線的本地IP和埠是多少,就可以呼叫getsockname來獲取。
- 在使用埠號0呼叫bind,是由核心選擇埠號,可以呼叫getsockname返回核心賦予的埠號。
- getsockname可以獲取某個套接字的地址協議族。
- 在伺服器和客戶端建立連線後,getsockname可以用於返回核心賦予該連線的本地IP地址,但必須是已連線套接字描述符,不能是監聽套接字描述符。
- 當一個伺服器是通過一個程序呼叫exec執行程式執行時,想要獲取客戶端地址資訊的唯一途徑便是呼叫getpeername。