主頁 >  其他 > 【Linux從青銅到王者】第十五篇:Linux網路編程套接字兩萬字詳解

【Linux從青銅到王者】第十五篇:Linux網路編程套接字兩萬字詳解

2021-08-06 07:45:25 其他

在這里插入圖片描述

系列文章目錄


文章目錄

  • 系列文章目錄
  • 前言
  • 一、網路資料的五元組資訊
      • 1.理解源IP地址和目的IP地址
      • 2.理解 "埠號" 和 "行程ID"
      • 3.理解源埠號和目的埠號
      • 4.理解TCP協議
      • 5.理解UDP協議
  • 二、主機位元組序<===>網路位元組序
  • 三、點分十進制IP<===>uint32_t
  • 四、UDP的socket編程(流程&介面)
      • 1.UDP的socket編程流程
        • 1.socket常見API
        • 2.socketaddr結構的分類
        • 3.socketaddr結構
        • 4.socketaddr_in結構
        • 5.in_addr結構
      • 2.UDP的socket編程介面
        • 1.創建套接字socket介面
        • 2.系結埠號bind介面
        • 3.UDP發送介面sendto
        • 4.UDP接收介面recvform
        • 5.UDP關閉介面close
      • 3.客戶端為什么不推薦系結地址資訊
  • 五、UDP的socket編程代碼
      • 1.客戶端
      • 2.服務端
      • 3.查看埠的使用情況:netstat -anp | grep [埠號]
  • 六、TCP的socket編程(流程&介面)
      • 1.TCP的socket編程流程
      • 2.TCP的socket編程介面
        • 1.服務端創建套接字socket介面
        • 2.服務端系結套接字bind介面
        • 3.服務端監聽套接字listen介面
        • 4.服務端接收鏈接套接字accept介面
        • 5.客戶端連接套接字connect介面
        • 6.TCP發送介面send介面
        • 7.TCP接收介面recv介面
        • 8.TCP關閉介面close介面
      • 3.TCP的連接建立
      • 4.單行程的TCP的發送和接收資料
        • 1.客戶端代碼
        • 2.服務端端代碼
      • 5.單行程的TCP的發送和接收資料的問題
      • 6.多執行緒的TCP的發送和接收資料
        • 1.客戶端代碼
        • 2.服務端代碼
      • 7. 多行程的TCP的發送和接收資料
        • 1.客戶端代碼
        • 2.服務端代碼
  • 七、TCP協議通訊流程
  • 💬總結


前言


在這里插入圖片描述

一、網路資料的五元組資訊

在這里插入圖片描述

1.理解源IP地址和目的IP地址

在這里插入圖片描述

  • 在IP資料包頭部中, 有兩個IP地址, 分別叫做源IP地址, 和目的IP地址:
  • 源IP地址:表示該條資訊來源于哪個機器,
  • 目的IP地址:表示該條資訊去往于哪個行程,

2.理解 “埠號” 和 “行程ID”

  • 埠號(port)是傳輸層協議的內容:
  • 埠號是一個2位元組16位的整數,
  • 埠號用來標識一個行程, 告訴作業系統, 當前的這個資料要交給哪一個行程來處理,
  • IP地址 + 埠號能夠標識網路上的某一臺主機的某一個行程,
  • 一個埠號只能被一個行程占用,
  • 一個行程可以系結多個埠號,
  • pid 表示唯一一個行程; 此處我們的埠號也是唯一表示一個行程,

3.理解源埠號和目的埠號

  • 傳輸層協議(TCP和UDP)的資料段中有兩個埠號, 分別叫做源埠號和目的埠號. 就是在描述 “資料是誰發的, 要發給誰”:
  • 源埠號:表示該條資訊來源于哪個行程,
  • 目的埠號:表示該條資訊去往于哪個機器,

以寄快遞為例子:
在這里插入圖片描述

4.理解TCP協議

在這里插入圖片描述

  • 協議:兩臺機器傳輸時用哪種協議,
  • TCP(Transmission Control Protocol 傳輸控制協議)有一個直觀的認識; 后面我們再詳細討論TCP的一些細節問題,
  • 傳輸層協議,
  • 有連接:雙方在發送網路資料之前必須建立連接,在進行發送,
  • 可靠傳輸:保證資料是可靠并且有序的到達對端,例如發送123、456時123資料先到達,456資料后到達,但是有時可以456資料先到達傳輸層,但會阻塞等待先等前面的資料就是123先到達,
  • 面向位元組流:TCP發送資料的單位是以位元組為單位,并且資料沒有明顯的邊界例如:123456資料不會分開,
    在這里插入圖片描述
    假設應用層要想傳輸層傳入“hello”,當hello傳入傳輸層還尾傳入網路層時,應用層又想向傳輸層傳入“world”,此時是不能傳輸的,只有等“hello”從傳輸層傳入網路層,“world”才能從應用層傳入傳輸層:
    在這里插入圖片描述

5.理解UDP協議

在這里插入圖片描述

  • 此處我們也是對UDP(User Datagram Protocol 用戶資料報協議)有一個直觀的認識; 后面再詳細討論,
  • 傳輸層協議,
  • 無連接:雙方在發送網路資料之前不需要建立連接,直接發送,客服端不用管服務端是否在線,
  • 可靠傳輸:UDP并不會保證資料有序的到達對端
  • 面向位元組流:UDP不管向應用層還是網路層傳遞資料都是整條資料

假設A機器的應用層先向傳輸層傳入一個“aaa”,再向傳輸層傳入一個“bbb”,到待對端機器的傳輸層不會區分,是不是一次傳過來的:
在這里插入圖片描述
在這里插入圖片描述

二、主機位元組序<===>網路位元組序

