天天看點

網絡程式設計(三)—— 套接字的使用&建立網絡連接配接

文章目錄

    • 🍓1 伺服器準備連接配接🍓
      • 🍇1.1 socket():預準備手機🍇
      • 🍇1.2 bind():綁定電話卡🍇
      • 🍇1.3 listen():真正的可用手機🍇
      • 🍇1.4 accept():電話鈴聲響起🍇
    • 🍓2 用戶端發請求連接配接🍓
      • 🍇2.1 connect():撥打伺服器的電話🍇
      • 🍇2.2 淺讀一下TCP三向交握🍇
網絡程式設計(三)—— 套接字的使用&建立網絡連接配接

前面學習了IPv4、IPv6、本地套接字的相關知識,現在學習一下怎麼去使用前面所學的知識——使用對應套接字格式完成網絡連接配接的建立

先有服務而後有客戶

🍓1 伺服器準備連接配接🍓

🍇1.1 socket():預準備手機🍇

網絡程式設計(三)—— 套接字的使用&建立網絡連接配接

要建立一個套接字需要用到下面的函數

  • domian:PF_INET、PF_INET6、PF_LOCAL(AF_XXX)等
  • type:
    • SOCK_STREAM:表示的是位元組流,對應TCP
    • SOCK_DGRAM:表示的是資料報,對應UDP
    • SOCK_RAW:表示原始套接字(少用到)
  • protocol:原用于指定通信協定,因前兩個參數已基本固定一個協定,是以這裡基本上設定為0即可

🍇1.2 bind():綁定電話卡🍇

網絡程式設計(三)—— 套接字的使用&建立網絡連接配接

由前面

socket()

函數準備好的套接字如果要被使用,那麼需要給它提供一個信号源,這就需要用到

bind()

函數把套接字和套接字位址綁定,也就是将手機卡裝入手機的SIM卡槽

  • fd:表示的是我們前面所建立出來的初始socket句柄
  • addr:通用位址格式,用以适配IPv4、IPv6、本地套接字格式等
  • len:通用位址的長度,通過這個參數确定傳入的位址格式是哪個類型的

如果是雙卡雙待手機,那該怎麼綁定手機卡呢?

這個時候,可以利用通配位址的能力幫助我們解決這個問題

對IPv4位址來說,通配位址的設定通過INADDR_ANY來完成;對于IPv6位址來說,則是通過INADDR6_ANY來完成的

struct sockaddr_in name;
name.sin_addr.s_addr = htonl (INADDR_ANY); /* IPV4通配位址 */
           

除了設定位址外,還有一個參數就是端口号。如果把端口号設定為0,那麼系統核心會按照一定的算法來選擇一個空閑的端口,以完成socket的綁定(這在伺服器端中并不常用,因為一般來說,伺服器端需要綁定一個已知的位址和端口号,否則客戶就不知道怎麼聯系上伺服器)

下面是初始化IPv4 TCP socket的例子

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>


int make_socket (uint16_t port)
{
  int sock;
  struct sockaddr_in name;


  /* 建立位元組流類型的IPV4 socket. */
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }


  /* 綁定到port和ip. */
  name.sin_family = AF_INET; /* IPV4 */
  name.sin_port = htons (port);  /* 指定端口 */
  name.sin_addr.s_addr = htonl (INADDR_ANY); /* 通配位址 */
  /* 把IPV4位址轉換成通用位址格式,同時傳遞長度 */
  if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
    {
      perror ("bind");
      exit (EXIT_FAILURE);
    }
  return sock
}
           

🍇1.3 listen():真正的可用手機🍇

網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接

經過綁定電話卡後,一個正常可用的手機就誕生了,此時的手機都是自己準備好,以便可以使用的,這一系列過程都是主動性的,但是作為伺服器端,需要“高冷”一點,不能太過主動,應該等待别人的主動。

listen()

将原本的主動性socket轉化為被動性的socket,以告訴系統核心:“我現在隻會等待别人的主動通知,我是用來被請求連接配接的!”

  • socketfd:套接字描述符,即上面綁定好手機卡後的socket
  • backlog:在Linux中表示已完成(ESTABLISHED)且未accept的隊列大小,這個參數的大小決定了可以接收的并發數目。參數越大理論上并發數目越大,但是數目過大會占用系統資源

到這裡,伺服器端已經準備的差不多了,即将進入最後的等待模式

什麼是并發?

網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接

百度了一下的知識,好像了解了但是又有點疑惑。

  • 并發

    :并發當有多個線程在操作時,如果系統隻有一個CPU,則它根本不可能真正同時進行一個以上的線程,它隻能把CPU運作時間劃分成若幹個時間段,再将時間 段配置設定給各個線程執行,在一個時間段的線程代碼運作時,其它線程處于挂起狀。.這種方式我們稱之為并發(Concurrent)
  • 并行

    :當系統有一個以上CPU時,則線程的操作有可能非并發。當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行,這種方式我們稱之為并行(Parallel)。

上面的兩個概念摘自并發,這裡并行很好了解,但是并發裡說的

多個線程運作在一個時間段裡同時運作

怎麼了解呢?這個先記下來後面看明白了再回來補充

🍇1.4 accept():電話鈴聲響起🍇

網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接

