unix網絡編程——TCP套接字編程

  TCP客戶端和服務端所需的基本套接字。服務器先啓動,以後的某個時刻客戶端啓動並試圖鏈接到服務器。以後客戶端向服務器發送請求,服務器處理請求,並給客戶端一個響應。該過程一直持續下去,直到客戶端關閉,給服務端發送EOF(文件結束),服務器也關閉鏈接的服務器端,而後結束運行或者等待新的客戶發起鏈接請求。如圖1所示:服務器

圖1 TCP網絡套接字示意圖網絡

  在圖中涉及到不一樣的函數,接下來進行詳細的介紹。socket


 

socket函數

  爲了進行網絡I/O,進程首先須要調用socket函數,指定使用的通訊協議類型(IPv4的TCP、IPv6的UDP、Inux域字節流協議等)。函數

#include<sys/socket.h> int socket(int family, int type, int protocol); 返回:若成功返回非負數,若失敗返回-1 

   family表示協議族,協議族取值如表1所示:spa

family 說明
AF_INET

IPv4協議unix

AF_INET6 IPv6協議
AF_LOCAL Unix域協議
AF_ROUTE 路由套接字
AF_KEY 密鑰套接字

表1 協議族family取值指針

  type表示套接字類型,套接字類型type如表2所示:blog

type 說明
SOCK_STREAM 字節流套接字
SOCK_DGRAM 數據報套接字
SOCK_SEQPACKET 有序分組套接字
SOCK_RAW 原始套接字

表2 套接字類型接口

  protocol表示某個協議類型常值,或者設置爲0,以選擇family和type組合的系統默認值,但並非全部的family和type組合都是有效的,表3給出了正確組合。隊列

這裏寫圖片描述表3 偷來的截圖

  socket函數調用成功後返回一個小的非負整數值,稱爲套接字描述符(socket descriptor),簡稱sockfd。指定了協議族(IPv四、Ipv6或Unix)和套接字類型(字節流、數據報或原始套接字),並無指定本地協議地址或遠程協議地址。


 

connect函數

  TCP客戶端使用connect函數來創建與TCP服務器之間的鏈接。

#include<sys/socket.h> int connect(int sockfd, const struct *servaddr, socklen_t addrlen); 返回:若成功返回0,若失敗返回-1 

  sockfd:socket函數返回的套接字描述符

  servaddr:套接字地址結構的指針

  addrlen:套接字地址結構的大小

  套接字地址結構必須含有服務器的IP地址和端口號。客戶端在調用connect函數前沒必要非要調用bind函數,由於若是須要的話,內核會確認源IP地址,並選擇一個臨時端口做爲源端口。

  若是是TCP套接字,調用connect函數會激發TCP三次握手,並且僅在鏈接創建成功或失敗時纔會返回。


bind函數

  bind函數將一個本地協議地址賦予一個套接字,對於網際協議,協議地址是32位的Ipv4地址或128位的IPv6地址與16位的TCP或UDP端口號的組合。

#include<sys/socket.h> int bind(int sockfd, const struct *myaddr, socklen_t addrlen); 返回:若成功返回0,若失敗返回-1 

  sockfd:socket函數返回的套接字描述符

  servaddr:套接字地址結構的指針

  addrlen:套接字地址結構的大小

  對於TCP,調用bind函數能夠指定一個端口號和一個IP地址,也能夠不指定。

  服務器在啓動時綁定它們的衆所周知的端口,當調用connect或listen的時候,內核會爲相應的套接字選擇一個臨時端口。讓內核選擇臨時端口對於TCP客戶端來講很正常,除非應用須要一個預留端口,然而對於TCP服務器來講卻極爲罕見,由於服務器是經過它們衆所周知的端口被你們認識的。

