5分鐘帶簡單讓你入門Socket

1.網絡中進程之間如何通信?

本地的進程間通信(IPC)有很多種方式,但總可以總結爲下面的4類:

  • 消息傳遞(管道、FIFO、消息隊列)
  • 同步(互斥量、條件變量、讀寫鎖、文件、寫記錄鎖、信號量)
  • 共享內存(匿名的和具名的)
  • 遠程過程調用(Solaris門和Sun RPC)

本地通過PID來唯一標識進程,網絡層的IP唯一標識主機,傳輸層的“協議+端口”唯一標識應用程序(進程),這樣
IP+協議+端口就可以標識網絡中的進程了,網絡中的進程通信就可以利用這個標誌來與其他的進程之間交互了。
幾個定義:
(1)IP地址:即依照TCP/IP協議分配給本地主機的網絡地址,兩個進程要通訊,任一進程首先要知道通訊對方的位置,即對方的IP。
(2)端口號:用來辨別本地通訊進程,一個本地的進程在通訊時均會佔用一個端口號,不同的進程端口號不同,因此在通訊前必須要分配一個沒有被訪問的端口號。
(3)連接:指兩個進程間的通訊鏈路。
(4)半相關:網絡中用一個三元組可以在全局唯一標誌一個進程:
(協議,本地地址,本地端口號)
這樣一個三元組,叫做一個半相關,它指定連接的每半部分。
(5)全相關:一個完整的網間進程通信需要由兩個進程組成,並且只能使用同一種高層協議。也就是說,不可能通信的一端用TCP協議,而另一端用UDP協議。因此一個完整的網間通信需要一個五元組來標識:
(協議,本地地址,本地端口號,遠地地址,遠地端口號)

這樣一個五元組,叫做一個相關(association),即兩個協議相同的半相關才能組合成一個合適的相關,或完全指定組成一連接。

2.socket 是什麼?

socket 起源於Unix,而Unix /liunux基本哲學就是“一切皆文件”,都可以用“打開open->讀寫write/read->關閉close”模式來操作。

3.socket 的基本操作

既然socket是“open-write/read-close”模式的一種實現,那麼socket就提供了這些操作的函數接口。
下面一TCP爲例,介紹幾個基本的socket接口函數。

(1) socket()函數

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

socket函數對應於普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用於創建一個
socket描述符,它唯一標識一個socket。這個socket描述字跟文件描述字一樣,後續的操作都有用到它,把它作爲參數,通過它來進行一些讀寫操作。

正如fopen的傳入不同參數值,以打開不同的文件。創建socket的時候,也可以指定不同的參數創建不同的soket描述符,socket函數的三個參數分別爲:

  1. domain:即協議域,又稱爲協議族(family)。常用的協議族有,AF_INET、AFINT6、AF_LOCAL、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號的組合、AF_NUIX決定了要用一個絕對路徑名作爲地址。
  2. type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_PACKET、SOCK_SEQPACKET等。
  3. protocol: 顧名思意,就是指定協議。常用的協議有IPROTO_TCP、IPROTO_UDP、 IPROTO_SCTP、 IPROTO_TIPC等。

注意:並不是上面的type 和protocol可以隨意組合的,如sock_steam不可以跟IPPROTO_UDP組合。當protocol爲o時,會自動選擇type類型對應的默認協議。

當我們調用socket創建一個socket時,返回socket描述字它存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時,系統會自動分配一個端口。

(2) bind()函數
正如上面所說bind()函把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

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

函數的三個參數分別爲:
sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。。bind()函數就是將這個描述字綁定一個名字。
addr:一個const struct sockaddr 指針,指向要綁定的sockfd的協議地址。這個地址結構根據創建socket時地址協議族的不同而不同,如ipv4對應的是:

struct sockaddr_in {
     sa_family_t   sin_family; /* address family: AF_INET */       
     in_port_t     sin_port;   /* port in network byte order */     
     struct in_addr     sin_addr;   /* internet address */
}; 
  
/* Internet address. */ struct in_addr {  
  uint32_t       s_addr;     /* address in network byte order */
};

ipv6對應的是:

struct sockaddr_in6 {     
     sa_family_t     sin6_family;   /* AF_INET6 */     
     in_port_t       sin6_port;     /* port number */     
     uint32_t        sin6_flowinfo; /* IPv6 flow information */     
     struct in6_addr sin6_addr;     /* IPv6 address */     
     uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
}; 
struct in6_addr {     
unsigned char   s6_addr[16];   /* IPv6 address */ 
};

Unix域對應的是:

define UNIX_PATH_MAX    108  struct sockaddr_un {
     sa_family_t sun_family;               /* AF_UNIX */     
     char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

addrlen:對應的是地址的長度。

通常服務器在啓動的時候都會綁定一個衆所周知的地址(ip+端口),用於提供服務,客戶端就可以通過它來連接服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是爲什麼通常服務器在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。
(3) listen()、connect()
如果作爲一個服務器,在調用socket()、bind()之後就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器就會收到這個請求。

int listen(int sockfd ,int backlog);

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

listen函數的第一個參數是要監聽的socekt描述字,第二個參數爲相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的連接請求。
connect函數的第一個參數是客戶端的socket的描述字,第二個參數爲socket地址,第三個參數爲socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接。

(4) accept()函數
TCP服務器端依次調用socket()、bind()、listen()之後,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之後就想TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求後,就會調用accept()函數取接收請求,這樣連接就建立好了。之後就可以開始網絡的IO操作了,即類同於普通文件的讀寫IO操作了。

int accept(int sockfd, struct sockaddr *addr, sockelen_t *addrlen);

accept()函數的第一個參數爲服務器的socket描述字,第二個參數爲指向struct sockaddr*的指針,用於返回客戶端的協議地址,第三個參數爲協議地址的長度。如果accept成功,那麼其返回值是有內核自動生成的一個全新的描述字,代表與返回客戶的TCP連接。

注意:accept的第一個參數爲

4.socket中TCP的三次握手建立連接詳解

我們知道tcp建立連接要進行“三次握手”,即交換三個分組。大致流程如下:

  • 客戶端向服務器發送一個SYN J
  • 服務器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
  • 客戶端再想服務器發一個確認ACK K+1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章