在這里插入圖片描述

  • 我們已經知道,記憶體中的多位元組資料相對于記憶體地址有大端和小端之分, 磁盤檔案中的多位元組資料相對于檔案中的偏移地址也有大端小端之分, 網路資料流同樣有大端小端之分. 那么如何定義網路資料流的地址呢?
  • 發送主機通常將發送緩沖區中的資料按記憶體地址從低到高的順序發出,
  • 接收主機把從網路上接到的位元組依次保存在接識訓沖區中,也是按記憶體地址從低到高的順序保存,
  • 網路資料流的地址應這樣規定:先發出的資料是低地址,后發出的資料是高地址,
  • TCP/IP協議規定,網路資料流應采用大端位元組序,即低地址高位元組,
  • 不管這臺主機是大端機還是小端機, 都會按照這個TCP/IP規定的網路位元組序來發送/接收資料,
  • 如果當前發送主機是小端, 就需要先將資料轉成大端; 否則就忽略, 直接發送即可,
    在這里插入圖片描述
  • 網路資料需要進行轉發之前:由主機位元組序轉換成為網路位元組序,
  • 網路資料接收之前:由網路位元組序轉換成為主機位元組序,
  • 【問題】為什么網路資料需要進行轉化成為網路位元組序?
  • 網路規定采用大端位元組序作為網路位元組序,
  • 路由設備或者交換機需要對網路資料進行分用到網路層面,以獲取到“目的IP地址”,而這些設備在進行分用的時候默認是按照網路位元組序進行分用的,
  • 主機位元組序轉換為網路位元組序(host to network)
  • 2個位元組 ?uint16_t htons(uint16_t hostshort),
  • 4個位元組 ?uint32_t htonl(uint32_t hostlong),
  • 網路位元組序轉換為主機位元組序( to network)
  • 2個位元組 ?uint16_t ntohs(uint16_t netshort);
  • 4個位元組 ?uint32_t ntohl(uint32_t netlong);

三、點分十進制IP<===>uint32_t

在這里插入圖片描述
本節只介紹基于IPv4的socket網路編程,sockaddr_in中的成員struct in_addr sin_addr表示32位 的IP 地址但是我們通常用點分十進制的字串表示IP 地址,以下函式可以在字串表示 和in_addr表示之間轉換;字串轉in_addr的函式,

  • 點分十進制IP轉換成為uint32_t
  • in_addr_t inet_addr(const char * cp);
  • 將字串的點分十進制IP地址轉換為uint32_t
  • 將uint32_t從主機位元組序轉換成為網路位元組序,
  • uint32_t轉換成為點分十進制IP
  • char * inet_ntoa(struct in_addr in);
  • 將網路位元組序uint32_t的整數轉換成為主機位元組序,
  • 將uint32_t轉換成為點分十進制的字串,

四、UDP的socket編程(流程&介面)

1.UDP的socket編程流程

  • cs模型(客戶端服務端):client-server,
  • bs模型:瀏覽器-服務器,

在這里插入圖片描述

1.socket常見API

// 創建 socket 檔案描述符 (TCP/UDP, 客戶端 + 服務器)
int socket(int domain, int type, int protocol);
// 系結埠號 (TCP/UDP, 服務器)
int bind(int socket, const struct sockaddr * address,socklen_t address_len);*
// 開始監聽socket (TCP, 服務器)
int listen(int socket, int backlog);
// 接收請求 (TCP, 服務器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立連接 (TCP, 客戶端)
int connect(int sockfd, const struct sockaddr*addr,socklen_t addrlen);

2.socketaddr結構的分類

socket API是一層抽象的網路編程介面,適用于各種底層網路協議,如IPv4、 IPv6,以及后面要講的UNIX DomainSocket. 然而, 各種網路協議的地址格式并不相同,
在這里插入圖片描述

  • IPv4和IPv6的地址格式定義在netinet/in.h中,IPv4地址用sockaddr_in結構體表示,包括16位地址型別, 16位埠號和32位IP地址,
  • IPv4、 IPv6地址型別分別定義為常數AF_INET、 AF_INET6. 這樣,只要取得某種sockaddr結構體的首地址,不需要知道具體是哪種型別的sockaddr結構體,就可以根據地址型別欄位確定結構體中的內容,
  • socket API可以都用struct sockaddr * 型別表示, 在使用的時候需要強制轉化成sockaddr_in; 這樣的好處是程式的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各種型別的sockaddr結構體指標做為引數,

3.socketaddr結構

在這里插入圖片描述

4.socketaddr_in結構

在這里插入圖片描述
雖然socket api的介面是sockaddr, 但是我們真正在基于IPv4編程時, 使用的資料結構是sockaddr_in; 這個結構里主要有三部分資訊: 地址型別, 埠號, IP地址,

5.in_addr結構

在這里插入圖片描述
in_addr用來表示一個IPv4的IP地址. 其實就是一個32位的整數,

2.UDP的socket編程介面

1.創建套接字socket介面

// 創建 socket 檔案描述符 (TCP/UDP, 客戶端 + 服務器)
int socket(int domain, int type, int protocol);

在這里插入圖片描述

     1	#include<iostream>
     2	#include<sys/types.h>
     3	#include<sys/socket.h>
     4	#include<unistd.h>
     5	
     6	using namespace std;
     7	int main()
     8	{
     9	    int SockFd=socket(AF_INET,SOCK_STREAM,0);
    10	    if(SockFd<0)
    11	    {
    12	        cout<<"套接字創建失敗!"<<endl;
    13	    }
    14	    cout<<"SockFd:"<<SockFd<<endl;
    15	    while(1)
    16	    {
    17	        sleep(1);
    18	    }
    19	    return 0;
    20	}

在這里插入圖片描述

2.系結埠號bind介面

在這里插入圖片描述

// 系結埠號 (TCP/UDP, 服務器)
int bind(int socket, const struct sockaddr * address,socklen_t address_len);*

  • sockfd:socket函式回傳的套接字描述符;將創建出來的套接字和網卡,埠好進行系結
  • addr:地址資訊結構
  • addr的型別是struct sockaddr ,struct sockaddr 是一個通用地址資訊結構,如下圖所示:
    在這里插入圖片描述
    假設,定義一個int fun(void * x)引數可以接收任何型別資料的函式,使用時就需要強轉 char* p = “abc”; ? fun((void*)lp);而如上結構體的作用相當于此例中的引數,因為bind函式可能系結 ipv4(uint32_t) / ipv6(uint128_t) / 本地域套接字 等不同型別的協議,所以系結不同版本的IP地址需要提供不同的系結函式,而此做法非常的麻煩,所以將協議的資料結構定義為一個通用的,要使用某一具體的協議,只需傳入具體的協議對應的資料結構并強轉即可,
    如下圖所示,我們可以在 vim /usr/include/netinet/in.h 路徑下查看ipv4協議使用的結構體:在這里插入圖片描述
  • addrlen:地址資訊結構的長度(告訴網路協議堆疊最多能決議多少個位元組)
     1	#include<iostream>
     2	#include<sys/types.h>
     3	#include<sys/socket.h>
     4	#include<unistd.h>
     5	#include<netinet/in.h>
     6	#include<arpa/inet.h>
     7	using namespace std;
     8	
     9	int main()
    10	{
    11	    int SockFd=socket(AF_INET,SOCK_DGRAM,0);
    12	    if(SockFd<0)
    13	    {
    14	        cout<<"套接字創建失敗!"<<endl;
    15	    }
    16	    cout<<"SockFd:"<<SockFd<<endl;
    17	
    18	    struct sockaddr_in addr; 
    19	
    20	    addr.sin_port=htons(20000);
    21	    addr.sin_family=AF_INET;
    22	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    23	    int ret=bind(SockFd,(struct sockaddr*)&addr,sizeof(addr));
    24	    if(ret<0)
    25	    {
    26	        cout<<"系結失敗!"<<endl;
    27	        return 0;
    28	    }
    29	    while(1)
    30	    {
    31	        sleep(1);
    32	    }
    33	    return 0;
    34	}