當用戶端的連接配接請求到達時,伺服器端應答成功,連接配接建立,這個時候作業系統核心需要把這個事件通知到應用程式,并讓應用程式感覺到這個連接配接。這個過程,就好比電信營運商完成了一次電話連接配接的建立, 應答方的電話鈴聲響起,通知有人撥打了号碼,這個時候就需要拿起電話筒開始應答。

  • listensockfd:從名字上可以看出就是經過

    listen()

    轉化後的被動socket
  • cliaddr:通過指針擷取用戶端的位址
  • addrlen:告訴我們位址的大小

    這個函數的過程可以了解成當我們拿起電話機時,看到了來電顯示,知道了對方的号碼

    需要注意的是,傳入的是監聽式套接字描述符,傳回的是已連接配接的套接字描述符,兩者不是同一個描述符

為什麼不把輸入的套接字描述符和傳回的描述符并為同一個?

這裡就用到了上面所說到的

并發

知識

和打電話的情形非常不一樣的地方就在于,打電話一旦有一個連接配接建立,别人是不能再打進來的,隻會得到語音播報:“您撥的電話正在通話中。”而網絡程式的一個重要特征就是并發處理,不可能一個應用程式運作之後隻能服務一個客戶,如果是這樣, 雙 11 搶購得需要多少伺服器才能滿足全國 “剁手黨 ” 的需求?是以監聽套接字一直都存在,它是要為成千上萬的客戶來服務的,直到這個監聽套接字關閉;而一旦一個客戶和伺服器連接配接成功,完成了 TCP 三次握手,作業系統核心就為這個客戶生成一個已連接配接套接字,讓應用伺服器使用這個已連接配接套接字和客戶進行通信處理。如果應用伺服器完成了對這個客戶的服務,比如一次網購下單,一次付款成功,那麼關閉的就是已連接配接套接字,這樣就完成了 TCP 連接配接的釋放。請注意,這個時候釋放的隻是這一個客戶連接配接,其它被服務的客戶連接配接可能還存在。最重要的是,監聽套接字一直都處于“監聽”狀态,等待新的客戶請求到達并服務

🍓2 用戶端發請求連接配接🍓

第一步還是和服務端一樣,要建立一個套接字,方法和前面是一樣的。不一樣的是用戶端需要調用 connect 向服務端發起請求。

🍇2.1 connect():撥打伺服器的電話🍇

網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接
  • sockfd:

    socket()

    建立的套接字描述符
  • servaddr:指向套接字位址結構的指針
  • addrlen:套接字位址結構的長度大小

connect()之前不需要bind()嗎?

用戶端在調用

connect()

之前不一定需要調用

bind()

函數,用戶端作為主動方,發起的請求連接配接目标可以是多個,是以并不需要使用

bind()

進行目标綁定。如果需要使用

bind()

的話,核心會确定源IP位址,按照一定算法配置設定一個臨時端口。

回想上一章節中的知識,(TCP socket)當程式執行到用戶端的

connect()

時,就已經觸發了TCP中的三次握手機制了,而且僅在連接配接建立成功或者出錯後才傳回。連接配接建立成功傳回很好了解,那什麼是連接配接出錯呢?

  1. 三次握手無法建立,用戶端發出的 SYN 包沒有任何響應,于是傳回 TIMEOUT 錯誤。這種情況比較常見的原因是對應的服務端 IP 寫錯。
  2. 用戶端收到了 RST(複位)回答,這時候用戶端會立即傳回 CONNECTION REFUSED 錯誤。這種情況比較常見于用戶端發送連接配接請求時的請求端口寫錯,因為 RST 是 TCP 在發生錯誤時發送的一種 TCP 分節。産生 RST 的三個條件是:目的地為某端口的 SYN 到達,然而該端口上沒有正在監聽的伺服器(如前所述);TCP 想取消一個已有連接配接;TCP 接收到一個根本不存在的連接配接上的分節。
  3. 客戶發出的 SYN 包在網絡上引起了"destination unreachable",即目的不可達的錯誤。這種情況比較常見的原因是用戶端和伺服器端路由不通。

🍇2.2 淺讀一下TCP三向交握🍇

網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接
網絡程式設計(三)—— 套接字的使用&amp;建立網絡連接配接

網上搜尋到的簡單講解:

  1. 用戶端的協定棧向伺服器端發送了 SYN 包,并告訴伺服器端目前發送序列号 j,用戶端進入 SYNC_SENT 狀态;
  2. 伺服器端的協定棧收到這個包之後,和用戶端進行 ACK 應答,應答的值為 j+1,表示對 SYN 包 j 的确認,同時伺服器也發送一個 SYN 包,告訴用戶端目前我的發送序列号為 k,伺服器端進入 SYNC_RCVD 狀态;
  3. 用戶端協定棧收到 ACK 之後,使得應用程式從 connect 調用傳回,表示用戶端到伺服器端的單向連接配接建立成功,用戶端的狀态為 ESTABLISHED,同時用戶端協定棧也會對伺服器端的 SYN 包進行應答,應答資料為 k+1;
  4. 應答包到達伺服器端後,伺服器端協定棧使得 accept 阻塞調用傳回,這個時候伺服器端到用戶端的單向連接配接也建立成功,伺服器端也進入 ESTABLISHED 狀态。

再以打電話為例子來淺讀一下這幾個知識點:(W:卧底,P:指揮官)

  1. W給P打電話:“摩西摩西,我上線了,我方暗号是j,收到請回答,over”
  2. P接通W的電話,聽到了W的暗号内容,确認了W的身份
  3. 确認W身份後,回複到:“我收到了你的暗号j,我方已準備好,随時可以行動,代号為k,請接受指令”
  4. W收到P的回複後,表示:“收到指令k,over。Just do it”

繼續閱讀