Linux基礎 (十四):socket網絡編程

? ? ? ? ?我們用戶是處在應用層的,根據不同的場景和業務需求,傳輸層就要為我們應用層提供不同的傳輸協議,常見的就是TCP協議和UDP協議,二者各自有不同的特點,網絡中的數據的傳輸其實就是兩個進程間的通信,兩個進程在通信時,傳輸層使用TCP協議將一方進程的應用層的數據傳輸給另一方進程的應用層,我們這一節就是基于TCP協議講解網絡數據的傳輸。

目錄

一、主機字節序列和網絡字節序列

1.1 概念

1.2 接口函數

二、套接字地址結構

2.0 套接字

2.1?通用 socket 地址結構

2.2?專用 socket 地址結構

2.3?IP 地址轉換函數

三、網絡編程接口

3.1 創建套接字(買個手機)

3.2? 套接字地址綁定(為手機辦卡,電話號碼相當于地址)

3.3?從監聽隊列中接收一個連接(開機)

3.4?接受客戶端連接請求并創建新的套接字 (接聽電話)

3.5?客戶端主動與服務器建立連接

3.6 從已連接的套接字中接收數據(TCP數據讀)

3.6 發送數據到已連接的套接字(TCP數據寫)

3.7?從已連接或未連接的套接字接收數據(UDP數據讀)

