最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Linux之TCP網(wǎng)絡(luò)套接字詳解

 更新時間:2025年08月11日 10:02:29   作者:每天敲200行代碼  
TCP socket API包含創(chuàng)建、綁定、監(jiān)聽、接受連接、連接等步驟,需三次握手建立連接,四次揮手?jǐn)嚅_,與UDP相比,TCP是面向連接的可靠傳輸協(xié)議,基于字節(jié)流進行數(shù)據(jù)交換,確保雙向通信可靠性

一、TCP socket API 詳解

1、插件套接字 socket

在通信之前要先把網(wǎng)卡文件打開。

  • socket() 打開一個網(wǎng)絡(luò)通訊端口,如果成功的話,就像 open() 一樣返回一個文件描述符。
  • 應(yīng)用程序可以像讀寫文件一樣用 read/write 在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù)。
  • 如果 socket()調(diào)用出錯則返回 -1。
  • 對于 IPv4,family參數(shù)指定為 AF_INET。
  • 對于 TCP 協(xié)議,type 參數(shù)指定為 SOCK_STREAM,表示面向流的傳輸協(xié)議。
  • protocol 參數(shù)的介紹忽略,指定為 0 即可。

成功則返回打開的文件描述符(指向網(wǎng)卡文件),失敗返回-1。 

這個函數(shù)的作用是打開一個文件,把文件和網(wǎng)卡關(guān)聯(lián)起來。 

  • domain:一個域,標(biāo)識了這個套接字的通信類型(網(wǎng)絡(luò)或者本地)。

(只需要關(guān)注上面兩個類,第一個 AF_UNIX 表示本地通信,而 AF_INET 表示網(wǎng)絡(luò)通信。

  • type:套接字提供服務(wù)的類型。

  • protocol:想使用的協(xié)議,默認(rèn)為 0 即可,因為前面的兩個參數(shù)決定了,就已經(jīng)決定了是 TCP 還是 UDP 協(xié)議了。

從這里我們就可以聯(lián)想到系統(tǒng)中的文件操作,未來各種操作都要通過這個文件描述符,所以在服務(wù)端類中還需要一個成員變量表示文件描述符。

2、綁定 bind

  • 服務(wù)器程序所監(jiān)聽的網(wǎng)絡(luò)地址和端口號通常是固定不變的,客戶端程序得知服務(wù)器程序的地址和端口號后就可以向服務(wù)器發(fā)起連接,服務(wù)器需要調(diào)用 bind 綁定一個固定的網(wǎng)絡(luò)地址和端口號。
  • bind() 的作用是將參數(shù) sockfd 和 myaddr 綁定在一起,使 sockfd 這個用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽 myaddr 所描述的地址和端口號。
  • 前面講過,struct sockaddr* 是一個通用指針類型,myaddr 參數(shù)實際上可以接受多種協(xié)議的 sockaddr 結(jié)構(gòu)體,而它們的長度各不相同,所以需要第三個參數(shù) addrlen 指定結(jié)構(gòu)體的長度。

 

bind() 成功返回 0,失敗返回 -1。

  • socket:創(chuàng)建套接字的返回值。
  • address:通用結(jié)構(gòu)體(前面有詳細(xì)介紹)。
  • address_len:傳入結(jié)構(gòu)體的長度。

所以我們需要先定義一個address_in 結(jié)構(gòu)體填充數(shù)據(jù),再傳遞進去。

3、設(shè)置監(jiān)聽狀態(tài) listen

因為 TCP 是面向連接的,當(dāng)我們正式通信的時候,需要先建立連接,那么 TCP 跟 UDP 的不同在這里就體現(xiàn)了出來。要把 socket 套接字的狀態(tài)設(shè)置為 listen 狀態(tài),只有這樣才能一直獲取新鏈接,接收新的鏈接請求。

舉例幫助理解:我們買東西如果出現(xiàn)了問題會去找客服,如果客服不在,那么就無法回復(fù)我們,所以就規(guī)定了客服在工作的時候必須要時刻接收回復(fù)消息,那么這個客服所處的狀態(tài)就叫做監(jiān)聽狀態(tài)。

 

關(guān)于第二個參數(shù):backlog,后邊講 TCP 協(xié)議參數(shù)時會再詳細(xì)介紹,目前先直接用。( 一般不能太大,也不能太小)

listen() 成功返回 0,失敗返回 -1。 

  • listen() 聲明 sockfd 處于監(jiān)聽狀態(tài),并且最多允許有 backlog 個客戶端處于連接等待狀態(tài),如果接收到更多的連接請求就忽略,這里設(shè)置不會太大(一般是 5)。

創(chuàng)建套接字成功,套接字對應(yīng)的文件描述符值是 3,為什么是 3 呢?

因為當(dāng)前對應(yīng)的文件描述符返回的套接字本身就是一個文件描述符,0、1、2 被占用,再創(chuàng)建一個文件,對應(yīng)的就是 3。

4、獲取新鏈接 accept

前面初始化完成,現(xiàn)在就是要開始運行服務(wù)端。TCP 不能直接發(fā)送數(shù)據(jù),因為它是面向鏈接的,所以必須要先建立鏈接。

成功返回一個文件描述符,失敗返回 -1。

  • sockfd:文件描述符,找到套接字。
  • addr:輸入輸出型參數(shù),是一個結(jié)構(gòu)體,用來獲取客戶端的信息。
  • addrlen:輸入輸出型參數(shù),客戶端傳過來的結(jié)構(gòu)體大小。 

三次握手完成后,服務(wù)器調(diào)用 accept() 接受連接。

如果服務(wù)器調(diào)用 accept() 時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。

addr 是一個傳出參數(shù),accept() 返回時傳出客戶端的地址和端口號。

如果給 addr 參數(shù)傳 NULL,表示不關(guān)心客戶端的地址。

addrlen 參數(shù)是一個傳入傳出參數(shù) (value-result argument),傳入的是調(diào)用者提供的,緩沖區(qū) addr 的長度以避免緩沖區(qū)溢出問題,傳出的是客戶端地址結(jié)構(gòu)體的實際長度(有可能沒有占滿調(diào)用者提供的緩沖區(qū))。

我們的服務(wù)器程序結(jié)構(gòu)是這樣的:

sockfd 本來就是一個文件描述符,那么這個返回的文件描述符是什么呢?

  • 舉例幫助理解:我們?nèi)コ燥垥r,會發(fā)現(xiàn)一些店鋪的門口有工作人員來招攬顧客,他將我們領(lǐng)進門店之后,他會站在門口繼續(xù)招攬顧客,而我們會由里面的服務(wù)員來招待我們,給我們提供服務(wù)。
  • 這里攬客的工作人員指的就是 sockfd,而店里面的服務(wù)員就是返回值的文件描述符。也就是說,sockfd 的作用就是把鏈接從底層獲取上來,而返回值的作用就是跟客戶端通信。