(例外狀況:RPC服務器,它們一般就由內核爲它們的監聽套接字選擇一個臨時端口,而該端口隨後經過RPC端口映射器進行註冊。客戶在connect這些服務器以前,必須與端口映射器聯繫來獲取它們的臨時端口,這種狀況也是用與UDP的RPC服務器)

  進程能夠將一個特定的IP地址綁定到它的套接字上,不過這個IP地址必須屬於其所在主機的網絡接口之一。對於TCP客戶端,這就爲在該套接字上發送的IP數據報指派了源IP地址,對於TCP服務器,這就限定該套接字只接收那些目的地爲這個IP地址的客戶鏈接。TCP客戶一般不把IP地址綁定到套接字上。當鏈接套接字的時候,內核將根據所用外出網絡接口選擇源IP地址,而全部外出接口則取決於到服務器所需的路徑。若是TCP服務器沒有把IP地址綁定到套接字上,內核就把客戶端發送的SYN的目的IP地址做爲服務器的源IP地址。

  若是指定端口爲0,那麼內核就在bind被調用時選擇一個臨時端口。然而若是指定IP地址爲通配地址,那麼內核將等到套接字已鏈接(TCP)或已在套接字上發出數據報(UDP)時才選擇一個本地IP地址。

  對於IPv4來講,通配地址由常值INADDR_ANY來指定,其值通常爲0。它告知內核去選擇IP地址。


 listen函數

 

#include<sys/socket.h> int listen(int sockfd, int backlog); 返回:若成功返回0,若失敗返回-1 

 

  sockfd:socket函數返回的套接字描述符

  backlog:內核應爲響應套接字排隊的最大鏈接個數

 

  listen函數一般在調用socket函數和bind函數以後,調用accept函數以前調用  

listen函數僅由TCP服務器調用,它作兩件事情:

  當socket函數建立一個套接字的時候,它被假設爲一個主動套接字,也就是說,它是一個將調用connect發起鏈接的客戶端套接字。listen函數把一個未鏈接的套接字轉換成一個被動套接字,指示內核應接受指向該套接字的鏈接請求。調用listen函數使得套接字從CLOSED狀態轉到LISTEN狀態。

圖2 TCP狀態轉換圖

  爲了理解backlog參數,必須爲內核維護兩個隊列:

  (1)未完成鏈接隊列(incomplete connection queue),每一個這樣的SYN分節對應其中一項:已由某個客戶端發起並達到服務器,而服務器正在等待完成相應的TCP三次握手過程。這些套接字處於SYN_RCVD狀態(見圖2);

  (2)已完成鏈接隊列(completed connection queue),每一個已完成TCP三次握手過程的客戶端對應其中一項,這些套接字處於ESTABLISHED狀態(見圖2)。


 

 accept函數

  accept函數由TCP服務器調用,用於從已完成鏈接隊列的頭部返回下一個已鏈接,若是已鏈接隊列爲空,那麼進程休眠(若是套接字是默認的阻塞方式)。

#include<sys/socket.h> int accept(int sockfd, struct *cliaddr, socklen_t *addrlen); 返回:若成功返回非負描述符,若失敗返回-1

  sockfd:socket函數返回的套接字描述符

  cliaddr:返回已鏈接的對端(客戶端)的協議地址

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


 

 fork和exec函數

  fork函數是unix派生新進程的惟一方法。

#include<unistd.h>
pid_t fork(void);
返回:在子進程中爲0,在父進程中爲子進程ID,若失敗返回-1

  fork函數調用一次,會返回兩次。在父進程(調用進程)中返回一次,返回值是子進程(新派生進程)的進程ID號;在子進程中又返回一次,返回值爲0。所以能夠經過返回值來肯定是父進程仍是子進程。

  fork函數在父進程和子進程中返回值不一樣的緣由:任何子進程只有一個父進程,並且子進程老是能夠經過調用getppid取得父進程的進程ID,反之,父進程有不少子進程,並且沒法獲取各個子進程的進程ID。若是父進程想要追蹤全部子進程的進程ID,那麼必須記錄每次調用fork的返回值(由於在每次調用fork的時候,父進程會返回子進程的ID號,若是每次都進行記錄,那麼就能夠追蹤全部子進程的進程ID)。

  父進程中調用fork以前打開的全部描述符在fork返回以後由子進程分享,咱們將看到網絡服務器利用這個特性:父進程調用accept以後調用fork,所接受的已鏈接套接字隨後在父進程和子進程之間共享。一般狀況下,子進程接着讀寫這個已鏈接套接字,父進程則關閉這個已鏈接套接字。

  fork的典型用法:

  (1)一個進程建立一個自身的副本,這樣每一個副本均可以在另外一個副本執行其餘任務的同時處理各自的某個操做。這是網絡服務器的典型用法。

  (2)一個進程想要執行另外一個程序。既然建立新進程的惟一方法是調用fork,該進程因而首先調用fork建立一個自身副本,而後其中一個副本(一般稱爲子進程)調用exec把自身替換成新的程序,

 

相關文章
相關標籤/搜索