在這里插入圖片描述

3.UDP發送介面sendto

在這里插入圖片描述

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

  • sockfd:套接字描述符
  • buf:要發送的資料
  • len:發送資料的長度
  • flags:0 阻塞發送
  • dest_addr:目標主機的地址資訊結構(IP,port)
  • addrlen:目標主機地址資訊結構的長度
  • 回傳值:
    成功回傳具體發送的位元組數量,失敗回傳-1

4.UDP接收介面recvform

在這里插入圖片描述

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

  • sockfd:套接字描述符
  • buf:將資料接收到buf當中
  • len:buf的最大接收能力
  • flags:0阻塞接收
  • src_addr:這個資料來源的主機的地址資訊結構(IP,port)---->由recvfrom()函式填充
  • addrlen:輸入輸出型引數
    輸入:在接收之前準備的對端地址資訊結構的長度
    輸出:實際接識訓來的地址資訊長度

5.UDP關閉介面close

close(int sockfd);

3.客戶端為什么不推薦系結地址資訊

本質上是不想讓客戶端程式將埠寫死,即不想讓客戶端在啟動的時候,都是系結一個埠的(一個埠只能被一個行程所系結),

eg:客戶端A系結了埠,本機在啟動客戶端B的時候就會系結失敗
??當客戶端沒有主動的系結埠,UDP客戶端在呼叫sendto的時候,會自動系結一個空閑的埠(作業系統分配一個空閑的埠),

五、UDP的socket編程代碼

1.客戶端

客戶端只需創建套接字,向服務端發送請求,接收服務端的回復即可,

     1	#include<iostream>
     2	#include<stdio.h>
     3	#include<sys/types.h>
     4	#include<sys/socket.h>
     5	#include<unistd.h>
     6	#include<netinet/in.h>
     7	#include<arpa/inet.h>
     8	#include<string.h>
     9	#include<stdlib.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int SockFd=socket(AF_INET,SOCK_DGRAM,0);
    15	    if(SockFd<0)
    16	    {
    17	        cout<<"套接字創建失敗!"<<endl;
    18	    }
    19	    cout<<"SockFd:"<<SockFd<<endl;
    20	
    21	   /* struct sockaddr_in addr; 
    22	
    23	    addr.sin_port=htons(20000);
    24	    addr.sin_family=AF_INET;
    25	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    26	    int ret=bind(SockFd,(struct sockaddr*)&addr,sizeof(addr));
    27	    if(ret<0)
    28	    {
    29	        cout<<"系結失敗!"<<endl;
    30	        return 0;
    31	    }*/
    32	    while(1)
    33	    {
    34	        char buf[1024]="i am client!";
    35	        struct sockaddr_in dest_addr;
    36	        dest_addr.sin_family=AF_INET;
    37	        dest_addr.sin_port=htons(20000);
    38	        dest_addr.sin_addr.s_addr=inet_addr("1.14.165.138");
    39	
    40	        sendto(SockFd,buf,strlen(buf),0,(struct sockaddr*)&dest_addr,sizeof(dest_addr));
    41	
    42	        memset(buf,'\0',sizeof(buf));
    43	
    44	        struct sockaddr_in peer_addr;
    45	        socklen_t len=sizeof(peer_addr);
    46	
    47	        ssize_t rece_size=recvfrom(SockFd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer_addr,&len);
    48	        if(rece_size<0)
    49	        {
    50	            continue;
    51	        }
    52	        cout<<"recv msg:"<<buf<<" from "<<inet_ntoa(peer_addr.sin_addr)<<" "<<ntohs(peer_addr.sin_port)<<endl;
    53	        sleep(1);
    54	    }
    55	    close(SockFd);
    56	    return 0;
    57	}

2.服務端

服務端只需創建套接字,系結埠,接收客戶端的請求,回復客戶端資訊即可,

     1	#include<iostream>
     2	#include<stdio.h>
     3	#include<sys/types.h>
     4	#include<sys/socket.h>
     5	#include<unistd.h>
     6	#include<netinet/in.h>
     7	#include<arpa/inet.h>
     8	#include<string.h>
     9	#include<stdlib.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int SockFd=socket(AF_INET,SOCK_DGRAM,0);
    15	    if(SockFd<0)
    16	    {
    17	        cout<<"套接字創建失敗!"<<endl;
    18	    }
    19	    cout<<"SockFd:"<<SockFd<<endl;
    20	
    21	    struct sockaddr_in addr; 
    22	
    23	    addr.sin_port=htons(20000);
    24	    addr.sin_family=AF_INET;
    25	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    26	    int ret=bind(SockFd,(struct sockaddr*)&addr,sizeof(addr));
    27	    if(ret<0)
    28	    {
    29	        cout<<"系結失敗!"<<endl;
    30	        return 0;
    31	    }
    32	    while(1)
    33	    {
    34	        char buf[1024]={0};
    35	        struct sockaddr_in peer_addr;
    36	        socklen_t len=sizeof(peer_addr);
    37	        ssize_t rece_size=recvfrom(SockFd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer_addr,&len);
    38	        if(rece_size<0)
    39	        {
    40	            continue;
    41	        }
    42	        cout<<"recv msg:"<<buf<<" from "<<inet_ntoa(peer_addr.sin_addr)<<" "<<ntohs(peer_addr.sin_port)<<endl;
    43	
    44	        memset(buf,'\0',sizeof(buf));
    45	        sprintf(buf,"welcome client %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
    46	        sendto(SockFd,buf,strlen(buf),0,(struct sockaddr*)&peer_addr,sizeof(peer_addr));
    47	    }
    48	    close(SockFd);
    49	    return 0;
    50	}

