1. 程式人生 > 實用技巧 >UNP筆記-基本的套接字程式設計

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。

注:在本函式中,domaintype的組合不一定有效,有效的組合如下所示:

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值。

注意

  1. 客戶端在呼叫connect前不一定非要呼叫bind函式,因為核心會確定源IP地址,並選擇一個臨時埠作為源埠。
  2. 如果是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 : 地址已經使用

注意

  1. bind函式捆綁時,可以指定IP和埠,也可以不指定,不指定的話核心會要為相應的套接字選擇一個臨時埠,但是如果是伺服器創建出來的套接字地址資訊客戶端不會知道。

  2. bind指定IP埠和不指定產生的結果如下:

    IP 結果
    非指定 0 核心選擇IP和埠
    非指定 非0 核心選擇IP,程序指定埠
    指定本機IP 0 程序指定IP,核心選擇埠
    指定本機IP 非0 程序指定IP和埠
  3. 如果想指定地址為通配地址,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
    
  4. 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值。
注意

  1. 該函式必須在accept之前呼叫。
  2. 當使用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值。

注意

  如果對接收後的客戶端的地址資訊不感興趣,cliaddraddrlen引數都可以為空指標。

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,那麼將會發生以下狀況:

  1. 父程序最終將耗盡可用描述符。
  2. 沒有一個客戶端連線被終止。就運算元程序關閉,只是將引用計數值由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。

這兩個函式後兩個引數都是值-結果引數,所以需要自己定義結構然後傳入,結果由這兩個函式進行填充。

這兩個函式的作用如下:

  1. 客戶端沒有呼叫bind,直接connect,那麼並不知道連線的本地IP和埠是多少,就可以呼叫getsockname來獲取。
  2. 在使用埠號0呼叫bind,是由核心選擇埠號,可以呼叫getsockname返回核心賦予的埠號。
  3. getsockname可以獲取某個套接字的地址協議族。
  4. 在伺服器和客戶端建立連線後,getsockname可以用於返回核心賦予該連線的本地IP地址,但必須是已連線套接字描述符,不能是監聽套接字描述符。
  5. 當一個伺服器是通過一個程序呼叫exec執行程式執行時,想要獲取客戶端地址資訊的唯一途徑便是呼叫getpeername。