那么我們就知道了,成員變量中的 _sock 并不是通信用的套接字,而是獲取鏈接的套接字。為了方便觀察,我們可以把前面所有的 _sock 換成 _listensock。

客服端的整體代碼如下:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cerrno>
#include <string>
#include "log.hpp"
#include <unistd.h>

Log lg;
class TcpServer
{
public:
    // 下面這個構(gòu)造函數(shù)時全缺省的,就不需要這個默認(rèn)構(gòu)造了
    // TcpServer()
    // {
    // }
    // 第一個參數(shù)時listensock,但是這個時在init的時候socket接口創(chuàng)建的套接字,你如果傳參的話,第一個傳遞的應(yīng)該時監(jiān)聽套接字,而不是一個端口號,
    // 你在調(diào)用的時候,傳遞的時一個端口號,所以這里時有問題的,
    // TcpServer(int sock = 0, uint16_t port = 8080, std::string ip = "0.0.0.0")
    //     : listensock_(sock), port_(port), ip_(ip)
    // {
    // }
    TcpServer(uint16_t port = 8080, std::string ip = "0.0.0.0")
        : listensock_(-1), port_(port), ip_(ip)
    {
    }
    void Init()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        lg(Info, "Create socket success,sockfd:%d", listensock_);
        struct sockaddr_in local;
        bzero(&local, 0);
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        local.sin_addr.s_addr = inet_addr(ip_.c_str());
        socklen_t len = sizeof(local);
        if (bind(listensock_, (const sockaddr *)&local, len) < 0)
        {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(3);
        }
        lg(Info, "bind success!");
        if (listen(listensock_, 5) < 0)
        {
            lg(Fatal, "listen error, errno: %d, err string: %s", errno, strerror(errno));
            exit(4);
        }
    }
    void start()
    {
        lg(Info, "tcpServer is running....");
        // 1. 獲取新連接
        char buffer[1024];
        struct sockaddr_in client;
        memset(&client, 0, sizeof(client));
        socklen_t len = sizeof(client);

        while (true)
        {
            // 這里是由問題的,因為你獲取一個連接之后,應(yīng)該是多次進行通信,而不是通信一次之后,在獲取連接,因為上一個連接還沒有處理完呢
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            string clientip = inet_ntoa(client.sin_addr);
            // 連接成功后,為客戶端提供服務(wù)
            while (1)
            {
                // 連接成功之后,多次處理這個連接的請求
                ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
                if (size > 0)
                {
                    buffer[size] = 0;
                    cout << '[' << clientip << ':' << clientport << "]# " << buffer << endl;
                    write(sockfd, buffer, size);
                }
                else if (size == 0)
                {
                    // 如果讀取到0的時候,就是對方斷開了,
                    // 需要做的事情就是關(guān)閉套接字,然后跳出循環(huán),獲取下一個連接
                    close(sockfd);
                    break;
                }
                else
                {
                    // 如果是小于0的話,那么就說明read這個錯誤失敗了,也需要關(guān)閉連接
                    close(sockfd);
                    break;
                }
            }
        }
        // while (true)
        // {
        //     // 這里是由問題的,因為你獲取一個連接之后,應(yīng)該是多次進行通信,而不是通信一次之后,在獲取連接,因為上一個連接還沒有處理完呢
        //     int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
        //     if (sockfd < 0)
        //     {
        //         lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
        //         continue;
        //     }
        //     uint16_t clientport = ntohs(client.sin_port);
        //     string clientip = inet_ntoa(client.sin_addr);
        //     // 連接成功后,為客戶端提供服務(wù)
        //     ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
        //     if (size > 0)
        //     {
        //         buffer[size] = 0;
        //         cout << '[' << clientip << ':' << clientport << "]# " << buffer << endl;
        //         write(sockfd, buffer, size);
        //         std::cout << 1 <<std::endl;
        //     }
        // }
    }

public:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

 

5、發(fā)起鏈接 connect

  • 客戶端需要調(diào)用 connect() 連接服務(wù)器。
  • connect 和 bind 的參數(shù)形式一致,區(qū)別在于 bind 的參數(shù)是自己的地址,而 connect 的參數(shù)是對方的地址。

 

connect() 成功返回 0,出錯返回 -1。

這里的 addr 和 addrlen 填入的是服務(wù)端信息。 

在 UDP 通信中,客戶端在 sendto 時會自動綁定 IP 和 port,而 TCP 就是在 connect 的時候進行綁定。因為 connect 是系統(tǒng)調(diào)用接口,所以在調(diào)用 connect 時會自動的給綁定當(dāng)前客戶端的 ip 和 port,進而可以讓我們在后續(xù)使用 sockfd 進行通信。

客戶端.cpp

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
using std::cout;
using std::endl;

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }
    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect error..." << std::endl;
        exit(2);
    }
    cout << "connect sunccess!" << endl;
    while (true)
    {
        while (true)
        {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

            int n = write(sockfd, message.c_str(), message.size());
            if (n < 0)
            {
                std::cerr << "write error..." << std::endl;
                // break;
            }

            char inbuffer[4096];
            n = read(sockfd, inbuffer, sizeof(inbuffer));
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << inbuffer << std::endl;
            }
            else
            {
                // break;
            }
        }

        close(sockfd);
    }

    return 0;
}

運行結(jié)果如下:

二、TCP 協(xié)議通訊流程

下圖是基于 TCP 協(xié)議的客戶端/服務(wù)器程序的一般流程:

1、服務(wù)器初始化

  • 調(diào)用 socket,創(chuàng)建文件描述符。
  • 調(diào)用 bind,將當(dāng)前的文件描述符和 ip/port 綁定在一起,如果這個端口已經(jīng)被其他進程占用了,就會 bind 失敗。
  • 調(diào)用 listen,聲明當(dāng)前這個文件描述符作為一個服務(wù)器的文件描述符,為后面的 accept 做好準(zhǔn)備。
  • 調(diào)用 accecpt,并阻塞,等待客戶端連接過來。

2、建立連接的過程

  • 調(diào)用 socket,創(chuàng)建文件描述符。
  • 調(diào)用 connect,向服務(wù)器發(fā)起連接請求。
  • connect 會發(fā)出 SYN 段并阻塞等待服務(wù)器應(yīng)答(第一次)。
  • 服務(wù)器收到客戶端的 SYN,會應(yīng)答一個 SYN-ACK 段表示 “同意建立連接”(第二次)。
  • 客戶端收到 SYN-ACK 后會從 connect() 返回,同時應(yīng)答一個 ACK 段(第三次)。