在這里插入圖片描述

3.查看埠的使用情況:netstat -anp | grep [埠號]

在這里插入圖片描述

六、TCP的socket編程(流程&介面)

在這里插入圖片描述

1.TCP的socket編程流程

在這里插入圖片描述

2.TCP的socket編程介面

創建套接字介面socket(),系結埠bind(),關閉套接字介面close(),的使用和UDP套接字編程中的使用是一樣的,下面介紹程式中用到的socket API,這些函式都在sys/socket.h中,

1.服務端創建套接字socket介面

在這里插入圖片描述

  • socket()打開一個網路通訊埠,如果成功的話,就像open()一樣回傳一個檔案描述符,
  • 應用程式可以像讀寫檔案一樣用read/write在網路上收發資料,
  • 如果socket()呼叫出錯則回傳-1,
  • 對于IPv4, family引數指定為AF_INET,
  • 對于TCP協議,type引數指定為SOCK_STREAM, 表示面向流的傳輸協議,
  • protocol引數的介紹從略,指定為0即可,
     1	#include<unistd.h>
     2	#include<iostream>
     3	#include<stdlib.h>
     4	#include<sys/types.h>
     5	#include<sys/socket.h>
     6	#include<arpa/inet.h>
     7	#include<error.h>
     8	#include<stdio.h>
     9	using namespace std;
    10	
    11	int main()
    12	{
    13	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    14	
    15	    if(listen_sock<0)
    16	    {
    17	        perror("socket");
    18	        return 0;
    19	    }
    20	    cout<<listen_sock<<endl;
    21	    return 0;
    22	}

在這里插入圖片描述

2.服務端系結套接字bind介面

在這里插入圖片描述

  • 服務器程式所監聽的網路地址和埠號通常是固定不變的,客戶端程式得知服務器程式的地址和埠號后就可以向服務器發起連接; 服務器需要呼叫bind系結一個固定的網路地址和埠號,
  • bind()成功回傳0,失敗回傳-1,
  • bind()的作用是將引數sockfd和myaddr系結在一起, 使sockfd這個用于網路通訊的檔案描述符監聽myaddr所描述的地址和埠號,
     1	#include<unistd.h>
     2	#include<iostream>
     3	#include<stdlib.h>
     4	#include<sys/types.h>
     5	#include<sys/socket.h>
     6	#include<arpa/inet.h>
     7	#include<error.h>
     8	#include<stdio.h>
     9	using namespace std;
    10	
    11	int main()
    12	{
    13	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    14	
    15	    if(listen_sock<0)
    16	    {
    17	        perror("socket");
    18	        return 0;
    19	    }
    20	    cout<<listen_sock<<endl;
    21	
    22	    struct sockaddr_in addr;
    23	
    24	    addr.sin_family=AF_INET;
    25	    addr.sin_port=htons(20000);
    26	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    27	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    28	    int ret=bind(listen_sock,(struct sockaddr*)&addr,sizeof(addr));
    29	
    30	    if(ret<0)
    31	    {
    32	        perror("bind");
    33	        return 0;
    34	    }
    35	
    36	    return 0;
    37	}

在這里插入圖片描述
在這里插入圖片描述

3.服務端監聽套接字listen介面

在這里插入圖片描述

int listen(int sockfd, int backlog);

  • sockfd:套接字描述符
  • backlog:已完成連接佇列的大小
  • 回傳值:
    ??成功:0
    ??失敗:-1
    在這里插入圖片描述
  • listen()宣告sockfd處于監聽狀態, 并且最多允許有backlog個客戶端處于連接等待狀態, 如果接收到更多的連接請求就忽略, 這里設定不會太大(一般是5),,
  • listen()成功回傳0,失敗回傳-1,

當客戶端和服務端進行三次握手的時候會存在兩種狀態:連接還未建立和連接已建立,此時作業系統內核中就會存在兩個佇列:未完成連接佇列和已完成連接佇列,

在這里插入圖片描述
如上圖若客戶端只完成①或①②則此連接在未完成連接佇列中,當完成三次握手后會由未完成連接佇列放到已完成連接佇列,而backlog就是已完成連接佇列的大小,backlog影響了服務端并發接收連接的能力,

eg:假設backlog=1,服務端不accepct接收連接,此時有三個客戶端都完成了三次握手,則必有一個客戶端連接進入已完成連接佇列中,由于已完成連接佇列空間不夠,所以剩余兩個客戶端的連接只能放入未完成連接佇列中,

     1	#include<unistd.h>
     2	#include<iostream>
     3	#include<stdlib.h>
     4	#include<sys/types.h>
     5	#include<sys/socket.h>
     6	#include<arpa/inet.h>
     7	#include<error.h>
     8	#include<stdio.h>
     9	using namespace std;
    10	
    11	int main()
    12	{
    13	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    14	
    15	    if(listen_sock<0)
    16	    {
    17	        perror("socket");
    18	        return 0;
    19	    }
    20	    cout<<listen_sock<<endl;
    21	
    22	    struct sockaddr_in addr;
    23	
    24	    addr.sin_family=AF_INET;
    25	    addr.sin_port=htons(20000);
    26	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    27	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    28	    int ret=bind(listen_sock,(struct sockaddr*)&addr,sizeof(addr));
    29	
    30	    if(ret<0)
    31	    {
    32	        perror("bind");
    33	        return 0;
    34	    }
    35	
    36	    ret=listen(listen_sock,1);
    37	    if(ret<0)
    38	    {
    39	        perror("listen");
    40	        return 0;
    41	    }
    42	
    43	    while(1)
    44	    {
    45	        sleep(1);
    46	    }
    47	
    48	    return 0;
    49	}

在這里插入圖片描述

4.服務端接收鏈接套接字accept介面

