Unix網絡編程 之 基本套接字調用(一)

       Unix/Linux支持伯克利風格的套接字編程,它同一時候支持面向鏈接和麪向無鏈接類型的套接字。編程

      套接字最常用的一些系統調用:緩存

      socket()      
網絡

      bind()socket

      connect()函數

      listen()spa

      accept()指針

      send()server

      recv()接口

      sendto()隊列

      recvfrom()

      close()

      shutdown()

      setsockopt()

      getsockopt()

      getpeername()

      getsockname()

      gethostbyname()

      gethostbyaddr()

      getservbyname()

      getservbyport()

      getprotobyname()

      fcntl()

如下具體解釋這些系統調用。

 

一、socket()函數

    #include <sys/socket.h>

    /*成功返回非負描寫敘述符。不然返回-1*/

    int socket(int family, int type, int protocol);

      當中,family參數指明協議族,常用的family值有AF_INET(IPv4協議)、AF_INET6(IPv6協議)、AF_LOCAL(Unix域協議)、AF_ROUTE(路由套接字)和AF_KEY(密鑰套接字)。該參數也每每被稱爲協議域。

注意,後兩種僅適用於原始套接字。

      type參數指明套接字類型。如SOCK_STREAM(字節流套接字)、SOCK_DGRAM(數據報套接字)、SOCK_SEQPACKET(有序分組套接字)以及SOCK_RAW(原始套接字)等。

      protocol參數可以設置爲IPPROTO_CP(TCP傳輸協議)、IPPROTO_UDP(UDP傳輸協議)或IPPROTO_SCTP(SCTP傳輸協議),同一時候該參數也可設置爲0,以選擇所給定family和type組合的系統默認值。

      socket()函數在成功時返回一非負整數,它與文件描寫敘述符相似,咱們稱之爲套接字描寫敘述符(Socket Descriptor)。簡稱sockfd。

      那麼,此函數的做用是什麼呢?socket函數經過咱們設定的協議族、套接字類型和傳輸協議參數來建立底層網絡文件,爲進行網絡通訊作準備。


二、bind()函數

      bind()函數把一個本地協議地址賦予一個套接字。對於網際網協議。協議地址是32位的IPv4地址或128位的IPv6地址與16位的TCP或UDPport號的組合。

    #include <sys/socket.h>

    /*成功返回0,不然返回-1*/

    int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);

      參數myaddr指向特定於協議的地址結構的指針,第三個參數是該地址結構的長度。

      假設一個TCP客戶或server未調用bind()捆綁一個port。當調用connect或listen時,內核就要爲對應的套接字選擇一個暫時port。讓內核選擇暫時port對於TCP客戶來講是正常的,但對TCPserver來講極爲罕見,因爲server是經過它們的衆所周知的port被你們所認識的。

      進程可以經過bind()函數把一個特定的IP地址綁定到其套接字上。只是此IP地址必須屬於其所在主機的網絡接口之中的一個。對於TCP客戶。這就爲該套接字所發送的數據報文指定了源IP地址。對於TCPserver,這就限定該套接字僅僅接收那些目的地址爲此IP地址的客戶鏈接。

      對於TCP。調用bind函數可以指定一個port號,或指定一個IP地址。也可以二者都指定。還可以都不指定。

那麼。當咱們未指定port號或IP地址時。系統調用會怎樣處理呢?

      通常而言。不指定port號,bind()函數默以爲0。不指定IP地址。函數默以爲通配地址。

進程指定

結果

IP地址

端口port

通配地址

0

內核選擇IP地址和port

通配地址

非0

內核選擇IP地址。進程指定port

本地IP地址

0

進程指定IP地址,內核選擇port

本地IP地址

非0

進程指定IP地址和port

      注:通配地址爲INADDR_ANY。

      當使用socket()函數獲得套接字描寫敘述符後,依狀況需要將socket綁定主機上的port:

      假設爲server進程。需要在port進行監聽(listen)操做。等待鏈接請求時。每每需要進行bind操做,而且這個port應該是衆所周知的;

      假設爲client進程,需要向遠端server發起鏈接(connect)請求。這時,綁定port是可選的。

      附:port號

      TCP、UDP和SCTP三種傳輸協議使用16位port號來區分進程。

      port號被劃分爲下面三段:

      *衆所周知的port(0-1023);

      *已登記的端口(registered port,1024-49151)。

      *動態(dynamic)或私用(private)port(49152-65535)。