3.8?通過套接字發送數據到指定目標地址((UDP數據寫)

3.9 關閉套接字

四、TCP 編程流程

五、三次握手和四次揮手(重點面試題)

5.1 三次握手

5.2?可以將三次握手改成兩次握手嗎?

5.3?四次揮手

5.4?可以將四次揮手改成三次揮手嗎?

六、tcp協議服務器-客戶端編程流程實驗(掌握)

6.1 服務器端

6.2 客戶端

七、實驗改進

7.1 服務器端

7.2 客戶端


一、主機字節序列和網絡字節序列

1.1 概念

? ? ? ? 主機字節序列分為大端字節序和小端字節序,不同的主機采用的字節序列可能不同。在兩臺使用不同字節序的主機之間傳遞數據時,可能會出現沖突。所以,在將數據發送到網絡時規定數據使用大端字節序,所以也把大端字節序成為網絡字節序列。對方接收到數據后,可以根據自己的字節序進行轉換。這是為了確保不同系統之間的數據傳輸一致性。無論主機字節序列是什么,數據在網絡上傳輸時都需要轉換為網絡字節序。

  1. 大端字節序是指:一個整數的高位字節存儲在內存的低地址處,低位字節存儲在內存的高地址 處。
  2. 小端字節序是指:整數的高位字節存儲在內存的高地址處,而低位字節則存儲在內存的 低地址處。

1.2 接口函數

? ? ? ? ? 為了在主機字節序列和網絡字節序列之間進行轉換,編程語言通常提供了一些標準函數。Linux 系統提供如下 4 個函數來完成主機字節序和網絡字節序之間的轉換:

#include <netinet/in.h>
uint32_t htonl(uint32_t hostlong); // 長整型的主機字節序轉網絡字節序
uint32_t ntohl(uint32_t netlong); // 長整型的網絡字節序轉主機字節序
uint16_t htons(uint16_t hostshort); // 短整形的主機字節序轉網絡字節序
uint16_t ntohs(uint16_t netshort); // 短整型的網絡字節序轉主機字節序

二、套接字地址結構

2.0 套接字

? ? ? ? 套接字(Socket)是網絡編程中用于描述IP地址和端口的通信端點。它是網絡通信中的一個抽象概念,通常用于描述兩個程序之間的雙向通信鏈路。套接字是網絡編程的基石,允許應用程序通過網絡發送和接收數據。

套接字的類型

套接字主要有兩種類型:

  1. 流式套接字(Stream Socket)

    • 使用TCP(傳輸控制協議)
    • 提供面向連接的、可靠的、基于字節流的通信。
    • 典型應用場景包括HTTP、FTP、SMTP等協議。
  2. 數據報套接字(Datagram Socket)

    • 使用UDP(用戶數據報協議)
    • 提供無連接的、不可靠的、基于數據報的通信。
    • 適用于需要快速傳輸且允許丟包的場景,如視頻流、在線游戲等。

2.1?通用 socket 地址結構

? ? ? ?在網絡編程中,通用的 socket 地址結構(Socket Address Structure)用于存儲網絡地址信息。不同的協議族(如 IPv4、IPv6 等)有不同的地址結構,但都遵循一個通用的框架。socket 網絡編程接口中表示 socket 地址的是結構體 sockaddr,這是所有地址結構的通用基礎,定義在 <sys/socket.h> 頭文件中。它是一個通用的地址結構,包含了地址族信息。

struct sockaddr {sa_family_t sa_family;  // 地址族(Address Family)char sa_data[14];       // 協議地址(Protocol Address)
};

地址族類型通常與協議族類型對應。常見的協議族和對應的地址族如下圖所示:

2.2?專用 socket 地址結構

? ? ? ? TCP/IP 協議族有 sockaddr_in 和 sockaddr_in6 兩個專用 socket 地址結構體,它們分別用于 IPV4 和 IPV6:

sockaddr_in 結構體(用于 IPv4)

? ?sockaddr_in 結構體用于存儲 IPv4 地址信息它定義在 <netinet/in.h> 頭文件中。結構體定義如下:

struct sockaddr_in {sa_family_t    sin_family;   // 地址族(必須為 AF_INET)in_port_t      sin_port;     // 端口號(使用 `htons` 將主機字節序轉換為網絡字節序)struct in_addr sin_addr;     // IP 地址(使用 `inet_pton` 等函數進行賦值)char           sin_zero[8];  // 填充字段,使得結構體大小與 `sockaddr` 一致
};
  • sin_family:地址族,必須設置為 AF_INET
  • sin_port:端口號,必須使用 htons 函數將主機字節序轉換為網絡字節序。
  • sin_addr:IP 地址,通常使用 inet_ptoninet_aton 函數進行設置。
  • sin_zero:填充字段,使得結構體大小與 sockaddr 結構體一致,通常設置為 0。

sockaddr_in6 結構體(用于 IPv6)

? ?sockaddr_in6 結構體用于存儲 IPv6 地址信息。它也定義在 <netinet/in.h> 頭文件中。結構體定義如下:

struct sockaddr_in6 {sa_family_t     sin6_family;   // 地址族(必須為 AF_INET6)in_port_t       sin6_port;     // 端口號(使用 `htons` 將主機字節序轉換為網絡字節序)uint32_t        sin6_flowinfo; // 流信息(通常設置為 0)struct in6_addr sin6_addr;     // IPv6 地址uint32_t        sin6_scope_id; // 范圍 ID(用于本地鏈路地址,通常設置為 0)
};
  • sin6_family:地址族,必須設置為 AF_INET6
  • sin6_port:端口號,必須使用 htons 函數將主機字節序轉換為網絡字節序。
  • sin6_flowinfo:流信息,通常設置為 0。
  • sin6_addr:IPv6 地址,使用 inet_pton 函數進行設置。
  • sin6_scope_id:范圍 ID,主要用于本地鏈路地址,通常設置為 0。

2.3?IP 地址轉換函數

? ? ? 通常,人們習慣用點分(用點分隔)十進制字符串表示 IPV4 地址,但編程中我們需要先把它們轉化為整數(4個字節32位)方能使用,下面函數可用于點分十進制字符串表示的 IPV4 地址和網絡字節序整數表示的 IPV4 地址之間的轉換(字符串轉整型函數接口):

需要引入的頭文件 #include <arpa/inet.h>in_addr_t inet_addr(const char *cp); //字符串表示的 IPV4 地址轉化為無符號整型
char* inet_ntoa(struct in_addr in); // IPV4 地址的網絡字節序(無符號整型)轉化為字符串表示

三、網絡編程接口

3.1 創建套接字(買個手機)

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

?參數解釋

  1. domain: 指定套接字所使用的協議族,也稱為地址族。常見的值包括:

    • AF_INET:IPv4 Internet 協議
    • AF_INET6:IPv6 Internet 協議
    • AF_UNIXAF_LOCAL:本地通信(UNIX 域套接字)
    • AF_PACKET:低級別的套接字接口,用于直接訪問網絡層
  2. type: 指定套接字的服務類型。常見的類型包括:

    • SOCK_STREAM:提供面向連接的可靠字節流服務(如 TCP)
    • SOCK_DGRAM:提供數據報服務(如 UDP)
    • SOCK_RAW:提供原始網絡協議訪問
    • SOCK_SEQPACKET:提供序列包服務,類似于 SOCK_STREAM,但每個消息邊界保留
  3. protocol: 指定使用的協議。通常設置為 0,以選擇默認協議。可以明確指定特定協議:

    • IPPROTO_TCP:如果 type 是 SOCK_STREAM
    • IPPROTO_UDP:如果 type 是 SOCK_DGRAM
    • IPPROTO_ICMP:如果 type 是 SOCK_RAW(用于原始套接字)

返回值

? ? ? ?成功時返回一個非負整數,即套接字文件描述符。 失敗時返回 -1,并設置 errno 以指示錯誤。

3.2? 套接字地址綁定(為手機辦卡,電話號碼相當于地址)

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

? ? ? ? 它是一個用于將套接字綁定到特定的本地地址和端口的系統調用。在網絡編程中,bind 函數通常用于服務器端套接字,以指定它們將在哪個地址和端口上監聽傳入連接

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. addr:

    • 這是一個指向 struct sockaddr 的指針(通用套接字地址結構體指針),包含要綁定到的地址信息。實際的地址結構根據使用的協議族不同而不同
      • 對于 IPv4,使用 struct sockaddr_in
      • 對于 IPv6,使用 struct sockaddr_in6
      • 對于本地通信(UNIX 域套接字),使用 struct sockaddr_un
  3. addrlen:

    • 這是地址結構的長度(以字節為單位)。利用sizeof求得即可,對于 IPv4 地址,通常是 sizeof(struct sockaddr_in);對于 IPv6 地址,通常是 sizeof(struct sockaddr_in6)

返回值

  • 成功時返回 0。
  • 失敗時返回 -1,并設置 errno 以指示錯誤。

3.3?從監聽隊列中接收一個連接(開機)

int listen(int sockfd, int backlog);

? ? ? 它是一個用于在指定的套接字上監聽連接請求的系統調用。它通常用于服務器端的套接字,以便將套接字轉換為被動模式,準備接受來自客戶端的連接請求。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建并綁定了地址(通過 bind 函數)的套接字文件描述符
  2. backlog:

    • accept 函數被調用之前可以排隊的連接請求數量。在Linux系統上指的是已經完成三次握手的客戶端的數量,在unix系統上指的是未完成加已完成的客戶端數量。
    • 如果連接請求的數量超過了此限制,新來的連接請求將被拒絕。

返回值

  • 成功時返回 0。
  • 失敗時返回 -1,并設置 errno 以指示錯誤。

? ? ? ?監聽隊列可以理解為:客戶端向服務器端發送連接請求時,首先,先將它放到監聽隊列中,讓它等著,然后服務器一個一個的從監聽隊列進行連接,相當于銀行大廳的等待區,監聽隊列的大小就是為等待客戶提供的凳子的數量。?

3.4?接受客戶端連接請求并創建新的套接字 (接聽電話)

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

? ? ? ?它是一個用于接受傳入連接請求的系統調用。它通常用于服務器端套接字,用于接受客戶端連接請求并創建新的套接字用于與客戶端通信。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建并綁定了地址(通過 bind 函數)的套接字文件描述符。
  2. addr:

    • 這是一個指向 struct sockaddr 類型的指針用于存儲連接的遠程地址信息(客戶端的套接字地址信息)。可以將其設置為 NULL,如果不關心連接的遠程地址信息。
  3. addrlen:

    • 這是一個指向 socklen_t 類型的指針,指示傳入的地址結構的長度。在調用 accept 函數之前,應該將其設置為 struct sockaddr 結構的大小。

返回值

  • 如果成功,返回一個新的套接字文件描述符,也就是連接套接字,用于與客戶端通信。
  • 如果失敗,返回 -1,并設置 errno 以指示錯誤。

3.5?客戶端主動與服務器建立連接

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

? ? ? 它是一個用于連接到遠程服務器的系統調用。它通常用于客戶端套接字,用于與服務器建立連接。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. serv_addr:

    • 這是一個指向 struct sockaddr 類型的指針,包含遠程服務器的地址信息
  3. addrlen:

    • 這是傳入地址結構的長度(以字節為單位)

返回值

  • 如果成功建立連接,則返回 0。
  • 如果失敗,返回 -1,并設置 errno 以指示錯誤。

3.6 從已連接的套接字中接收數據(TCP數據讀)

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

? ? ?它是一個用于從套接字接收數據的系統調用。它通常用于在網絡編程中從已連接的套接字中接收數據。recv的返回值如果等于0,說明對方關閉了!!!這是循環收發判斷的唯一條件!

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. buff:

    • 這是一個指向接收數據緩沖區的指針,用于存儲接收到的數據。
  3. len:

    • 這是接收數據緩沖區的長度,即 buff 所指向的緩沖區的大小
  4. flags:

    • 這是一組控制接收行為的標志,可以為 0 或以下之一的按位或:
      • MSG_WAITALL:阻塞直到接收到指定長度的數據。
      • MSG_DONTWAIT:非阻塞接收數據。

返回值

  • 如果成功接收到數據,則返回接收到的字節數。
  • 如果連接已關閉,則返回 0。
  • 如果發生錯誤,則返回 -1,并設置 errno 來指示錯誤。

3.6 發送數據到已連接的套接字(TCP數據寫)

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

? ? ? ?它是一個用于將數據通過套接字發送到遠程端的系統調用。通常在網絡編程中,它被用于發送數據到已連接的套接字上。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. buff:

    • 這是一個指向要發送數據的緩沖區的指針
  3. len:

    • 這是要發送的數據的字節數
  4. flags:

    • 這是一組控制發送行為的標志,可以為 0 或以下之一的按位或:
      • MSG_CONFIRM:要求數據發送得到確認。
      • MSG_DONTROUTE:數據不路由,僅限于本地接收。
      • MSG_EOR:數據結束標志。
      • MSG_MORE:還有更多數據等待發送。
      • MSG_NOSIGNAL:忽略 SIGPIPE 信號,如果連接已關閉,則不會引發信號。

返回值

  • 如果成功發送數據,則返回實際發送的字節數。
  • 如果發送過程中出現錯誤,則返回 -1,并設置 errno 來指示錯誤。

3.7?從已連接或未連接的套接字接收數據(UDP數據讀)

ssize_t recvfrom(int sockfd, void *buff, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen);

? ? ? 它是一個用于從已連接或未連接的套接字接收數據的系統調用。與 recv 不同的是,recvfrom 可以從任意地址接收數據,而不僅僅是連接到套接字的對等方。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. buff:

    • 這是一個指向接收數據緩沖區的指針,用于存儲接收到的數據。
  3. len:

    • 這是接收數據緩沖區的長度,即 buff 所指向的緩沖區的大小。
  4. flags:

    • 這是一組控制接收行為的標志,可以為 0 或以下之一的按位或:
      • MSG_WAITALL:阻塞直到接收到指定長度的數據。
      • MSG_DONTWAIT:非阻塞接收數據。
      • MSG_TRUNC:截斷超出緩沖區大小的數據。
  5. src_addr:

    • 這是一個指向存儲發送端地址信息的 struct sockaddr 結構體的指針。
  6. addrlen:

    • 這是傳入地址結構的長度(以字節為單位)。在調用 recvfrom 函數之前,應該將其設置為 struct sockaddr 結構的大小。

返回值

  • 如果成功接收到數據,則返回接收到的字節數。
  • 如果連接已關閉,則返回 0。
  • 如果發生錯誤,則返回 -1,并設置 errno 來指示錯誤。

3.8?通過套接字發送數據到指定目標地址((UDP數據寫)

ssize_t sendto(int sockfd, void *buff, size_t len, int flags, struct sockaddr* dest_addr, socklen_t addrlen);

? ? ? ?它是一個用于通過套接字發送數據到指定目標地址的系統調用。與 send 不同的是,sendto 允許指定目標地址,因此適用于無連接的 UDP 套接字以及有連接的套接字。

參數解釋

  1. sockfd:

    • 這是由 socket 函數創建的套接字文件描述符。
  2. buff:

    • 這是一個指向要發送數據的緩沖區的指針。
  3. len:

    • 這是要發送的數據的字節數。
  4. flags:

    • 這是一組控制發送行為的標志,可以為 0 或以下之一的按位或:
      • MSG_CONFIRM:要求數據發送得到確認。
      • MSG_DONTROUTE:數據不路由,僅限于本地發送。
      • MSG_EOR:數據結束標志。
      • MSG_MORE:還有更多數據等待發送。
      • MSG_NOSIGNAL:忽略 SIGPIPE 信號,如果連接已關閉,則不會引發信號。
  5. dest_addr:

    • 這是一個指向包含目標地址信息的 struct sockaddr 結構體的指針。
  6. addrlen:

    • 這是傳入目標地址結構的長度(以字節為單位)。在調用 sendto 函數之前,應該將其設置為 struct sockaddr 結構的大小。

返回值

  • 如果成功發送數據,則返回實際發送的字節數。
  • 如果發生錯誤,則返回 -1,并設置 errno 來指示錯誤。

3.9 關閉套接字

int close(int sockfd);

它是一個用于關閉套接字的系統調用。關閉套接字后,不再可以使用該套接字進行數據傳輸或接收。

參數解釋

  • sockfd:
    • 這是要關閉的套接字的文件描述符。

返回值

  • 如果成功關閉套接字,則返回 0。
  • 如果發生錯誤,則返回 -1,并設置 errno 來指示錯誤。

四、TCP 編程流程

TCP 提供的是面向連接的、可靠的、字節流服務。TCP 的服務器端和客戶端編程流程如 下:

1、socket()方法是用來創建一個套接字,有了套接字就可以通過網絡進行數據的收發。

? ? ? ? ?這也是為什么進行網絡通信的程序首先要創建一個套接字。創建套接字時要指定使用的服務類型,使用 TCP 協議選擇流式服務(SOCK_STREAM)。

2、bind()方法是用來指定套接字使用的 IP 地址和端口。

? ? ? ? ?IP 地址就是自己主機的地址,如果主機沒有接入網絡,測試程序時可以使用回環地址“127.0.0.1”。端口是一個 16 位的整型值, 一般 0-1024 為知名端口,如 HTTP 使用的 80 號端口。這類端口一般用戶不能隨便使用。其 次,1024-4096 為保留端口,用戶一般也不使用。4096 以上為臨時端口,用戶可以使用。在 Linux 上,1024 以內的端口號,只有 root 用戶可以使用。

3、listen()方法是用來創建監聽隊列。

? ? ? ? ?監聽隊列有兩種,一個是存放未完成三次握手的連接, 一種是存放已完成三次握手的連接。listen()第二個參數就是指定已完成三次握手隊列的長度。

? ? ? ? ?在網絡編程中,服務器端通過監聽指定的網絡地址和端口來等待客戶端的連接請求。

監聽隊列就像是一個等待區,它存放著已經發送連接請求但還沒有得到服務器響應的客戶端連接請求。當一個客戶端請求連接時,服務器將其放入監聽隊列中,然后按照一定的順序逐個處理這些請求。通俗地說,你可以把監聽隊列想象成是一個餐廳的等候區。當你到達餐廳時,可能會看到一個等候區,里面坐滿了等待就座的人。服務員會按照先來后到的順序逐個安排客人入座,就像服務器按照監聽隊列中連接請求的順序逐個處理客戶端的連接請求一樣。

4、accept()處理存放在 listen 創建的已完成三次握手的隊列中的連接。

? ? ? ?每處理一個連接,則 accept()返回該連接對應的套接字描述符。如果該隊列為空,則 accept 阻塞。

5、connect()方法一般由客戶端程序執行,需要指定連接的服務器端的 IP 地址和端口。

? ? ? 該方法執行后,會進行三次握手, 建立連接。

6、send()方法用來向 TCP 連接的對端發送數據。

? ? ? send()執行成功,只能說明將數據成功寫入到發送端的發送緩沖區中,并不能說明數據已經發送到了對端。send()的返回值為實際寫入到發送緩沖區中的數據長度。

7、recv()方法用來接收 TCP 連接的對端發送來的數據。

? ? ? ? recv()從本端的接收緩沖區中讀取數 據,如果接收緩沖區中沒有數據,則 recv()方法會阻塞。返回值是實際讀到的字節數,如果 recv()返回值為 0, 說明對方已經關閉了 TCP 連接。

8、close()方法用來關閉 TCP 連接。

? ? ?此時,會進行四次揮手。

五、三次握手和四次揮手(重點面試題)

5.1 三次握手

? ? ? ? ? 客戶端在進行connect()開始建立連接 之后就會進行三次握手!

? ? ? ? 三次握手是TCP/IP協議中用于建立可靠連接的過程。在進行通信之前,客戶端和服務器之間需要通過三次握手來確認彼此的通信能力和參數設置。這個過程包括以下步驟:

  1. 客戶端發送同步(SYN)報文:客戶端首先向服務器發送一個帶有SYN標志的TCP報文段,表示客戶端想要建立連接,并且指定初始序列號(sequence number)。

  2. 服務器確認同步(SYN-ACK)報文:服務器收到客戶端的SYN報文后,會向客戶端發送一個帶有SYN和ACK標志的TCP報文段作為確認。該報文段中也包含服務器選擇的初始序列號。

  3. 客戶端確認(ACK)報文:最后,客戶端收到服務器的SYN-ACK報文后,會向服務器發送一個帶有ACK標志的TCP報文段作為確認。這個報文段不攜帶SYN標志。

完成了這三次握手之后,客戶端和服務器之間的連接就建立起來了,雙方可以開始進行數據傳輸。這個過程確保了雙方都能夠收到彼此的確認,從而建立了可靠的通信連接。

下面為面試內容!!? ? ?

? ? ? ? 在完成握手時,有兩個隊列,一個是未完三次握手隊列,一個是已完成三次握手隊列,客戶端請求連接,首先會放到未完三次握手隊列,然后等他完成三次握手隊列,也就是建立好連接以后,就會將它放到已完成三次握手隊列,(注意:listen(socked,5) 在linux里這里的5是代表已完成三次握手隊列的大小,在unix里代表未完成和已完成隊列之和。這里的5,不是說只能完成5次握手,而是完成握手隊列里能放5個鏈接,第六個就放在未完成隊列里,等到完成握手隊列里有空位了,在挪下來 ),然后在進行accept()的時候,它會去已完成三次握手隊列的里面去看,如果有已經完成三次握手隊列的客戶端請求,那么他就會與該客戶端建立連接,產生一個連接套接字,否則,他會一直阻塞住!accept只處理已完成握手隊列中的鏈接

三次握手發生在客戶端執行 connect()的時候,該方法返回成功,則說明三次握手已經建 立。三次握手示例圖如下:?

?

現在解釋這個圖:

  1. 客戶端首先向服務器發送一個帶有 SYN(同步)標志的報文,表示客戶端想要建立連接,并指定初始序列號。這是第一次握手。

  2. 服務器收到客戶端的 SYN 報文后,會發送一個帶有 SYN 和 ACK(確認)標志的報文給客戶端,表示服務器收到了客戶端的請求,并同意建立連接,同時服務器也指定了自己的初始序列號。這是第二次握手。

  3. 客戶端收到服務器的 SYN-ACK 報文后,發送一個帶有 ACK 標志的報文給服務器,表示客戶端確認收到了服務器的確認,并同意建立連接。這是第三次握手。

5.2?可以將三次握手改成兩次握手嗎?

? ? ? 不可以!根據TCP協議的設計,三次握手是必需的,并且是建立可靠連接的基礎。在標準的TCP實現中,無法將三次握手簡化為兩次握手。這是因為第三次握手中客戶端必須發送ACK包來確認連接建立,以確保雙方都能夠收到對方的確認信息。

5.3?四次揮手

? ? ? ?執行close()之后就會進行四次揮手操作,服務器端和客服端那一端先close()都可以!

三次揮手是TCP/IP協議中用于關閉連接的過程。與建立連接時的三次握手相似,關閉連接時需要進行四次揮手以確保雙方都能夠完成數據傳輸并關閉連接,這個過程包括以下步驟:

  1. 客戶端發送關閉請求(FIN):當客戶端決定關閉連接時,它會發送一個帶有FIN標志的TCP報文段給服務器,表示它不再發送數據了,但仍然可以接收數據。

  2. 服務器確認關閉請求(ACK):服務器收到客戶端的關閉請求后,會發送一個帶有ACK標志的TCP報文段作為確認,表示它已經收到了客戶端的關閉請求。

  3. 服務器發送關閉請求(FIN):當服務器確定不再發送數據時,它也會向客戶端發送一個帶有FIN標志的TCP報文段,表示它也準備關閉連接。

  4. 客戶端確認關閉請求(ACK):客戶端收到服務器的關閉請求后,會發送一個帶有ACK標志的TCP報文段作為確認。此時,雙方的連接就被完全關閉了。

通過這個四次揮手的過程,客戶端和服務器都有機會告知對方它們不再發送數據,并且確認對方的關閉請求,從而安全地關閉連接,避免數據丟失或不完整的傳輸。

四次揮手發生在客戶端或服務端執行 close()關閉連接的時候,示例圖如下:?

?

這里是四次揮手的解釋:

  1. 客戶端首先發送一個帶有 FIN(關閉請求)標志的報文給服務器,表示客戶端不再發送數據,但仍然能接收數據。這是第一次揮手。

  2. 服務器收到客戶端的 FIN 報文后,發送一個帶有 ACK(確認)標志的報文給客戶端,表示服務器已經收到了客戶端的關閉請求,但服務器還可以向客戶端發送數據。這是第二次揮手。

  3. 服務器在確定不再發送數據后,發送一個帶有 FIN(關閉請求)標志的報文給客戶端,表示服務器也準備關閉連接。這是第三次揮手。

  4. 客戶端收到服務器的 FIN 報文后,發送一個帶有 ACK(確認)標志的報文給服務器,表示客戶端確認收到了服務器的關閉請求。這是第四次揮手。

5.4?可以將四次揮手改成三次揮手嗎?

? ? ? ?可以,四次揮手可以演化成三次揮手 當一端close 發送報文過來,此時我也要close了,回復報文,和通知對方關閉的報文一起發送。

  • 第一次揮手(FIN): 客戶端發送一個FIN報文,表示它要關閉到服務器的數據傳送。
  • 第二次揮手(FIN): 服務器收到FIN后,直接發送一個FIN報文,表示它也要關閉到客戶端的數據傳送。
  • 第三次揮手(ACK): 客戶端收到FIN后,發送一個ACK報文,確認收到關閉請求,連接關閉。

六、tcp協議服務器-客戶端編程流程實驗(掌握)

6.1 服務器端

? ? ? 簡單的TCP服務器,它監聽6000端口,接收來自客戶端的消息,回復“ok”并關閉連接。服務器無限運行,一次處理一個客戶端。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);  //創建套接字   ----->監聽套接字(相當于飯店的接待員)if( sockfd == -1 ){exit(1);}struct sockaddr_in saddr,caddr;                                   //定義服務器套接字地址,客戶端套接字地址memset(&saddr,0,sizeof(saddr));                                 //清零套接字地址結構體的第四個成員/*為服務器套接字地址結構體初始化*/saddr.sin_family = AF_INET;                                      //地址族   IPV4     saddr.sin_port = htons(6000);                                    //端口號   6000saddr.sin_addr.s_addr = inet_addr("43.138.164.79");                  //服務器IP地址;回環地址(用的是測試的本機)int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));    //套接字地址綁定if ( res == -1 ){printf("bind err\n");                                        //這里容易失敗,所以要打印觀察exit(1);}if (listen(sockfd,5) == -1 )                                     //從監聽隊列中接收一個連接{exit(1);             }//服務器無限運行,一次處理一個客戶端。while( 1 )                                                      //循環接收連接{int len = sizeof(caddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len);   //返回值為連接套接字(得到新的套接字描述符),沒有人連接時,可能會阻塞!(沒有客人來)if (c < 0 )                                             //c是套接字文件描述符,相當于服務員,連接失敗{continue;}printf("accept c =%d\n",c );char buff[128] = {0};                                    int n = recv(c,buff,127,0);                           //接收客戶端發送過來的數據,如果客戶端未發送數據,此時便會阻塞! 讀取最多127字節,以留出一個字節用于\0終止符printf("recv=%s\n",buff);                             send(c,"ok",2,0);                                     //服務器給客戶端發送數據close(c);                                             //關閉本次與客戶端連接的套接字描述符}
}