在這里插入圖片描述
從已經完成連接佇列中獲取已經完成三次握手的連接,沒有連接時,呼叫accept會阻塞,

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

  • sockfd:套接字描述符(listen_sockfd)
  • addr:客戶端地址資訊結構(客戶端IP,客戶端的埠)
  • addrlen:客戶端地址資訊結構的長度
  • 回傳值:
    ??成功:回傳新連接的套接字描述符
    ??失敗:回傳-1

三次握手的時候是對listen_sockfd進行操作,當呼叫accept()會在Tcp服務端內部創建一個新的套接字new_sockfd,三次握手之后的資料收發都是多new_sockfd進行操作,如下圖所示:
在這里插入圖片描述

  • 三次握手完成后, 服務器呼叫accept()接受連接,
  • 如果服務器呼叫accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來,
  • addr是一個傳出引數,accept()回傳時傳出客戶端的地址和埠號,
  • addr是一個傳出引數,accept()回傳時傳出客戶端的地址和埠號,
  • addrlen引數是一個傳入傳出引數(value-result argument), 傳入的是呼叫者提供的, 緩沖區addr的長度以避免緩沖區溢位問題, 傳出的是客戶端地址結構體的實際長度(有可能沒有占滿呼叫者提供的緩沖區),

5.客戶端連接套接字connect介面

在這里插入圖片描述

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

  • sockfd:套接字描述符(listen_sockfd)
  • addr:服務端地址資訊結構(服務端IP,服務端的埠)
  • addrlen:服務端地址資訊結構的長度
  • 回傳值:
    ??成功:回傳0
    ??小于0,連接失敗
  • 客戶端需要呼叫connect()連接服務器,
  • connect和bind的引數形式一致, 區別在于bind的引數是自己的地址, 而connect的引數是對方的地址,

6.TCP發送介面send介面

在這里插入圖片描述

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

  • sockfd:套接字描述符(new_sockfd)
  • buf:待要發送的資料
  • len:發送資料的長度
  • flags:
    ??0:阻塞發送
    ??MSG_OOB:發送帶外資料
    回傳值:
    ??大于0:回傳發送的位元組數量
    ??-1:發送失敗

帶外資料:即在緊急情況下所產生的資料,會越過前面進行排隊的數據優先進行發送,

7.TCP接收介面recv介面

在這里插入圖片描述

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

  • sockfd:套接字描述符(new_sockfd)
  • buf:將接收的資料放到buf
  • len:buf的最大接收能力
  • flags:0:阻塞發送;如果客戶端沒有發送資料,呼叫recv會阻塞
  • 回傳值:
    ??大于0:正常接收了多少位元組資料
    ??等于0:對端將連接關閉了
    ??小于0:接受失敗

8.TCP關閉介面close介面

在這里插入圖片描述

3.TCP的連接建立

我們可以用cmd工具telnet模仿TCP三次握手建立連接,在cmd視窗輸入 “tenlet + 公網IP + 埠號” 即可模擬測驗:
在這里插入圖片描述
按下回車鍵出現以下現象則連接成功:
在這里插入圖片描述
當我們使用telnet與服務器建立三次連接(即進行三次三次握手)我們會看到,當我們查看服務器埠的使用情況是時會看到如下情況:
在這里插入圖片描述
雖然我們在代碼中將已完成連接佇列的大小設為1,但上圖已完成連接佇列中卻放了兩個已完成連接,正常情況當我們就backlog設為1,已完成連接佇列中只能放一個已完成連接,那么為什么會出現種情況呢?原因是作業系統內核中判斷已完成佇列是否已滿的邏輯是如下所示:
在這里插入圖片描述
所以我們設定的bakclog=1,向已完成連接佇列中放入一個已完成連接,queue.size= 1不大于backlog=1,所以再向已完成連接佇列中放入一個已完成連接,此時queue.size= 2大于backlog=1,不再放入,所以就出現如上圖所示現象,雖然我們將backlog設為1,但已完成連接佇列中卻有兩個已完成連接,

4.單行程的TCP的發送和接收資料

1.客戶端代碼

客戶端流程:創建套接字,發起連接connect,發送和接收:

     1	#include<unistd.h>
     2	#include<string.h>
     3	#include<iostream>
     4	#include<stdlib.h>
     5	#include<sys/types.h>
     6	#include<sys/socket.h>
     7	#include<arpa/inet.h>
     8	#include<error.h>
     9	#include<stdio.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    15	
    16	    if(sockfd<0)
    17	    {
    18	        perror("socket");
    19	        return 0;
    20	    }
    21	    cout<<sockfd<<endl;
    22	
    23	    struct sockaddr_in addr;
    24	
    25	    addr.sin_family=AF_INET;
    26	    addr.sin_port=htons(20000);
    27	    addr.sin_addr.s_addr=inet_addr("1.14.165.138");
    28	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    29	    int ret=connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    30	
    31	    if(ret<0)
    32	    {
    33	        perror("connect");
    34	        return 0;
    35	    }
    36	
    37	    if(ret<0)
    38	    {
    39	        perror("connect");
    40	        return 0;
    41	    }
    42	
    43	    
    44	    while(1)
    45	    {
    46	        char buf[1024]="i am client呀呀呀!";
    47	
    48	        send(sockfd,buf,strlen(buf),0);
    49	        memset(buf,'\0',sizeof(buf));
    50	        //接收
    51	        ssize_t recv_size=recv(sockfd,buf,sizeof(buf)-1,0);
    52	        if(recv_size<0)
    53	        {
    54	            perror("recv");
    55	            return 0;
    56	        }
    57	        else if(recv_size==0)
    58	        {
    59	            cout<<"close peer connect"<<endl;
    60	            close(sockfd);
    61	            continue;
    62	        }
    63	
    64	        cout<<"buf:"<<buf<<endl;
    65	
    66	
    67	
    68	
    69	        sleep(1);
    70	    }
    71	
    72	    return 0;
    73	}

2.服務端端代碼