TCP 是面向連接的通信協(xié)議,在通信之前需要進行 3 次握手,來進行連接的建立。這個建立連接的過程通常稱為 三次握手 

3、數(shù)據(jù)傳輸?shù)倪^程

  • 建立連接后,TCP 協(xié)議提供全雙工的通信服務(wù)。所謂全雙工的意思是,在同一條連接中, 同一時刻,通信雙方可以同時寫數(shù)據(jù)。相對的概念叫做半雙工,同一條連接在同一時刻,只能由一方來寫數(shù)據(jù)。
  • 服務(wù)器從 accept() 返回后立刻調(diào)用 read(),讀 socket 就像讀管道一樣,如果沒有數(shù)據(jù)到達就阻塞等待。
  • 這時客戶端調(diào)用 write() 發(fā)送請求給服務(wù)器, 服務(wù)器收到后從 read() 返回,對客戶端的請求進行處理,在此期間客戶端調(diào)用 read()阻塞等待服務(wù)器的應(yīng)答;
  • 服務(wù)器調(diào)用 write() 將處理結(jié)果發(fā)回給客戶端,再次調(diào)用 read() 阻塞等待下一條請求。
  • 客戶端收到后從 read() 返回,發(fā)送下一條請求,如此循環(huán)下去。

4、斷開連接的過程

  • 如果客戶端沒有更多的請求了,就調(diào)用 close() 關(guān)閉連接,客戶端會向服務(wù)器發(fā)送 FIN 段(第一次)。
  • 此時服務(wù)器收到 FIN 后,會回應(yīng)一個 ACK,同時 read 會返回 0(第二次)。
  • read 返回之后,服務(wù)器就知道客戶端關(guān)閉了連接, 也調(diào)用 close 關(guān)閉連接,這個時候服務(wù)器會向客戶端發(fā)送一個 FIN(第三次)。
  • 客戶端收到 FIN,再返回一個 ACK 給服務(wù)器(第四次)。

當(dāng) TCP 斷開連接這個斷開連接的過程 , 通常稱為 四次揮手 。

為什么是四次揮手呢?

  • 因為 TCP 是基于確定應(yīng)答來保證單項可靠性的,如果對方給我發(fā)消息,我也給對方進行應(yīng)答,那么就能夠保證雙向的可靠性。所以,發(fā)出去的斷開連接的過程需要應(yīng)答。
  • 當(dāng)客戶端斷開連接時,要保證客戶端到服務(wù)的連接被成功關(guān)閉,所以需要調(diào)用一次,而服務(wù)端除了要釋放自身創(chuàng)建好的文件描述符,也要關(guān)閉從服務(wù)端到客戶端對應(yīng)的連接,因為雙方都要調(diào)用 close() 各自兩次,那么一來一來就緒各自需要兩次揮手,加起來就是四次揮手。

三、總結(jié)

對比 UDP 服務(wù)器,TCP 服務(wù)器多了獲取新鏈接和監(jiān)聽的操作,而因為 TCP 是面向字節(jié)流的,所以接收和發(fā)送數(shù)據(jù)都是 IO 操作,也就是文件操作。