6.2 客戶端

? ? ? ?實現了一個簡單的TCP客戶端,它連接到IP地址為 43.138.164.79、端口為 6000 的服務器,讀取用戶輸入,將輸入發送到服務器,接收服務器的響應并打印,然后關閉連接。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);                   //創建套接字---->監聽套接字if (sockfd == -1 ){exit(1);}/**注意:客戶端不需要綁定套接字地址(調用bind()函數)端口號會隨機分配,IP地址就直接用*//*客戶端需要連接服務器端,因此需要指定連接的服務器的套接字地址,下面的都是服務器的套接字地址信息*/struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;                                    //地址族saddr.sin_port = htons(6000);                                  //服務器的端口號saddr.sin_addr.s_addr = inet_addr("43.138.164.79");            //服務器IP地址int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));  //連接服務器,失敗概率高,打印顯示if (res == -1 ){printf("connect err\n");exit(1);}char buff[128] = {0};printf("input:\n");fgets(buff,128,stdin);send(sockfd,buff,strlen(buff)-1,0);                         //客戶端發送數據給服務器,客戶端不分監聽套接字和連接套接字memset(buff,0,128);recv(sockfd,buff,127,0);                                    //客戶端接收服務器發送過來的數據printf("buff=%s\n",buff);close(sockfd);exit(0);
}