服務端流程:創建偵聽套接字,系結地址資訊,監聽,接收新連接accept,接收,發送:

     1	#include<unistd.h>
     2	#include<string.h>
     3	#include<iostream>
     4	#include<stdlib.h>
     5	#include<sys/types.h>
     6	#include<sys/socket.h>
     7	#include<arpa/inet.h>
     8	#include<error.h>
     9	#include<stdio.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    15	
    16	    if(listen_sock<0)
    17	    {
    18	        perror("socket");
    19	        return 0;
    20	    }
    21	    cout<<listen_sock<<endl;
    22	
    23	    struct sockaddr_in addr;
    24	
    25	    addr.sin_family=AF_INET;
    26	    addr.sin_port=htons(20000);
    27	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    28	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    29	    int ret=bind(listen_sock,(struct sockaddr*)&addr,sizeof(addr));
    30	
    31	    if(ret<0)
    32	    {
    33	        perror("bind");
    34	        return 0;
    35	    }
    36	
    37	    ret=listen(listen_sock,1);
    38	    if(ret<0)
    39	    {
    40	        perror("listen");
    41	        return 0;
    42	    }
    43	
    44	    struct sockaddr_in cli_addr;
    45	    socklen_t cli_addrlen=sizeof(cli_addr);
    46	    int newsockfd=accept(listen_sock,(struct sockaddr*)&cli_addr,&cli_addrlen);
    47	    if(newsockfd<0)
    48	    {
    49	        perror("accept");
    50	        return 0;
    51	    }
    52	    
    53	    cout<<"i accept new connect form client:"<<inet_ntoa(cli_addr.sin_addr)<<" :"<<ntohs(cli_addr.sin_port)<<endl;
    54	    while(1)
    55	    {
    56	        char buf[1024]={0};
    57	
    58	        ssize_t recv_size=recv(newsockfd,buf,sizeof(buf)-1,0);
    59	        if(recv_size<0)
    60	        {
    61	            perror("recv");
    62	            return 0;
    63	        }
    64	        else if(recv_size==0)
    65	        {
    66	            cout<<"close peer connect"<<endl;
    67	            close(newsockfd);
    68	            continue;
    69	        }
    70	
    71	        cout<<"buf:"<<buf<<endl;
    72	
    73	        memset(buf,'\0',sizeof(buf));
    74	        strcpy(buf,"i am serve!!!");
    75	        send(newsockfd,buf,strlen(buf),0);
    76	
    77	
    78	
    79	        sleep(1);
    80	    }
    81	
    82	    return 0;
    83	}

在這里插入圖片描述

  • 由于客戶端不需要固定的埠號,因此不必呼叫bind(),客戶端的埠號由內核自動分配,
  • 客戶端不是不允許呼叫bind(), 只是沒有必要呼叫bind()固定一個埠號. 否則如果在同一臺機器上啟動多個客戶端, 就會出現埠號被占用導致不能正確建立連接,
  • 服務器也不是必須呼叫bind(), 但如果服務器不呼叫bind(), 內核會自動給服務器分配監聽埠, 每次啟動服務器時埠號都不一樣, 客戶端要連接服務器就會遇到麻煩,

5.單行程的TCP的發送和接收資料的問題

再啟動一個客戶端, 嘗試連接服務器, 發現第二個客戶端, 不能正確的和服務器進行通信,
分析原因, 是因為我們accecpt了一個請求之后, 就在一直while回圈嘗試read, 沒有繼續呼叫到accecpt, 導致不能接受新的請求

在這里插入圖片描述
在這里插入圖片描述
通過pstack查看服務端阻塞在recv處,因為服務端accept接收新連接在while回圈外面,所以服務端在進行一次連接之后會進入while回圈內部,不能再接收新連接(雖然客戶端2和服務端完成了三次握手建立了新連接,但服務端無法接收連接,此時客戶端則無法收到服務端的資料)

TCP單行程存在的問題:當存在多個客戶端與服務器進行通信時,可能會出現recv阻塞或accept阻塞,

若將accept放入while回圈里呢?

將accpect放入while回圈中,則每個客戶端只能收到一條,當客戶端與服務端建立連接,向服務端發送資料服務端,服務端接收資料并回復客戶端,此時服務端將回到while回圈的開始阻塞在accept處(因為之前已經接收客戶端發起的連接,當第二次accept時,已完成連接佇列中就是空佇列),

  • 解決辦法:
  • 多執行緒,
  • 多行程,

6.多執行緒的TCP的發送和接收資料

多行程的客戶端代碼和單行程是一樣的,單行程服務端父行程負責accept,子行程負責資料的接收和發送,需要注意的是,父行程一定要行程等待,防止子行程先于父行程退出使子行程變為僵尸行程,而父行程不能直接父行程的邏輯處使用wait或waitpid進行等待,因為阻塞等待,若子行程一直不退出,則父行程一直在等待,永遠無法接收新連接,我們 需要使用需要使用自定義信號處理方式將SIGCHLD信號重新定義,當子行程退出發出SIGCHLD信號時,父行程則對子行程的資源進行回收,

1.客戶端代碼

創建套接字,發起連接,發送和接收

     1	#include<unistd.h>
     2	#include<string.h>
     3	#include<iostream>
     4	#include<stdlib.h>
     5	#include<sys/types.h>
     6	#include<sys/socket.h>
     7	#include<arpa/inet.h>
     8	#include<error.h>
     9	#include<stdio.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    15	
    16	    if(sockfd<0)
    17	    {
    18	        perror("socket");
    19	        return 0;
    20	    }
    21	    cout<<sockfd<<endl;
    22	
    23	    struct sockaddr_in addr;
    24	
    25	    addr.sin_family=AF_INET;
    26	    addr.sin_port=htons(20000);
    27	    addr.sin_addr.s_addr=inet_addr("1.14.165.138");
    28	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    29	    int ret=connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    30	
    31	    if(ret<0)
    32	    {
    33	        perror("connect");
    34	        return 0;
    35	    }
    36	
    37	    if(ret<0)
    38	    {
    39	        perror("connect");
    40	        return 0;
    41	    }
    42	
    43	    
    44	    while(1)
    45	    {
    46	        char buf[1024]="i am client!";
    47	
    48	        send(sockfd,buf,strlen(buf),0);
    49	        memset(buf,'\0',sizeof(buf));
    50	        //接收
    51	        ssize_t recv_size=recv(sockfd,buf,sizeof(buf)-1,0);
    52	        if(recv_size<0)
    53	        {
    54	            perror("recv");
    55	            return 0;
    56	        }
    57	        else if(recv_size==0)
    58	        {
    59	            cout<<"close peer connect"<<endl;
    60	            close(sockfd);
    61	            continue;
    62	        }
    63	
    64	        cout<<"buf:"<<buf<<endl;
    65	
    66	
    67	
    68	
    69	        sleep(1);
    70	    }
    71	
    72	    return 0;
    73	}