TCP 和 UDP 對比

  • 可靠傳輸 VS 不可靠傳輸
  • 有連接 VS 無連接
  • 字節(jié)流 VS 數(shù)據(jù)報

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Linux netstat命令查看并發(fā)連接數(shù)的方法

    Linux netstat命令查看并發(fā)連接數(shù)的方法

    當(dāng)我們的網(wǎng)站部署在linux的服務(wù)器上,特別是web服務(wù)器,我們可能有時候做為運維人員,肯定是要查看網(wǎng)站的并發(fā)連接數(shù)是不是達到瓶頸等,所以在linux下,我們?nèi)绾尾榭捶?wù)器的并發(fā)連接數(shù)呢?下面這篇文章就給大家詳細(xì)的介紹了Linux netstat命令查看并發(fā)連接數(shù)的方法。
    2016-11-11
  • CentOS7 安裝 zabbix 4.0 教程(圖文詳解)

    CentOS7 安裝 zabbix 4.0 教程(圖文詳解)

    這篇文章主要介紹了CentOS7 安裝 zabbix 4.0 教程,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-10-10
  • CentOS?6?Linux系統(tǒng)添加永久靜態(tài)路由的方法

    CentOS?6?Linux系統(tǒng)添加永久靜態(tài)路由的方法

    在Linux系統(tǒng)中,特別是對于服務(wù)器管理而言,正確配置網(wǎng)絡(luò)路由是確保網(wǎng)絡(luò)通信順暢的重要步驟,本文將介紹如何在CentOS?6系統(tǒng)中添加永久靜態(tài)路由,通過本文的學(xué)習(xí),你將能夠掌握如何在不影響現(xiàn)有網(wǎng)絡(luò)配置的情況下,為你的系統(tǒng)添加一條或多條靜態(tài)路由,需要的朋友可以參考下
    2025-03-03
  • Linux join命令快速實現(xiàn)從大文件中匹配內(nèi)容

    Linux join命令快速實現(xiàn)從大文件中匹配內(nèi)容

    本文介紹了如何使用Linux命令和join命令進行大數(shù)據(jù)文件的高效匹配,通過對文件進行排序和使用join命令,可以顯著提升匹配效率,同時,還討論了數(shù)據(jù)準(zhǔn)備、文件排序、join命令的應(yīng)用以及數(shù)據(jù)整理的方法
    2025-11-11
  • CentOS下使用Squid架設(shè)CDN服務(wù)器的方法

    CentOS下使用Squid架設(shè)CDN服務(wù)器的方法

    這篇文章主要介紹了CentOS下使用Squid架設(shè)CDN服務(wù)器的方法,需要的朋友可以參考下
    2014-07-07
  • Ubuntu系統(tǒng)更換Docker鏡像源的完整指南(2025最新)

    Ubuntu系統(tǒng)更換Docker鏡像源的完整指南(2025最新)

    在國內(nèi)使用 Docker 拉取鏡像時,經(jīng)常會遇到連接超時、connection reset by peer 等問題,這是由于默認(rèn)使用的官方鏡像倉庫網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致的,所以本篇文章將教你如何為 Ubuntu 系統(tǒng)下的 Docker 更換國內(nèi)可用的鏡像加速源,需要的朋友可以參考下
    2025-09-09
  • 詳解CentOS 7下安裝Tomcat到服務(wù)

    詳解CentOS 7下安裝Tomcat到服務(wù)

    本篇文章主要介紹了CentOS 7下安裝Tomcat到服務(wù),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Linux使用watch命令監(jiān)控Docker容器狀態(tài)的操作方法

    Linux使用watch命令監(jiān)控Docker容器狀態(tài)的操作方法

    在現(xiàn)代的開發(fā)和運維環(huán)境中,容器化技術(shù)已經(jīng)成為一種重要的趨勢,而Docker作為最流行的容器化平臺之一,Linux中的watch命令就是一個非常有用的工具,它可以幫助我們定期執(zhí)行指定的命令,并全屏顯示輸出,本文給大家介紹了在Linux中使用watch命令監(jiān)控Docker容器狀態(tài)
    2024-10-10
  • linux給用戶添加root權(quán)限方法總結(jié)

    linux給用戶添加root權(quán)限方法總結(jié)

    在本篇文章里小編給大家整理的是關(guān)于
    2020-02-02
  • Linux創(chuàng)建文件的五種方法

    Linux創(chuàng)建文件的五種方法

    本文介紹五種在Linux系統(tǒng)中創(chuàng)建文件的方法,包括使用touch命令、使用文本編輯器創(chuàng)建文件、使用echo命令創(chuàng)建文件、使用cat命令創(chuàng)建文件以及使用重定向符號創(chuàng)建文件,全面了解并掌握在Linux系統(tǒng)中創(chuàng)建文件的各種常用方法,需要的朋友可以參考下
    2025-03-03

最新評論

浦城县| 雷山县| 林芝县| 潢川县| 鄂尔多斯市| 高雄市| 桃园市| 江城| 安乡县| 宁安市| 微山县| 新丰县| 施秉县| 广州市| 墨竹工卡县| 怀化市| 徐闻县| 宿松县| 玉屏| 镇安县| 灵川县| 丰城市| 连江县| 军事| 肇东市| 修武县| 卢湾区| 邮箱| 安吉县| 凤山县| 汽车| 宁安市| 天镇县| 东至县| 涟水县| 日土县| 三河市| 湄潭县| 肇东市| 渭源县| 珲春市|