三、connect()函數

      TCPclient用connect()函數來創建與TCPserver的鏈接。

    #include <sys/socket.h>

    /*成功返回0,出錯返回-1*/

    int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

      第二個、第三個參數各自是一個指向套接字地址結構的指針和該結構的大小。套接字地址結構必須含有server的IP地址和port號。

      client在調用connect()函數時,沒有必要調用bind()函數。

咱們並不在意咱們本地用什麼port來進行通訊,咱們在意的是client需要鏈接到遠端server的哪一個port。當咱們未調用bind()函數時,內核本身主動選擇一個未被使用的本地port。

      關於connect()具體是怎樣工做的,會在網絡協議的TCP三次握手時具體介紹。


四、listen()函數

    #include <sys/socket.h>

    /*成功返回0,不然返回-1*/

    int listen(int sockfd, int backlog);

      listen()函數僅由TCPserver調用,它主要完畢兩件事:

      *當socket()函數建立一個套接字是,其被設爲主動套接字,也就是說,它是一個將調用connect()函數來主動發起鏈接的client套接字。listen()函數把一個未鏈接的主動套接字轉換成一個被動套接字。指示內核應接受指向該套接字的鏈接請求。

      *backlog規定了內核應該爲對應的套接字排隊的最大鏈接個數。

      本函數一般應該在socket()和bind()函數以後。並在調用accept()函數以前調用。

      這裏。需要理解backlog參數:

      內核爲不論什麼一個給定的監聽套接字維護兩個隊列:

      (1)未完畢鏈接隊列(incomplete connection queue)。這些套接字處於SYN_RCVD狀態;

      (2)已完畢鏈接隊列(completed connection queue),每個已完畢TCP三路握手過程,這些套接字處於ESTABLISHED狀態。

      下圖描繪了監聽套接字的兩個隊列。

     

      每當在未完畢隊列建立一項時,來自監聽套接字的參數就拷貝到即將創建的鏈接中。

      當來自客戶的SYN到達server時。serverTCP在未完畢鏈接隊列中建立一個新項,而後對應以三路握手的server的SYN響應,當中捎帶對客戶SYN的ACK。這一項一直保留在未完畢鏈接隊列中,直到三路握手的第三個分節(客戶對serverSYN的ACK)到達或該項超時爲止。

假設三路握手正常完畢。該項就從未完畢隊列移至已完畢鏈接隊列的隊尾。當進程調用accept()時。已完畢鏈接隊列中的隊頭項將返回到進程。假設該隊列爲空,那麼進程將被投入睡眠。直到TCP在該隊列中放入一項才喚醒它。

      假設一個客戶的SYN到達時,兩個隊列是滿的。那麼TCP就會忽略該分節,但不會發送RST。這樣作有一個優勢:兩隊列是滿的的狀況僅僅是臨時的。假設serverTCP不發送RST。那麼clientTCP就會重發SYN,這樣,可能不久就能在這些隊列中找到可用空間。


五、accept()函數

      #include <sys/socket.h>

      /*成功返回非負描寫敘述符,出錯返回-1*/

    int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

      參數cliaddr和addrlen用來返回已鏈接的對端進程(client)的協議地址。

addrlen是值-結果參數:調用前,咱們將有*addrlen所引用的整數值置爲由cliaddr所指的套接字地址結構的長度,返回時,該整數值即爲由內核存放在該套接字地址結構內的確切字節數。

      假設accept成功。其返回值是由內核本身主動生成的一個全新描寫敘述符,表明與所返回客戶的TCP鏈接,咱們稱之爲已鏈接套接字(connected socket)。

      本函數最多返回三個值:一個既多是新套接字描寫敘述符也多是出錯指示的整數,客戶進程的協議地址(由cliaddr指針所指)以及該地址的大小(由addrlen指針所指)。假設咱們對是哪一個主機鏈接了該server(客戶協議地址)不感興趣,那麼可以把cliaddr和addrlen均置爲空指針。