2.服務端代碼

服務端主要流程:創建偵聽套接字,系結地址資訊,監聽,接收新連接,創建子行程,接收,發送

     1	#include<unistd.h>
     2	#include<string.h>
     3	#include<iostream>
     4	#include<stdlib.h>
     5	#include<sys/types.h>
     6	#include<sys/socket.h>
     7	#include<arpa/inet.h>
     8	#include<error.h>
     9	#include<stdio.h>
    10	#include<pthread.h>
    11	using namespace std;
    12	
    13	struct ThreadInfo
    14	{
    15	    int _newSockFd;
    16	};
    17	
    18	void* TcpThreadStart(void* arg)
    19	{
    20	    pthread_detach(pthread_self());
    21	    struct ThreadInfo* ti=(struct ThreadInfo*)arg;
    22	    int newsockfd=ti->_newSockFd;
    23	
    24	    while(1)
    25	    {
    26	        char buf[1024]={0};
    27	
    28	        ssize_t recv_size=recv(newsockfd,buf,sizeof(buf)-1,0);
    29	        if(recv_size<0)
    30	        {
    31	            perror("recv");
    32	            return 0;
    33	        }
    34	        else if(recv_size==0)
    35	        {
    36	            cout<<"close peer connect"<<endl;
    37	            close(newsockfd);
    38	            continue;
    39	        }
    40	
    41	        cout<<"buf:"<<buf<<endl;
    42	
    43	        memset(buf,'\0',sizeof(buf));
    44	        strcpy(buf,"i am serve!!!");
    45	        send(newsockfd,buf,strlen(buf),0);
    46	
    47	    }
    48	    delete ti;
    49	    return NULL;
    50	}
    51	int main()
    52	{
    53	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    54	
    55	    if(listen_sock<0)
    56	    {
    57	        perror("socket");
    58	        return 0;
    59	    }
    60	    cout<<listen_sock<<endl;
    61	
    62	    struct sockaddr_in addr;
    63	
    64	    addr.sin_family=AF_INET;
    65	    addr.sin_port=htons(20000);
    66	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    67	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    68	    int ret=bind(listen_sock,(struct sockaddr*)&addr,sizeof(addr));
    69	
    70	    if(ret<0)
    71	    {
    72	        perror("bind");
    73	        return 0;
    74	    }
    75	
    76	    ret=listen(listen_sock,1);
    77	    if(ret<0)
    78	    {
    79	        perror("listen");
    80	        return 0;
    81	    }
    82	
    83	    while(1)
    84	    {
    85	        struct sockaddr_in cli_addr;
    86	        socklen_t cli_addrlen=sizeof(cli_addr);
    87	        int newsockfd=accept(listen_sock,(struct sockaddr*)&cli_addr,&cli_addrlen);
    88	        if(newsockfd<0)
    89	        {
    90	            perror("accept");
    91	            return 0;
    92	        }
    93	
    94	        cout<<"i accept new connect form client:"<<inet_ntoa(cli_addr.sin_addr)<<" :"<<ntohs(cli_addr.sin_port)<<endl;
    95	
    96	        struct ThreadInfo* ti =new ThreadInfo;
    97	        ti->_newSockFd=newsockfd;
    98	
    99	
   100	        pthread_t tid;
   101	        ret=pthread_create(&tid,NULL,TcpThreadStart,(void*)ti);
   102	        if(ret<0)
   103	        {
   104	            perror("pthread_create");
   105	            close(newsockfd);
   106	            delete ti;
   107	            continue;
   108	        }
   109	
   110	
   111	
   112	
   113	        sleep(1);
   114	    }
   115	
   116	    return 0;
   117	}

在這里插入圖片描述

7. 多行程的TCP的發送和接收資料

1.客戶端代碼

   1	#include<unistd.h>
     2	#include<string.h>
     3	#include<iostream>
     4	#include<stdlib.h>
     5	#include<sys/types.h>
     6	#include<sys/socket.h>
     7	#include<arpa/inet.h>
     8	#include<error.h>
     9	#include<stdio.h>
    10	using namespace std;
    11	
    12	int main()
    13	{
    14	    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    15	
    16	    if(sockfd<0)
    17	    {
    18	        perror("socket");
    19	        return 0;
    20	    }
    21	    cout<<sockfd<<endl;
    22	
    23	    struct sockaddr_in addr;
    24	
    25	    addr.sin_family=AF_INET;
    26	    addr.sin_port=htons(20000);
    27	    addr.sin_addr.s_addr=inet_addr("1.14.165.138");
    28	    // addr.sin_addr.s_addr=inet_addr("0.0.0.0");這個地址包含所有本地網卡地址
    29	    int ret=connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    30	
    31	    if(ret<0)
    32	    {
    33	        perror("connect");
    34	        return 0;
    35	    }
    36	
    37	    if(ret<0)
    38	    {
    39	        perror("connect");
    40	        return 0;
    41	    }
    42	
    43	    
    44	    while(1)
    45	    {
    46	        char buf[1024]="i am client!";
    47	
    48	        send(sockfd,buf,strlen(buf),0);
    49	        memset(buf,'\0',sizeof(buf));
    50	        //接收
    51	        ssize_t recv_size=recv(sockfd,buf,sizeof(buf)-1,0);
    52	        if(recv_size<0)
    53	        {
    54	            perror("recv");
    55	            return 0;
    56	        }
    57	        else if(recv_size==0)
    58	        {
    59	            cout<<"close peer connect"<<endl;
    60	            close(sockfd);
    61	            continue;
    62	        }
    63	
    64	        cout<<"buf:"<<buf<<endl;
    65	
    66	
    67	
    68	
    69	        sleep(1);
    70	    }
    71	
    72	    return 0;
    73	}

