【網絡編程】04-TCP三次握手

服務端準備連接的過程

創建套接字:
int socket(int domain, int type, int protocol)

domain就是指PF_INET、PF_INET6、PF_LOCAL等,表示什麼樣的套接字
type的可用的值如下:
SOCK_STREAM:表示的是字節流,也就是TCP
SOCK_DGRAM:表示的是數據報,也就是UDP。
SOCK_RAW:表示的原始套接字。
參數protocol原本用來指定通信協議的,現在基本已經廢棄,一般寫0即可。

Bind:綁定電話號碼

創建出來的套接字如果需要被別人使用,就需要調用bind函數把套接字和套接字地址綁定。函數調用如下:

bind(int fd, sockaddr * addr, socklen_t len)

第二個參數sockaddr是通用地址格式,雖然是通用地址格式但是實際傳入的可能是IPV4或者IPv6或者本地套接字格式。Bind會根據len字段的長度判斷傳入的addr參數如何解析,len就是傳入的地址長度是一個可變的值。
如下是一段初始化IPv4 TCP套接字的例子。

#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
}

Listen:接上電話線,一切準備就緒

初始化創建的套接字可以理解爲是一個主動套接字,其目的就是之後主動發起請求,而listen函數,可以將主動的套接字變爲被動套接字,告訴操作系統內核,“我這個套接字是等待用戶請求的”。Listen的函數原型如下:

int listen (int socketfd, int backlog)

這裏的第二個參數爲backlog,官方的解釋是未完成連接隊列的大小,這個參數決定了可以接收的併發連接的數目。

Accept:電話鈴響起了

Accept這個函數的作用就是連接建立以後,操作系統內核和應用程序之間的橋樑。函數原型如下:

int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)

cliadd 是通過指針方式獲取的客戶端的地址,addrlen 告訴我們地址的大小,函數的返回值是一個全新的描述字,代表了與客戶端的連接。這裏一定要注意有兩個套接字描述字,第一個是監聽套接字描述字 listensockfd,它是作爲輸入參數存在的;第二個是返回的已連接套接字描述字。

客戶端發起連接的過程

前面講述的 bind、listen 以及 accept 的過程,是典型的服務器端的過程。第一步還是和服務端一樣,要建立一個套接字,方法和前面是一樣的。不一樣的是客戶端需要調用 connect 向服務端發起請求。

connect: 撥打電話

客戶端和服務器端的連接建立,是通過 connect 函數完成的。這是 connect 的構建函數:

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

這個函數的第二個、第三個參數 servaddr 和 addrlen 分別代表指向套接字地址結構的指針和該結構的大小。套接字地址結構必須含有服務器的 IP 地址和端口號。
如果是 TCP 套接字,那麼調用 connect 函數將激發 TCP 的三次握手過程,而且僅在連接建立成功或出錯時才返回。其中出錯返回可能有以下幾種情況:

  • 三次握手無法建立,客戶端發出的 SYN 包沒有任何響應,於是返回 TIMEOUT 錯誤。這種情況比較常見的原因是對應的服務端 IP 寫錯。
  • 客戶端收到了 RST(復位)回答,這時候客戶端會立即返回 CONNECTION REFUSED 錯誤。這種情況比較常見於客戶端發送連接請求時的請求端口寫錯,因爲 RST 是 TCP 在發生錯誤時發送的一種 TCP 分節。產生 RST 的三個條件是:目的地爲某端口的 SYN 到達,然而該端口上沒有正在監聽的服務器(如前所述);TCP 想取消一個已有連接;TCP 接收到一個根本不存在的連接上的分節。
  • 客戶發出的 SYN 包在網絡上引起了"destination unreachable",即目的不可達的錯誤。這種情況比較常見的原因是客戶端和服務器端路由不通。
    對於無論是服務端還是客戶端TCP連接建立過程的流程圖如下所示:
    在這裏插入圖片描述

TCP三次握手

在這裏插入圖片描述
Tcp三次握手的流程如上圖所示。具體過程如下:

  1. 客戶端的協議棧向服務器發送了SYN包,並且告訴服務器當前客戶端發送包的起始序號爲j,客戶端進入SYN_SEND狀態
  2. 服務器端的協議棧收到該SYN包後,向客戶端發送ACK應答,應答值爲j+1,表示是對客戶端SYN的應答,同時服務端發送自己的SYN包告訴當前服務端的報文初識序號爲k。這個時候服務端進入SYN_RCVD狀態
  3. 客戶端協議棧收到自己的SYN包的ACK應答之後,便從connect函數的調用中返回,表示客戶端到服務端的單向連接已經建立完成。客戶端的狀態變爲ESTABLISHED,同時客戶端協議棧也會對服務端發來的SYN進行應答,發送ACK應答值爲k+1.
  4. 客戶端發送的ACK應答達到服務端後服務端協議棧便從accept的函數調用中返回,這個時候服務端到客戶端的單項連接也已經建立ok,服務端也進入ESTABLISHED狀態。

這樣連續的報文發送總共進行了三次,也就是著名的TCP三次握手,其實三次握手本質就是通信的雙方互相確認我發送的你能收到,同時通信的雙方告知對方自己報文的初識序號,爲後面的報文發送做好準備。

發佈了93 篇原創文章 · 獲贊 93 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章