六、send()和recv()函數

      這兩個函數時最主要的,經過鏈接的套接字流進行通訊的函數。

      #include <sys/socket.h>

      ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

      send()參數含義例如如下:

      sockfd表明與遠程程序鏈接的套接字描寫敘述符;

      buff指針指向發送信息的字符串。

      nbytes指發送信息的長度。

      flags指發送標記。

      send()函數在調用後返回它真正發送數據的長度。

但是,此發送數據可能少於參數指定的長度。

假設錯誤發生,則返回-1。錯誤代碼存儲在全局標量errno中。

      #include<sys/socket.h>

      ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);

      recv()參數含義例如如下:

      sockfd指讀取數據的套接字描寫敘述符。

      buff指針指向存儲數據的內存緩存區域;

      nbytes是緩存區的最大尺寸。

      flags是發送標記。

      recv()返回它所真正接收到的長度,也就是存儲到buf中數據的長度。假設返回-1則表明發生了錯誤(比方網絡意外中斷,對方關閉了套接字鏈接等),全局變量errno存儲了錯誤代碼。


七、sendto()和recvfrom函數

      這兩個函數時進行無鏈接的UDP通訊時使用的。使用這兩個函數,則數據會在沒有創建過不論什麼鏈接的網絡上傳輸。在這裏,由於數據報套接字沒法對遠程主機創建鏈接。所以,咱們在發送數據前需要知道遠端主機的IP地址和port號。

      #include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, const struct sockaddr *from, socklen_t *addrlen);

      前三個參數sockfd、buff和nbytes:套接字描寫敘述符、指向讀入寫出緩衝區的指針和讀寫字節數。

      sendto的to參數指向一個含有數據報接收者的協議地址的套接字地址結構。其大小由addrlen參數指定。recvfrom的from參數指向一個將由該函數在返回時填寫數據包發送者的協議地址的套接字地址結構。

      注意:sendto的最後一個參數是整數值。而recvfrom的最後一個參數是一個指向整數值的指針(即值-結果參數)。

      recvfrom的最後兩個參數相似於accept的最後兩個參數,返回時當中套接字地址結構的內容告訴咱們是誰發送了數據報(UDP狀況下)或是誰發起了鏈接(TCP狀況下)。sendto的最後兩個參數相似於connect的最後兩個參數,調用時當中套接字結構被咱們填入數據報將發往(UDP狀況下)或與之創建鏈接(TCP狀況下)的協議地址。

      這兩個函數都把所讀寫的數據的長度做爲函數返回值。

      注意:recvfrom和sendto都可以用於TCP,雖然一般沒有理由這樣作。


八、close()和shutdown()函數

      Unix使用close函數和shutdown函數來關閉套接字,並終止TCP鏈接。

      #include <unistd.h>

    int close(int sockfd);

    /*若成功則返回0,出錯返回-1*/

      close一個TCP套接字的默認行爲是把該套接字標記爲關閉。而後立刻返回到調用進程。該套接字描寫敘述符不能再由調用進程使用,也就是說它不能再做爲read和write的第一個參數。

然而,TCP將嘗試發送已排隊等待發送到對端的數據,發送完成後發生的是正常的TCP鏈接終止序列。

    #include <sys/socket.h>

    int shutdown(int sockfd, int how);

      注意當中的how參數。0表示不一樣意之後數據的接收操做。1表示不一樣意之後數據的發送操做,2表示和close()同樣。不一樣意之後的不論什麼數據操做。

 

附加內容:

send/recv與write/read函數的差異

      recv和send函數提供了和read和write幾乎相同的功能。但是它們提供了第四個參數來控制讀寫操做。

      ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

      ssize_t recv(int sockfd, void *buff, size_t nbytes, unsigned int flags);

      前面的三個參數和read,write一樣,第四個參數能夠是0或是下面的組合:

flags

說明

recv

send

MSG_DONTROUTE

繞過路由表查找

 

*

MSG_DONTWAIT

僅本操做堵塞

*

*

MSG_OOB

發送或接收帶外數據

*

*

MSG_PEEK

窺看外來消息

*

 

MSG_WAITALL

等待所有數據

*

 

      假設flags爲0。則和read,write同樣的操做。


PS.在下一系列博客,將會涉及到詳細的網絡編程樣例。

相關文章
相關標籤/搜索