2.服務端代碼

     1	#include<iostream>
     2	#include<errno.h>
     3	#include<stdlib.h>
     4	#include<sys/socket.h>
     5	#include<sys/types.h>
     6	#include<unistd.h>
     7	#include<string.h>
     8	#include<arpa/inet.h>
     9	#include<netinet/in.h>
    10	#include<sys/wait.h>
    11	#include<stdio.h>
    12	using namespace std;
    13	
    14	void signalcallback(int signo)
    15	{
    16	    //當前的wait是行程等待的阻塞介面, 但是應用的場景一定是子行程退出之后,
    17	    //父行程收到了SIGCHLD信號之后, 才會回呼sigcallback函式, 才會呼叫wait
    18	    cout<<"recv signo:"<<signo<<endl;
    19	    wait(NULL);
    20	}
    21	int main()
    22	{
    23	    signal(SIGCHLD,signalcallback);
    24	    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    25	    if(listen_sock<0)
    26	    {
    27	        perror("socket");
    28	        return 0;
    29	    }
    30	
    31	    struct sockaddr_in addr;
    32	
    33	    addr.sin_family=AF_INET;
    34	    addr.sin_port=htons(20000);
    35	    addr.sin_addr.s_addr=inet_addr("172.16.0.9");
    36	
    37	    int ret=bind(listen_sock,(struct sockaddr*)&addr,sizeof(addr));
    38	    if(ret<0)
    39	    {
    40	        perror("bind");
    41	        return 0;
    42	    }
    43	
    44	
    45	    ret=listen(listen_sock,5);
    46	    if(ret<0)
    47	    {
    48	        perror("listen");
    49	        return 0;
    50	    }
    51	
    52	    while(1)
    53	    {
    54	        int new_sock=accept(listen_sock,NULL,NULL); //客服端的地址和埠號,這么傳NULL,不關心客服端的埠號
    55	        if(new_sock<0)
    56	        {
    57	            continue;
    58	        }
    59	
    60	        //創建子行程
    61	        int pid=fork();
    62	        if(pid<0)
    63	        {
    64	            //創建子行程失敗,但是接收新鏈接成功
    65	            close(new_sock);
    66	            continue;
    67	        }
    68	        else if(pid==0)
    69	        {
    70	            //child
    71	            close(listen_sock);
    72	
    73	            while(1)
    74	            {
    75	                //recv  and send
    76	
    77	                char buf[1024]={0};
    78	
    79	                ssize_t recv_size=recv(new_sock,buf,sizeof(buf)-1,0);
    80	                if(recv_size<0)
    81	                {
    82	                    perror("recv");
    83	                    continue;
    84	                }
    85	                else if(recv_size==0)
    86	                {
    87	                    cout<<"peer shutdown!"<<endl;
    88	                    close(new_sock);
    89	
    90	                    //子行程故障
    91	                    //exit(1);
    92	
    93	                }
    94	
    95	                cout<<"client say:"<<buf<<endl;
    96	
    97	                memset(buf,'\0',sizeof(buf));
    98	                strcpy(buf,"i am serve");
    99	                send(new_sock,buf,strlen(buf),0);
   100	            }
   101	        }
   102	        else 
   103	        {
   104	            close(new_sock);
   105	        }
   106	    }
   107	    return 0;
   108	}

在這里插入圖片描述

七、TCP協議通訊流程

  • 服務器初始化:
  • 呼叫socket, 創建檔案描述符,
  • 呼叫bind, 將當前的檔案描述符和ip/port系結在一起; 如果這個埠已經被其他行程占用了, 就會bind失敗,
  • 呼叫listen, 宣告當前這個檔案描述符作為一個服務器的檔案描述符, 為后面的accept做好準備,
  • 呼叫accecpt, 并阻塞, 等待客戶端連接過來符,
  • 建立連接的程序:
  • 呼叫socket, 創建檔案描述符,
  • 呼叫connect, 向服務器發起連接請求,
  • connect會發出SYN段并阻塞等待服務器應答; (第一次),
  • 服務器收到客戶端的SYN, 會應答一個SYN-ACK段表示"同意建立連接"; (第二次),
  • 客戶端收到SYN-ACK后會從connect()回傳, 同時應答一個ACK段; (第三次),

這個建立連接的程序, 通常稱為 三次握手,

  • 資料傳輸的程序:
  • 建立連接后,TCP協議提供全雙工的通信服務; 所謂全雙工的意思是, 在同一條連接中, 同一時刻, 通信雙方可以同時寫資料; 相對的概念叫做半雙工, 同一條連接在同一時刻, 只能由一方來寫資料,
  • 服務器從accept()回傳后立刻調 用read(), 讀socket就像讀管道一樣, 如果沒有資料到達就阻塞等待,
  • 這時客戶端呼叫write()發送請求給服務器, 服務器收到后從read()回傳,對客戶端的請求進行處理, 在此期間客戶端呼叫read()阻塞等待服務器的應答,
  • 服務器呼叫write()將處理結果發回給客戶端, 再次呼叫read()阻塞等待下一條請求,
  • 客戶端收到后從read()回傳, 發送下一條請求,如此回圈下去,
  • 斷開連接的程序:
  • 如果客戶端沒有更多的請求了, 就呼叫close()關閉連接, 客戶端會向服務器發送FIN段(第一次),
  • 此時服務器收到FIN后, 會回應一個ACK, 同時read會回傳0 (第二次),
  • read回傳之后, 服務器就知道客戶端關閉了連接, 也呼叫close關閉連接, 這個時候服務器會向客戶端發送一個FIN; (第三次),
  • 客戶端收到FIN, 再回傳一個ACK給服務器; (第四次),

這這個斷開連接的程序, 通常稱為 四次揮手,

  • 在學習socket API時要注意應用程式和TCP協議層是如何互動的:
  • 應用程式呼叫某個socket函式時TCP協議層完成什么動作,比如呼叫connect()會發出SYN段,
  • 用程式如何知道TCP協議層的狀態變化,比如從某個阻塞的socket函式回傳就表明TCP協議收到了某些段,再比如read()回傳0就表明收到了FIN段,

💬總結

以上就是今天要講的內容,本文詳細介紹了網路編程中UDP、和Tcp的編程等知識的使用,網路提供了大量的方法供我們使用,非常的便捷,我們務必掌握,希望大家多多支持!另外如果上述有任何問題,請懂哥指教,不過沒關系,主要是自己能堅持,更希望有一起學習的同學可以幫我指正,但是如果可以請溫柔一點跟我講,愛與和平是永遠的主題,愛各位了,加油啊!

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292036.html

標籤:其他

上一篇:一看就懂的IP協議!!!

下一篇:在 Linux 系統中配置 yum 源倉庫

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more