運行有先后順序,先運行服務器端,在運行客戶端。

七、實驗改進

7.1 服務器端

一旦有客戶端連接成功,便會一直建立連接,循環收發數據!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{int sockfd = socket_init();if (sockfd == -1){printf("create socket failed\n");exit(1);}while( 1 ){struct sockaddr_in  caddr;int len = sizeof(caddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//阻塞if (c < 0 ){continue;}//上面一旦有客戶端連接成功,便會進行下面的循環數據收發/****改動之處:一直建立連接,可以循環收發數據****/while ( 1 ){char buff[128] = {0};int n = recv(c,buff,127,0);  //可能會阻塞if(n<=0)                    //n等于0說明對方關閉,n小于0說明出錯了{ break;               //對方關閉后,不需要進行通信了}printf("recv(c=%d)=%s\n",c,buff);send(c,"ok",2,0);}close(c);                   //服務器端也應該關閉與該客戶端進行通信}
}//封裝創建套接字并綁定,進行監聽的函數
int socket_init()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);//tcp if (sockfd == -1){exit(1);}struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;               //地址族 ipv4saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("0.0.0.0");int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));if (res == -1){printf("bind err\n");exit(1);}if (listen(sockfd, 5) == -1){exit(1);}return sockfd;}

7.2 客戶端

客戶端連接成功,便會一直建立連接,循環收發數據!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{//創建套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if (sockfd == -1 ){exit(1);}//連接服務器struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);//ser 6000saddr.sin_addr.s_addr = inet_addr("43.138.164.79");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if (res == -1 ){printf("connect err\n");exit(1);}printf("連接成功!\n");//****客戶端可以循環進行發送***/while(1){ char buff[128] = {0};printf("input:\n");fgets(buff,128,stdin);if (strncmp(buff, "end", 3) == 0){break;}send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}close(sockfd);exit(0);
}

至此,已經講解完畢!篇幅較長,慢慢消化,以上就是全部內容!請務必掌握,創作不易,歡迎大家點贊加關注評論,您的支持是我前進最大的動力!下期再見!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23132.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23132.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23132.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

32C3-2模組與樂鑫ESP32--C3--WROOM--02模組原理圖、升級口說明

模組原理圖&#xff1a; 底板原理圖&#xff1a; u1 是AT通信口&#xff0c;wiif-tx wifi-rx 是升級口&#xff0c;chip-pu是reset復位口&#xff0c;GPIO9拉低復位進入下載模式 ESP32-WROOM-32 系列硬件連接管腳分配? 功能 ESP32 開發板/模組管腳 其它設備管腳 下載固件…

【Python報錯】AttributeError: ‘NoneType‘ object has no attribute ‘xxx‘

成功解決“AttributeError: ‘NoneType’ object has no attribute ‘xxx’”錯誤的全面指南 一、引言 在Python編程中&#xff0c;AttributeError是一種常見的異常類型&#xff0c;它通常表示嘗試訪問對象沒有的屬性或方法。而當我們看到錯誤消息“AttributeError: ‘NoneTyp…

激發AI創新潛能,OPENAIGC開發者大賽賽題解析

人工智能&#xff08;AI&#xff09;的飛速發展&#xff0c;特別是AIGC、大模型、數字人技術的成熟&#xff0c;不僅改變了數據處理和信息消費的方式&#xff0c;也為企業和個人提供了前所未有的機遇。在這種技術進步的背景下&#xff0c;由聯想拯救者、AIGC開放社區、英特爾共…

PostgreSQL的視圖pg_stat_database

PostgreSQL的視圖pg_stat_database pg_stat_database 是 PostgreSQL 中的一個系統視圖&#xff0c;用于提供與數據庫相關的統計信息。這個視圖包含了多個有用的指標&#xff0c;可以幫助數據庫管理員了解數據庫的使用情況和性能。 以下是 pg_stat_database 視圖的主要列和其含…

三生隨記——理發店詭事

在城市的邊緣&#xff0c;隱藏著一家不起眼的理發店。它沒有華麗的裝飾&#xff0c;也沒有喧囂的廣告&#xff0c;只是靜靜地矗立在一條狹窄的小巷盡頭。據說&#xff0c;這家店只在深夜營業&#xff0c;而且只接待那些真心尋求改變的人。 有一天&#xff0c;一個名叫林逸的年輕…

基于SSM+Jsp的高校二手交易平臺

開發語言&#xff1a;Java框架&#xff1a;ssm技術&#xff1a;JSPJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;數據庫工具&#xff1a;Navicat11開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包…

【遠程連接服務器】—— Workbench和Xshell遠程連接阿里云服務器失敗和運行Xshell報錯找不到 MSVCP110.d的問題分析及解決

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、遠程連接不上服務器1. Workbench遠程連接失敗2.Xshell也連接不上3.解決方法(1)問題描述&#xff1a;(2)解決&#xff1a; 4.再次連接服務器 二、運行Xshell…

Android 上層的View透傳/不透傳 點擊事件 到下層

今天有個需求就是在本不該有laoding的地方加個 laoding&#xff0c;源碼中有騰訊的QMUI&#xff0c;所以選用了&#xff0c;QMUILoadingView。 但是有個問題&#xff0c;就是即使這個View蓋在最上層&#xff0c;顯示出來的時候&#xff0c;依然可以點擊下邊的控件。 處理&#…

【前端面試3+1】18 vue2和vue3父傳子通信的差別、props傳遞的數據在子組件是否可以修改、如何往window上添加自定義屬性、【多數元素】

一、vue2和vue3父傳子通信的差別 1、Vue2 父組件向子組件傳遞數據通常通過props屬性來實現。父組件可以在子組件的標簽中使用v-bind指令將數據傳遞給子組件的props屬性。在子組件中&#xff0c;可以通過props屬性來接收這些數據。這種方式是一種單向數據流的方式&#xff0c;父…

常用位算法

1&#xff0c;位翻轉 n^1 &#xff0c;n 是0 或 1&#xff0c;和 1 異或后位翻轉了。 2&#xff0c; 判斷奇偶&#xff0c;n&1&#xff0c;即判斷最后一位是0還是1&#xff0c;如果結果為0&#xff0c;就是偶數&#xff0c;是1 就是奇數。 獲取 32 位二進制的 1 的個數&a…

python-opencv圖像分割

文章目錄 二值化圖像骨骼連通域分割 二值化 所謂圖像分割&#xff0c;就是將圖像的目標和背景分離開來&#xff0c;更直觀一點&#xff0c;就是把目標涂成白色&#xff0c;背景涂成黑色&#xff0c;言盡于此&#xff0c;是不是恍然大悟&#xff1a;這不就是二值化么&#xff1…

香橙派 AIpro 的系統評測

0. 前言 你好&#xff0c;我是悅創。 今天受邀測評 Orange Pi AIpro開發板&#xff0c;我將準備用這個測試簡單的代碼來看看這塊開發版的性能體驗。 分別從&#xff1a;Sysbench、Stress-ng、PyPerformance、RPi.GPIO Benchmark、Geekbench 等方面來測試和分析結果。 下面就…

DevExpress Installed

一、What’s Installed 統一安裝程序將DevExpress控件和庫注冊到Visual Studio中&#xff0c;并安裝DevExpress實用工具、演示應用程序和IDE插件。 Visual Studio工具箱中的DevExpress控件 Visual Studio中的DevExpress菜單 Demo Applications 演示應用程序 Launch the Demo…

Python如何查詢數據庫:深入探索與實踐

Python如何查詢數據庫&#xff1a;深入探索與實踐 在數據驅動的世界中&#xff0c;Python作為一種強大且靈活的語言&#xff0c;自然成為了數據庫查詢的得力助手。本文將通過四個方面、五個方面、六個方面和七個方面&#xff0c;詳細探討Python如何查詢數據庫&#xff0c;并力…

elementary OS 8的新消息

原文&#xff1a;Happy Pride! Have Some Updates! ? elementary Blog 這個月&#xff0c;我們為OS 7帶來了一些意外驚喜&#xff0c;包括GNOME應用的新版本和郵件應用的重大更新。Wayland也來了&#xff0c;我們有了一種新的方式來管理驅動程序&#xff0c;并且我們現在默認…

PS去水印

去除圖片水印 step1&#xff1a;使用套索工具框選圖片水印 step2&#xff1a;CTRLshiftU 去色 step3&#xff1a;CTRLL 色階 step4&#xff1a;使用第三根吸管去點擊需要去掉的圖片水印 成功去掉 去掉文字水印 也可按照上述方法去除

計算機網絡 期末復習(謝希仁版本)第1章

大眾熟知的三大網絡&#xff1a;電信網絡、有線電視網絡、計算機網絡。發展最快起到核心的是計算機網絡。Internet是全球最大、最重要的計算機網絡。互聯網&#xff1a;流行最廣、事實上的標準譯名。互連網&#xff1a;把許多網絡通過一些路由器連接在一起。與網絡相連的計算機…

【多模態】35、TinyLLaVA | 3.1B 的 LMM 模型就可以實現 7B LMM 模型的效果

文章目錄 一、背景二、方法2.1 模型結構2.2 訓練 pipeline 三、模型設置3.1 模型結構3.2 訓練數據3.3 訓練策略3.4 評測 benchmark 四、效果 論文&#xff1a;TinyLLaVA: A Framework of Small-scale Large Multimodal Models 代碼&#xff1a;https://github.com/TinyLLaVA/T…

AcWing 842. 排列數字——算法基礎課題解

AcWing 842. 排列數字 題目描述 給定一個整數 &#x1d45b;&#xff0c;將數字 1~&#x1d45b; 排成一排&#xff0c;將會有很多種排列方法。 現在&#xff0c;請你按照字典序將所有的排列方法輸出。 輸入格式 共一行&#xff0c;包含一個整數 &#x1d45b;。 輸出格…

【Unity性能優化】使用多邊形碰撞器網格太多,性能消耗太大了怎么辦

&#x1f468;?&#x1f4bb;個人主頁&#xff1a;元宇宙-秩沅 &#x1f468;?&#x1f4bb; hallo 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! &#x1f468;?&#x1f4bb; 本文由 秩沅 原創 &#x1f468;?&#x1f4bb; 專欄交流&#x1f9e7;&…