国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解]

這篇具有很好參考價(jià)值的文章主要介紹了「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解]。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

「前言」文章內(nèi)容大致是傳輸層協(xié)議,TCP協(xié)議講解的第二篇,續(xù)上篇TCP。

「歸屬專欄」網(wǎng)絡(luò)編程

「主頁(yè)鏈接」個(gè)人主頁(yè)

「筆者」楓葉先生(fy)

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

二、TCP協(xié)議

2.9 TCP連接管理機(jī)制

首先明確,TCP是面向連接的,TCP通信之前需要先建立連接,就是因?yàn)?strong>TCP的各種可靠性保證都是基于連接的,要保證傳輸數(shù)據(jù)的可靠性的前提就是先建立好連接。

TCP連接不直接保證可靠性,但是會(huì)間接保證可靠性

TCP進(jìn)行連接會(huì)進(jìn)行三次握手,斷開連接會(huì)進(jìn)行四次揮手。

TCP協(xié)議的客戶端/服務(wù)器程序的一般流程:
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

2.9.1 三次握手

雙方在進(jìn)行TCP通信之前需要先建立連接,建立連接的這個(gè)過(guò)程我們稱之為三次握手。

三次握手的流程如下:
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  1. 第一次握手:客戶端向服務(wù)器發(fā)送一個(gè)SYN(同步)報(bào)文,請(qǐng)求與服務(wù)器建立連接。
  2. 第二次握手:服務(wù)器收到客戶端的SYN報(bào)文后,向客戶端發(fā)送一個(gè)SYN/ACK(同步/確認(rèn))報(bào)文,表示同意建立連接。
  3. 第三次握手:客戶端收到服務(wù)器的SYN/ACK報(bào)文后,向服務(wù)器發(fā)送一個(gè)ACK(確認(rèn))報(bào)文,表示連接建立成功。

通過(guò)三次握手,客戶端和服務(wù)器都確認(rèn)了對(duì)方的請(qǐng)求,并建立了可靠的連接。

注意:三次握手是連接通信的策略,即三次握手也可能會(huì)出現(xiàn)失敗的情況

為什么是三次握手??一次握手、兩次握手、四次握手行不行??

首先明確,三次握手是策略,不一定百分之百成功,也可能出現(xiàn)失敗。

比如,通信雙方在進(jìn)行三次握手時(shí),其中前兩次握手能夠保證被對(duì)方收到,因?yàn)榍皟纱挝帐侄加袑?duì)應(yīng)的應(yīng)答,但第三次握手是沒(méi)有對(duì)應(yīng)的應(yīng)答報(bào)文的,如果第三次握手時(shí)客戶端發(fā)送的ACK報(bào)文丟失了,那么連接建立就會(huì)失敗。
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
但是,我們不怕失敗丟包,因?yàn)門CP有配套的解決方案:

  • 前面兩次握手成功后,在客戶端看來(lái),連接已經(jīng)建立好了,但是在服務(wù)端看來(lái),連接還沒(méi)有建立好。
  • 如果第三層握手ACK發(fā)生丟包了,一段時(shí)間后服務(wù)端還是沒(méi)有收到ACK報(bào)文,此時(shí)服務(wù)端就會(huì),重新發(fā)起SYN/ACK(同步/確認(rèn))報(bào)文
  • 又或者客戶端認(rèn)為連接建立好之后,就直接發(fā)數(shù)據(jù)報(bào)文了,此時(shí)服務(wù)端還沒(méi)有收到ACK確認(rèn),即服務(wù)端看來(lái)連接還沒(méi)有建立好,此時(shí)服務(wù)端就會(huì)收到該數(shù)據(jù)報(bào)文,服務(wù)端說(shuō):你都沒(méi)有跟我建立連接,發(fā)什么數(shù)據(jù)報(bào)文,重連。此時(shí)服務(wù)端就會(huì)把RST標(biāo)志位設(shè)置為1,發(fā)給客戶端,讓客戶端與服務(wù)端進(jìn)行重連

注意:第一次和第二次握手不攜帶數(shù)據(jù),第三次握手可能會(huì)攜帶數(shù)據(jù)

一次握手行不行?

絕對(duì)不行的。

  • 首先明確,連接是需要被管理起來(lái)的,被OS管理起來(lái),如果管理連接??先描述,再組織。OS維護(hù)一個(gè)連接是有成本的。
  • 即如果連接過(guò)多,OS管理不過(guò)來(lái),即代表服務(wù)器要寄了

一次握手的話,只要客戶端發(fā)起連接,就可以直接建立連接了(服務(wù)端認(rèn)為連接已經(jīng)建立好了),這樣就會(huì)導(dǎo)致單主機(jī)下SYN洪水攻擊

一次握手會(huì)發(fā)生SYN洪水攻擊,就是有人搞事,通過(guò)大量偽造的SYN報(bào)文向目標(biāo)服務(wù)器發(fā)送連接請(qǐng)求,從而消耗服務(wù)器資源,當(dāng)服務(wù)器的半連接隊(duì)列被耗盡后,合法用戶的連接請(qǐng)求無(wú)法被處理,導(dǎo)致服務(wù)不可用。

還有一點(diǎn)就是無(wú)法驗(yàn)證全雙工,即無(wú)法保證全雙工通信通道是流暢的,因?yàn)門CP是全雙工的

二次握手同上

為什么三次握手可以?

因?yàn)槿挝帐质球?yàn)證全雙工通信信道流暢的最小次數(shù)

  • TCP是全雙工通信的,因此連接建立的核心要?jiǎng)?wù)實(shí)際是,驗(yàn)證雙方的通信信道是否是連通的。
  • 而三次握手恰好是驗(yàn)證雙方通信信道的最小次數(shù),通過(guò)三次握手后雙方就都能知道自己和對(duì)方是否都能夠正常發(fā)送和接收數(shù)據(jù)
  • 還有一點(diǎn)就是有效規(guī)避單主機(jī)下的SYN洪水攻擊

注意:TCP的工作是建立通信信道,服務(wù)器受到攻擊本身就不是TCP要解決的。但是如果三次握手有明顯的漏洞,讓客戶端利用了,這就是你TCP的問(wèn)題了

四次握手行不行?五次、六次…呢?

  • 三次握手已經(jīng)是最小成本驗(yàn)證了全雙工,再多余就是浪費(fèi)時(shí)間

三次握手也可以叫四次握手,原因如下:

  • 第二次握手時(shí),服務(wù)器收到客戶端的SYN報(bào)文后,向客戶端發(fā)送一個(gè)SYN/ACK(同步/確認(rèn))報(bào)文,這個(gè)SYN/ACK報(bào)文也可以分兩次發(fā)送給客戶端
  • 但是這種情況幾乎不存在。

三次握手時(shí)的狀態(tài)變化

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
服務(wù)端:

  1. [CLOSED -> LISTEN] 服務(wù)器端調(diào)用listen后進(jìn)入LISTEN狀態(tài),等待客戶端連接
  2. [LISTEN -> SYN_RCVD] 一旦監(jiān)聽到連接請(qǐng)求(同步報(bào)文段),就將該連接放入內(nèi)核等待隊(duì)列中,并向客戶端發(fā)送SYN確認(rèn)報(bào)文
  3. [SYN_RCVD -> ESTABLISHED] 服務(wù)端一旦收到客戶端的確認(rèn)報(bào)文,就進(jìn)入ESTABLISHED狀態(tài),可以進(jìn)行讀寫數(shù)據(jù)了

客戶端:

  1. [CLOSED -> SYN_SENT] 客戶端調(diào)用connect,發(fā)送同步報(bào)文段
  2. [SYN_SENT -> ESTABLISHED] connect調(diào)用成功,則進(jìn)入ESTABLISHED狀態(tài),開始讀寫數(shù)據(jù)

2.9.2 四次揮手

TCP通信結(jié)束之后就需要斷開連接,斷開連接的這個(gè)過(guò)程我們稱之為四次揮手。

四次揮手的過(guò)程如下:
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  1. 第一次揮手:客戶端向服務(wù)器發(fā)送一個(gè)FIN報(bào)文,表示客戶端不再發(fā)送數(shù)據(jù)。
  2. 第二次揮手:服務(wù)器接收到客戶端的FIN報(bào)文后,向客戶端發(fā)送一個(gè)ACK報(bào)文,表示已經(jīng)收到客戶端的斷開請(qǐng)求。
  3. 第三次揮手:服務(wù)器向客戶端發(fā)送一個(gè)FIN報(bào)文,表示服務(wù)器也不再發(fā)送數(shù)據(jù)。
  4. 第四次揮手:客戶端接收到服務(wù)器的FIN報(bào)文后,向服務(wù)器發(fā)送一個(gè)ACK報(bào)文,表示已經(jīng)收到服務(wù)器的斷開請(qǐng)求。

這樣,雙方都確認(rèn)對(duì)方已經(jīng)斷開連接,完成四次揮手后,TCP連接就徹底關(guān)閉。

為什么要四次揮手?

  • TCP是全雙工的通信協(xié)議,建立連接時(shí)需要雙方都確認(rèn)建立連接,而斷開連接時(shí)需要雙方都確認(rèn)斷開連接。因此,四次揮手的過(guò)程是必要的。
  • 即斷開連接需要征得雙方的同意
  • 每?jī)纱螕]手對(duì)應(yīng)就是關(guān)閉一個(gè)方向的通信信道,因此斷開連接時(shí)需要進(jìn)行四次揮手

四次揮手也可能變成三次揮手,原因如下:

  • 第二次揮手:服務(wù)器接收到客戶端的FIN報(bào)文后,向客戶端發(fā)送一個(gè)ACK報(bào)文,表示已經(jīng)收到客戶端的斷開請(qǐng)求。
  • 第三次揮手:服務(wù)器向客戶端發(fā)送一個(gè)FIN報(bào)文,表示服務(wù)器也不再發(fā)送數(shù)據(jù)
  • 這兩個(gè)報(bào)文可能會(huì)合在一起發(fā),即ACK+FIN
  • 因?yàn)镕IN和ACK都是不同的標(biāo)志位,不會(huì)影響雙方

四次揮手時(shí)的狀態(tài)變化

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

客戶端狀態(tài)轉(zhuǎn)化:

  1. [FIN_WAIT_1 -> FIN_WAIT_2] 客戶端收到服務(wù)器對(duì)結(jié)束報(bào)文段的確認(rèn),則進(jìn)入FIN_WAIT_2,開始等待服務(wù)器的結(jié)束報(bào)文段
  2. [FIN_WAIT_2 -> TIME_WAIT] 客戶端收到服務(wù)器發(fā)來(lái)的結(jié)束報(bào)文段,進(jìn)入TIME_WAIT, 并發(fā)出LAST_ACK
  3. [TIME_WAIT -> CLOSED] 客戶端要等待一個(gè)2MSL(Max Segment Life, 報(bào)文最大生存時(shí)間)的時(shí)間,才會(huì)進(jìn)入CLOSED狀態(tài)

服務(wù)端狀態(tài)轉(zhuǎn)化:

  1. [ESTABLISHED -> CLOSE_WAIT] 當(dāng)客戶端主動(dòng)關(guān)閉連接(調(diào)用close),服務(wù)器會(huì)收到結(jié)束報(bào)文段,服務(wù)器返回確認(rèn)報(bào)文段并進(jìn)入CLOSE_WAIT
  2. [CLOSE_WAIT -> LAST_ACK] 進(jìn)入CLOSE_WAIT后說(shuō)明服務(wù)器準(zhǔn)備關(guān)閉連接(需要處理完之前的數(shù)據(jù));當(dāng)服務(wù)器真正調(diào)用close關(guān)閉連接時(shí),會(huì)向客戶端發(fā)送FIN,此時(shí)服務(wù)器進(jìn)入LAST_ACK狀態(tài),等待最后一個(gè)ACK到來(lái)(這個(gè)ACK是客戶端確認(rèn)收到了FIN)
  3. [LAST_ACK -> CLOSED] 服務(wù)器收到了對(duì)FIN的ACK,徹底關(guān)閉連接

:觸發(fā)四次揮手是上層雙方調(diào)用close(sock)

  • 主動(dòng)斷開連接的一方,最終狀態(tài)是TIME_WAIT
  • 被動(dòng)斷開連接的一方,兩次揮手完成,進(jìn)入CLOSE_WAIT

下面進(jìn)行做實(shí)驗(yàn),查看這兩個(gè)狀態(tài)

2.9.3 演示查看TIME_WAIT和CLOSE_WAIT狀態(tài)

代碼直接采用socket套接字TCP多線程版的,前面已經(jīng)講解過(guò)了,就不再解釋

初始化服務(wù)器initServer函數(shù)步驟大致如下:

  • 調(diào)用socket函數(shù),創(chuàng)建套接字。
  • 調(diào)用bind函數(shù),為服務(wù)端綁定一個(gè)端口號(hào)
  • 調(diào)用listen函數(shù),將套接字設(shè)置為監(jiān)聽狀態(tài)

啟動(dòng)服務(wù)器start函數(shù)步驟大致如下:

  • 調(diào)用accept函數(shù),獲取新鏈接
  • 為客戶端提供服務(wù)

服務(wù)端提供的服務(wù),什么也不做,等待20秒服務(wù)端就直接退出即可,我們?cè)谶@20秒內(nèi)操作,操作就暈在客戶端連接好了之后,客戶端在20秒內(nèi)主動(dòng)退出即可

即演示的目的效果是:

  • 客戶端主動(dòng)退出,最終狀態(tài)是TIME_WAIT
  • 服務(wù)端是被動(dòng)斷開連接的一方,被動(dòng)斷開連接的一方,兩次揮手完成,進(jìn)入CLOSE_WAIT

tcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

using namespace std;

static const int gbacklog = 5;

// 錯(cuò)誤類型枚舉
enum
{
    UAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};

class tcpServer; // 聲明
class ThreadDate
{
public:
    ThreadDate(tcpServer *self, int sockfd)
        : _self(self), _sockfd(sockfd)
    {}

public:
    tcpServer *_self;
    int _sockfd;
};

class tcpServer
{
public:
    tcpServer(const uint16_t &port)
        : _listensock(-1), _port(port)
    {}

    // 初始化服務(wù)器
    void initServer()
    {
        // 1.創(chuàng)建套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock == -1)
        {
            cout << "create socket error" << endl;
            exit(SOCKET_ERR);
        }

        // 2.綁定端口
        // 2.1 填充 sockaddr_in 結(jié)構(gòu)體
        struct sockaddr_in local;
        bzero(&local, sizeof(local));       // 把 sockaddr_in結(jié)構(gòu)體全部初始化為0
        local.sin_family = AF_INET;         // 未來(lái)通信采用的是網(wǎng)絡(luò)通信
        local.sin_port = htons(_port);      // htons(_port)主機(jī)字節(jié)序轉(zhuǎn)網(wǎng)絡(luò)字節(jié)序
        local.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY 就是  0x00000000

        // 2.2 綁定
        int n = bind(_listensock, (struct sockaddr *)&local, sizeof(local)); // 需要強(qiáng)轉(zhuǎn),(struct sockaddr*)&local
        if (n == -1)
        {
            cout << "bind socket error" << endl;
            exit(BIND_ERR);
        }

        // 3. 把_listensock套接字設(shè)置為監(jiān)聽狀態(tài)
        if (listen(_listensock, gbacklog) == -1)
        {
            cout << "listen socket error" << endl;
            exit(LISTEN_ERR);
        }
    }

    // 啟動(dòng)服務(wù)器
    void start()
    {
        for (;;)
        {
            // 4. 獲取新鏈接,accept從_listensock套接字里面獲取新鏈接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 這里的sockfd才是真正為客戶端請(qǐng)求服務(wù)
            int sockfd = accept(_listensock, (struct sockaddr *)&peer, &len);
            if (sockfd < 0) // 獲取新鏈接失敗,但不會(huì)影響服務(wù)端運(yùn)行
            {
                cout << "accept error, next!" << endl;
                continue;
            }
            cout << "accept a new line success, sockfd: " << sockfd << endl;

            // 5. 為sockfd提供服務(wù),即為客戶端提供服務(wù)
            // 多線程版
            pthread_t tid;
            ThreadDate *td = new ThreadDate(this, sockfd);
            pthread_create(&tid, nullptr, threadRoutine, td);
        }
    }

    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); // 線程分離
        ThreadDate *td = static_cast<ThreadDate *>(args);
        td->_self->serviceIo(td->_sockfd);
        
        close(td->_sockfd); // 必須關(guān)閉,由新線程關(guān)閉
        delete td;
        return nullptr;
    }

    // 提供服務(wù)
    void serviceIo(int sockfd)
    {
        sleep(20); // 20秒之后線程關(guān)閉_sockfd,線程也退出
    }

    ~tcpServer()
    {}

private:
    int _listensock; // listen套接字,不是用來(lái)數(shù)據(jù)通信的,是用來(lái)監(jiān)聽鏈接到來(lái)
    uint16_t _port;  // 端口號(hào)
};

tcpServer.cc

#include "tcpServer.hpp"
#include <memory>

// 使用手冊(cè)
// ./tcpServer port
static void Uage(string proc)
{
    cout << "\nUage:\n\t" << proc << " local_port\n\n";
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Uage(argv[0]);
        exit(UAGE_ERR);
    }

    uint16_t port = atoi(argv[1]); // string to int

    unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer(); // 初始化服務(wù)器
    tsvr->start();      // 啟動(dòng)服務(wù)器

    return 0;
}

下面需要使用telnet命令,先介紹該命令

2.9.4 telnet命令

telnet命令

Telnet是一種用于遠(yuǎn)程登錄和管理網(wǎng)絡(luò)設(shè)備的協(xié)議,同時(shí)也可以用于測(cè)試網(wǎng)絡(luò)連接和端口的連通性。Telnet客戶端可以通過(guò)命令行或者圖形界面進(jìn)行操作。

在命令行中,可以使用telnet命令來(lái)連接到遠(yuǎn)程主機(jī)或者測(cè)試網(wǎng)絡(luò)連接。以下是使用telnet命令的一些常見用法:

1、連接到遠(yuǎn)程主機(jī):

telnet <hostname> [port]

是要連接的遠(yuǎn)程主機(jī)的域名或者IP地址,[port]是要連接的端口,默認(rèn)為23(Telnet默認(rèn)端口)

例如,要連接到主機(jī)example.com的Telnet服務(wù),可以使用以下命令:

telnet example.com

2、測(cè)試端口連通性:

telnet <hostname> <port>

<hostname>是要測(cè)試的主機(jī)的域名或者IP地址,<port>是要測(cè)試的端口號(hào)

例如,要測(cè)試主機(jī)example.com80端口是否連通,可以使用以下命令:

telnet example.com 80

2、退出Telnet會(huì)話:

在Telnet會(huì)話中,可以使用以下命令退出:

quit

或者按下Ctrl+],然后輸入quit。

4、安裝

如果Linux沒(méi)有安裝,先安裝telnet客戶端

yum install -y telnet

:普通用戶需要sudo提權(quán)

2.9.5 演示

先運(yùn)行服務(wù)端,然后使用telnet命令連接服務(wù)端

注:由于沒(méi)有多余的機(jī)器,只在一臺(tái)機(jī)器下測(cè)試

打循環(huán)查看tcpServer:(查看服務(wù)端的)

while : ;do netstat -natp | grep tcpServer; sleep 1; echo "-----------------"; done

打循環(huán)查看telnet:(查看客戶端)

while : ;do netstat -natp | grep telnet; sleep 1; echo "-----------------"; done

打循環(huán)查看TIME_WAIT:(查看客戶端)

while : ;do netstat -natp | grep TIME_WAIT; sleep 1; echo "-----------------"; done

打循環(huán)查看TIME_WAIT是因?yàn)椋瑃elnet退出后查不到該進(jìn)程了

準(zhǔn)備工作完成,先運(yùn)行循環(huán),再啟動(dòng)服務(wù)端,再進(jìn)行telnet,客戶端要在20秒內(nèi)退出

0、運(yùn)行循環(huán)

1、啟動(dòng)服務(wù)端
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
2、telnet
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
3、telnet在20秒內(nèi)退出連接
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
服務(wù)端關(guān)閉sock,并退出
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
客戶端退出后,可以查到客戶端的TIME_WAIT狀態(tài)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
上述是我演示的是四次揮手的過(guò)程

  • 主動(dòng)斷開連接的一方,最終狀態(tài)是TIME_WAIT
  • 被動(dòng)斷開連接的一方,兩次揮手完成,進(jìn)入CLOSE_WAIT

TIME_WAIT狀態(tài)持續(xù)一段時(shí)間后才進(jìn)入真正的關(guān)閉
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

TIME_WAIT的等待時(shí)長(zhǎng)是多少?

  • 太長(zhǎng)的TIME_WAIT狀態(tài)會(huì)導(dǎo)致等待方維持連接的成本增加,浪費(fèi)資源。
  • 太短的TIME_WAIT狀態(tài)可能無(wú)法保證ACK被對(duì)方接收,數(shù)據(jù)在網(wǎng)絡(luò)中消散。
  • TCP協(xié)議規(guī)定,主動(dòng)關(guān)閉連接的一方在四次揮手后要進(jìn)入TIME_WAIT狀態(tài),等待兩個(gè)MSL的時(shí)間才能進(jìn)入CLOSED狀態(tài)。這樣可以確保連接的可靠關(guān)閉。

查看Linux的MSL時(shí)間長(zhǎng)度:

cat /proc/sys/net/ipv4/tcp_fin_timeout

Centos7上默認(rèn)配置的值是60秒(可以修改)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

為什么是TIME_WAIT的時(shí)間是2MSL?

  • MSL是TCP報(bào)文的最大生存時(shí)間, 因此TIME_WAIT持續(xù)存在2*MSL的話
  • 就能保證在兩個(gè)傳輸方向上的尚未被接收或遲到的報(bào)文段都已經(jīng)消失(否則服務(wù)器立刻重啟,可能會(huì)收到來(lái)自上一個(gè)進(jìn)程的遲到的數(shù)據(jù),但是這種數(shù)據(jù)很可能是錯(cuò)誤的)
  • 同時(shí)也是在理論上保證最后一個(gè)報(bào)文可靠到達(dá)(假設(shè)最后一個(gè)ACK丟失,那么服務(wù)器會(huì)再重發(fā)一個(gè)FIN。這時(shí)雖然客戶端的進(jìn)程不在了,但是TCP連接還在,仍然可以重發(fā)FIN)

如果服務(wù)器出現(xiàn)了大量的CLOSE_WAIT狀態(tài),說(shuō)明服務(wù)器:

  • 服務(wù)器有bug,沒(méi)有做關(guān)閉文件符操作close(sock)
  • 服務(wù)器有壓力,可能服務(wù)端一直推送消息給客戶端,導(dǎo)致來(lái)不及close文件描述符

2.10 解決TIME_WAIT狀態(tài)引起的bind失敗的問(wèn)題

綁定失敗現(xiàn)象:

有客戶端連著服務(wù)端,服務(wù)端主動(dòng)退出(需要進(jìn)行重啟),緊接著服務(wù)端再次啟動(dòng)綁定相同的端口就會(huì)出現(xiàn)綁定失敗的現(xiàn)象

綁定失敗的原因:

  • 服務(wù)端主動(dòng)斷開連接(服務(wù)端退出),最終狀態(tài)是TIME_WAIT
  • TIME_WAIT持續(xù)存在2*MSL,即TIME_WAIT狀態(tài)需要等待兩個(gè)MSL的時(shí)間才能進(jìn)入CLOSED狀態(tài)
  • 2*MSL時(shí)間內(nèi),服務(wù)器綁定的端口還一直被占用
  • 即服務(wù)端立馬退出,再進(jìn)行重啟,這時(shí)候就會(huì)存在綁定端口失敗的問(wèn)題

現(xiàn)象演示

代碼依舊是上面的,運(yùn)行服務(wù)器,客戶端進(jìn)行連接,然后服務(wù)端主動(dòng)退出,再進(jìn)行重啟就會(huì)出現(xiàn)綁定端口失敗
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

綁定端口失敗的危害

  • 比如在某些場(chǎng)景下,618,雙11,這時(shí)候服務(wù)器的壓力會(huì)比較大,如果說(shuō)服務(wù)器崩潰了,需要立即馬上進(jìn)行重啟,并且能夠立馬重啟成功,如果在一分鐘內(nèi)或兩分鐘內(nèi)無(wú)法重啟成功,就會(huì)造成巨大的損失(金錢),618,雙11都是分秒必爭(zhēng)的

解決方法

使用setsockopt()設(shè)置socket描述符的選項(xiàng)SO_REUSEADDR為1,表示允許創(chuàng)建端口號(hào)相同但I(xiàn)P地址不同的多個(gè)socket描述符
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

// 設(shè)置地址復(fù)用
int opt = 1;
setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

修改代碼

在創(chuàng)建套接字后面設(shè)置即可
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
編譯運(yùn)行,再進(jìn)行測(cè)試,bind綁定失敗問(wèn)題沒(méi)有了
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

2.11 流量控制

這個(gè)在前面的16位窗口大小已經(jīng)談過(guò)一部分了(上一篇),這里再來(lái)詳細(xì)介紹。

接收端處理數(shù)據(jù)的速度是有限的,如果發(fā)送端發(fā)的太快,導(dǎo)致接收端的緩沖區(qū)被打滿,這個(gè)時(shí)候如果發(fā)送端繼續(xù)發(fā)送,就會(huì)造成丟包,繼而引起丟包重傳等等一系列連鎖反應(yīng)

因此TCP支持根據(jù)接收端的處理能力,來(lái)決定發(fā)送端的發(fā)送速度,這個(gè)機(jī)制就叫做流量控制(Flow Control)

  • 接收端將自己可以接收的緩沖區(qū)大小放入 TCP 首部中的 “窗口大小” 字段,通過(guò)ACK端通知發(fā)送端
  • 窗口大小字段越大,說(shuō)明網(wǎng)絡(luò)的吞吐量越高
  • 接收端一旦發(fā)現(xiàn)自己的緩沖區(qū)快滿了,就會(huì)將窗口大小設(shè)置成一個(gè)更小的值通知給發(fā)送端
  • 發(fā)送端接受到這個(gè)窗口之后,就會(huì)減慢自己的發(fā)送速度
  • 如果接收端緩沖區(qū)滿了,就會(huì)將窗口置為0,這時(shí)發(fā)送方不再發(fā)送數(shù)據(jù),但是需要定期發(fā)送一個(gè)窗口探測(cè)數(shù)據(jù)段,使接收端把窗口大小告訴發(fā)送端

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

16位窗口大小:16位最大表示65535,那TCP窗口最大就是65535嗎?

  • 理論上確實(shí)是這樣的,但實(shí)際上TCP報(bào)頭當(dāng)中40字節(jié)的選項(xiàng)字段中包含了一個(gè)窗口擴(kuò)大因子M,實(shí)際窗口大小是窗口字段的值左移M位得到的

第一次向?qū)Ψ桨l(fā)送數(shù)據(jù)時(shí)如何得知對(duì)方的窗口大?。?/p>

  • 雙方在進(jìn)行TCP通信之前需要進(jìn)行三次握手建立連接。
  • 在握手過(guò)程中,除了驗(yàn)證通信信道是否通暢,雙方還會(huì)交換其他信息,其中包括告知對(duì)方自己的接收能力。
  • 這樣,在雙方正式開始通信之前,雙方已經(jīng)了解對(duì)方的接收數(shù)據(jù)能力。
  • 因此,雙方在發(fā)送數(shù)據(jù)時(shí)可以根據(jù)對(duì)方的接收能力進(jìn)行調(diào)整,避免緩沖區(qū)溢出的問(wèn)題。

2.12 滑動(dòng)窗口

前面已經(jīng)提到過(guò)TCP的工作模式了(上一篇),TCP的工作模式有兩種

第一種串行發(fā)送數(shù)據(jù)(不是TCP真正的工作模式)

一發(fā)一收的方式性能較低(串行)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

第二種并行發(fā)送數(shù)據(jù)(TCP真正的工作模式)

并行發(fā)送數(shù)據(jù),可以大大的提高性能(其實(shí)是將多個(gè)段的等待時(shí)間重疊在一起了)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
第二種情況是TCP的真正工作模式,即主流,但是也會(huì)存在第一種工作模式,第一種情況是很少的,但也會(huì)存在

發(fā)送方可以一次發(fā)送多個(gè)報(bào)文給對(duì)方,此時(shí)也就意味著發(fā)送出去的這部分報(bào)文當(dāng)中有相當(dāng)一部分?jǐn)?shù)據(jù)是暫時(shí)沒(méi)有收到應(yīng)答的

發(fā)送緩沖區(qū)當(dāng)中的數(shù)據(jù)可以分為三部分:

  • 已經(jīng)發(fā)送并且已經(jīng)收到ACK的數(shù)據(jù)。
  • 已經(jīng)發(fā)送還但沒(méi)有收到ACK的數(shù)據(jù)。
  • 還沒(méi)有發(fā)送的數(shù)據(jù)。
  • 還有一個(gè)就是剩余空間,即沒(méi)有數(shù)據(jù)的空間,這個(gè)剩余空間可能有也可能沒(méi)有

發(fā)送緩沖區(qū)的第二部分就叫做滑動(dòng)窗口

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

窗口大小指的是無(wú)需等待確認(rèn)應(yīng)答而可以繼續(xù)發(fā)送數(shù)據(jù)的最大值

  • 下圖的窗口大小就是4000個(gè)字節(jié)(四個(gè)段)
  • 發(fā)送前四個(gè)段的時(shí)候,不需要等待任何ACK,直接發(fā)送

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  • 收到第一個(gè)ACK后,滑動(dòng)窗口向后移動(dòng),繼續(xù)發(fā)送下一個(gè)段的數(shù)據(jù),依次類推
  • 操作系統(tǒng)內(nèi)核為了維護(hù)這個(gè)滑動(dòng)窗口,需要開辟發(fā)送緩沖區(qū)來(lái)記錄當(dāng)前還有哪些數(shù)據(jù)沒(méi)有應(yīng)答,只有確認(rèn)應(yīng)答過(guò)的數(shù)據(jù),才能從緩沖區(qū)刪掉
  • 窗口越大, 則網(wǎng)絡(luò)的吞吐率就越高

例如

  • 1-1000、1001-2000數(shù)據(jù)接收端已經(jīng)收到了并且進(jìn)行了ACK,則1-1000、1001-2000的數(shù)據(jù)不在發(fā)送緩沖區(qū)的滑動(dòng)窗口里面了
  • 現(xiàn)在連續(xù)發(fā)送2001-3000、3001-4000、4001-5000、5001-6001的四個(gè)段數(shù)據(jù),此時(shí)不需要等待任何ACK,可以直接進(jìn)行發(fā)送

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  • 當(dāng)收到對(duì)方應(yīng)答的確認(rèn)序號(hào)為3001時(shí),說(shuō)明2001-3000這個(gè)數(shù)據(jù)段已經(jīng)被對(duì)方收到了,此時(shí)該數(shù)據(jù)段應(yīng)該被歸入發(fā)送緩沖區(qū)當(dāng)中的已發(fā)送&&已收到ACK部分
  • 而由于我們假設(shè)對(duì)方的窗口大小一直是4000,因此滑動(dòng)窗口現(xiàn)在可以向右移動(dòng),繼續(xù)發(fā)送6001-7000的數(shù)據(jù)段,以此類推

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  • TCP的重傳機(jī)制要求暫時(shí)保存發(fā)出但未收到確認(rèn)的數(shù)據(jù),這些數(shù)據(jù)實(shí)際上位于滑動(dòng)窗口中。
  • 滑動(dòng)窗口的左側(cè)是已經(jīng)被對(duì)方可靠接收到的數(shù)據(jù),因此只有滑動(dòng)窗口左側(cè)的數(shù)據(jù)可以被覆蓋或刪除。
  • 滑動(dòng)窗口除了限定可以直接發(fā)送的數(shù)據(jù),還支持TCP的重傳機(jī)制,即當(dāng)發(fā)送的數(shù)據(jù)未收到確認(rèn)時(shí),可以重新發(fā)送滑動(dòng)窗口中的數(shù)據(jù)。
  • 這樣可以確保數(shù)據(jù)的可靠傳輸,提高通信的可靠性和效率。

發(fā)送緩沖區(qū)建模1:

  • 緩沖區(qū)的本質(zhì)就是一個(gè)char類型的數(shù)組,而滑動(dòng)窗口就在發(fā)送緩沖區(qū)內(nèi),所以滑動(dòng)窗口本質(zhì)也是一個(gè)char類型的數(shù)組
  • 滑動(dòng)窗口可以被看作是由兩個(gè)指針限定的一個(gè)范圍(指向數(shù)組下標(biāo))。比如,我們可以用win_start指針指向滑動(dòng)窗口的左側(cè),win_end指針指向滑動(dòng)窗口的右側(cè)。
  • win_startwin_end之間的區(qū)間范圍內(nèi)的數(shù)據(jù)可以被稱為滑動(dòng)窗口。
  • 通過(guò)移動(dòng)這兩個(gè)指針,可以實(shí)現(xiàn)滑動(dòng)窗口的滑動(dòng)和調(diào)整大小,以適應(yīng)不同的網(wǎng)絡(luò)條件和接收方的接收能力。這樣的設(shè)計(jì)可以有效地控制發(fā)送方的發(fā)送速率,并支持TCP的重傳機(jī)制,以確保數(shù)據(jù)的可靠傳輸。

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
當(dāng)發(fā)送端收到對(duì)方的ACK應(yīng)答時(shí),如果響應(yīng)當(dāng)中的確認(rèn)序號(hào)為xxx,窗口大小為win,此時(shí)就可以將win_start更新為xxx,而將win_end更新為win_start+win(暫時(shí)這樣理解,下面再詳細(xì)解釋)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

滑動(dòng)窗口大小是怎么設(shè)定的??未來(lái)怎么變化?

  • 滑動(dòng)窗口的大小是根據(jù)網(wǎng)絡(luò)條件和接收方的接收能力來(lái)設(shè)定的。通常,滑動(dòng)窗口的大小由發(fā)送方和接收方之間的協(xié)商來(lái)確定。
  • 在建立TCP連接時(shí),雙方會(huì)通過(guò)握手過(guò)程交換窗口大小的信息。發(fā)送方通常會(huì)根據(jù)接收方的通知來(lái)設(shè)定初始的滑動(dòng)窗口大小。這個(gè)初始大小可以是固定的值,也可以是根據(jù)網(wǎng)絡(luò)條件動(dòng)態(tài)調(diào)整的值。
  • 未來(lái),滑動(dòng)窗口的大小可以根據(jù)網(wǎng)絡(luò)條件和接收方的反饋信息進(jìn)行動(dòng)態(tài)變化。TCP協(xié)議中有一種叫做擁塞控制的機(jī)制,可以根據(jù)網(wǎng)絡(luò)擁塞的程度來(lái)調(diào)整滑動(dòng)窗口的大小。當(dāng)網(wǎng)絡(luò)擁塞時(shí),發(fā)送方會(huì)減小滑動(dòng)窗口的大小,以降低發(fā)送速率,從而減輕網(wǎng)絡(luò)負(fù)載。當(dāng)網(wǎng)絡(luò)狀況改善時(shí),發(fā)送方可以增大滑動(dòng)窗口的大小,以提高發(fā)送速率。(擁塞控制下面談)

總之,滑動(dòng)窗口的大小是根據(jù)網(wǎng)絡(luò)條件和接收方的接收能力來(lái)動(dòng)態(tài)設(shè)定和調(diào)整的,以實(shí)現(xiàn)更高效的數(shù)據(jù)傳輸和網(wǎng)絡(luò)擁塞控制。

16位窗口大小與滑動(dòng)窗口

  • 16位窗口大小指的是TCP協(xié)議中的窗口字段的大小,它用16位二進(jìn)制數(shù)來(lái)表示。窗口字段表示接收方還能接收多少字節(jié)的數(shù)據(jù),用于控制發(fā)送方的發(fā)送速率。
  • 滑動(dòng)窗口是一種數(shù)據(jù)傳輸?shù)臋C(jī)制,用于控制發(fā)送方發(fā)送數(shù)據(jù)的速率和接收方接收數(shù)據(jù)的能力?;瑒?dòng)窗口的大小可以根據(jù)網(wǎng)絡(luò)條件和接收方的接收能力進(jìn)行調(diào)整。
  • 滑動(dòng)窗口的大小可以與16位窗口大小相關(guān)聯(lián)。發(fā)送方在發(fā)送數(shù)據(jù)時(shí),會(huì)根據(jù)接收方的窗口大小來(lái)確定發(fā)送窗口的大小。如果接收方的16位窗口大小為N,發(fā)送方可以將發(fā)送窗口的大小設(shè)置為N,以確保發(fā)送的數(shù)據(jù)不會(huì)超過(guò)接收方的窗口大小。
  • 當(dāng)發(fā)送方發(fā)送了一段數(shù)據(jù)后,接收方會(huì)發(fā)送確認(rèn)消息(ACK)給發(fā)送方,同時(shí)更新窗口字段的值。發(fā)送方根據(jù)接收方的窗口字段的值來(lái)調(diào)整發(fā)送窗口的大小,以控制發(fā)送的數(shù)據(jù)量。

總之,16位窗口大小與滑動(dòng)窗口的關(guān)系是,發(fā)送方根據(jù)接收方的16位窗口大小來(lái)確定發(fā)送窗口的大小,并根據(jù)接收方的確認(rèn)消息來(lái)動(dòng)態(tài)調(diào)整發(fā)送窗口的大小,以實(shí)現(xiàn)數(shù)據(jù)的可靠傳輸和流量控制。

滑動(dòng)窗口會(huì)向左滑動(dòng)嗎?滑動(dòng)窗口整體一定會(huì)向右滑動(dòng)么?

  • 一定不會(huì)向左滑動(dòng),滑動(dòng)窗口左邊的數(shù)據(jù)一定是已經(jīng)發(fā)送了并且是對(duì)方已經(jīng)收到了
  • 在正常情況下,滑動(dòng)窗口整體是向右滑動(dòng)的,即發(fā)送方不斷發(fā)送新的數(shù)據(jù),接收方不斷確認(rèn)收到數(shù)據(jù),并將窗口向右滑動(dòng)。這樣可以實(shí)現(xiàn)高效的數(shù)據(jù)傳輸。
  • 然而,在某些特殊情況下,滑動(dòng)窗口整體可能會(huì)不滑動(dòng),滑動(dòng)窗口還可能一直變小。比如,對(duì)方的接收緩沖區(qū)的上層不取數(shù)據(jù),緩沖區(qū)慢慢會(huì)被打滿,此時(shí)滑動(dòng)窗口的大小也慢慢變小,并且整體不會(huì)向右滑動(dòng)

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

滑動(dòng)窗口大小會(huì)一直不變嗎?會(huì)變小嗎?會(huì)變大嗎??

  • 滑動(dòng)窗口的大小是根據(jù)網(wǎng)絡(luò)條件和接收方的接收能力來(lái)動(dòng)態(tài)設(shè)定和調(diào)整的,即一直會(huì)發(fā)生變化(也可能不變,不會(huì)是一直不變,如上面例子確認(rèn)3001、4001)
  • 滑動(dòng)窗口可能會(huì)變小,上面的例子,確認(rèn)5001、6001,最小就是變?yōu)?
  • 滑動(dòng)窗口可能會(huì)變大,比如,對(duì)方的接收緩沖區(qū)的上層一下子讀取了完了全部的數(shù)據(jù),此時(shí)接收緩沖區(qū)的接收能力就會(huì)變大,滑動(dòng)窗口也隨之變大,提高發(fā)送數(shù)據(jù)的速率

丟包問(wèn)題

當(dāng)發(fā)送端一次發(fā)送多個(gè)報(bào)文數(shù)據(jù)時(shí),此時(shí)的丟包情況也可以分為兩種。

情況一: 數(shù)據(jù)包已經(jīng)抵達(dá),ACK丟包。

  • 部分ACK丟包并不要緊,此時(shí)可以通過(guò)后續(xù)的ACK進(jìn)行確認(rèn)
  • 在這種情況下,由于接收方的ACK對(duì)于2001-3000和4001-5000的數(shù)據(jù)包丟失了,發(fā)送方無(wú)法直接知道這些數(shù)據(jù)包是否已經(jīng)成功到達(dá)。但是,發(fā)送方可以通過(guò)接收到的最后一個(gè)有效ACK(5001-6000數(shù)據(jù)包的確認(rèn))來(lái)推斷一些信息。
  • TCP協(xié)議的規(guī)定,收到一個(gè)確認(rèn)ACK序號(hào)為6001的確認(rèn)消息表示接收方成功接收了序號(hào)為1-6000的字節(jié)數(shù)據(jù)。因此,發(fā)送方可以推測(cè)在丟失的這一段(2001-3000和4001-5000)之前的數(shù)據(jù)包也應(yīng)該已經(jīng)到達(dá)了接收方。

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
情況二: 數(shù)據(jù)包真的丟了。

  • 當(dāng)發(fā)送端連續(xù)收到三次確認(rèn)序號(hào)為1001的響應(yīng)報(bào)文,即接收方對(duì)于1001-2000的數(shù)據(jù)包的確認(rèn)被重復(fù)確認(rèn)了三次,發(fā)送端會(huì)認(rèn)為這些數(shù)據(jù)包丟失了。
  • TCP協(xié)議的重傳機(jī)制,當(dāng)發(fā)送端連續(xù)收到三次相同的確認(rèn)序號(hào)時(shí),會(huì)觸發(fā)快速重傳。在這種情況下,發(fā)送端會(huì)立即重傳1001-2000的數(shù)據(jù)包,以確保接收方能夠正確接收到這些數(shù)據(jù)。
  • 通過(guò)快速重傳,發(fā)送端可以快速恢復(fù)丟失的數(shù)據(jù)包,提高數(shù)據(jù)的可靠性和傳輸效率。
  • 因此,在這個(gè)特殊情況下,滑動(dòng)窗口的整體位置不會(huì)發(fā)生變化,但發(fā)送端會(huì)根據(jù)丟失的數(shù)據(jù)包觸發(fā)快速重傳機(jī)制,重新發(fā)送丟失的數(shù)據(jù)包。

TCP協(xié)議的重傳機(jī)制,當(dāng)發(fā)送端連續(xù)收到三次相同的確認(rèn)序號(hào)時(shí)(觸發(fā)重傳機(jī)制),會(huì)觸發(fā)高速重發(fā)控制,也叫快重傳

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

發(fā)送緩沖區(qū)建模2:如果滑動(dòng)窗口一直向后滑動(dòng),空間大小不夠了怎么辦??

  • 發(fā)送緩沖區(qū)被內(nèi)核組織稱為了一種環(huán)形結(jié)構(gòu)(本質(zhì)依舊是插入類型的線性數(shù)組),通過(guò)數(shù)據(jù)下標(biāo)進(jìn)行模運(yùn)算實(shí)現(xiàn)
  • 環(huán)形結(jié)構(gòu)怎么滑動(dòng)也不會(huì)發(fā)生越界問(wèn)題

快重傳 VS 超時(shí)重傳

  • 快速重傳和超時(shí)重傳是TCP協(xié)議中兩種常用的重傳機(jī)制,用于處理丟失的數(shù)據(jù)包。它們的主要區(qū)別在于觸發(fā)重傳的條件和重傳的時(shí)機(jī)。
  • 快速重傳(Fast Retransmit)是指當(dāng)發(fā)送方連續(xù)收到三個(gè)重復(fù)的確認(rèn)序號(hào)時(shí),即接收方對(duì)同一個(gè)數(shù)據(jù)包的確認(rèn)被重復(fù)確認(rèn)了三次,發(fā)送方會(huì)立即重傳該數(shù)據(jù)包。這是因?yàn)檫B續(xù)收到重復(fù)確認(rèn)序號(hào)通常意味著該數(shù)據(jù)包已經(jīng)丟失了,為了快速恢復(fù)丟失的數(shù)據(jù)包,發(fā)送方會(huì)觸發(fā)快速重傳機(jī)制,立即重傳該數(shù)據(jù)包,而不必等待超時(shí)重傳。
  • 超時(shí)重傳(Timeout Retransmission)是指當(dāng)發(fā)送方發(fā)送一個(gè)數(shù)據(jù)包后,等待一段時(shí)間(超時(shí)時(shí)間)后仍未收到對(duì)應(yīng)的確認(rèn)消息時(shí),發(fā)送方會(huì)認(rèn)為該數(shù)據(jù)包丟失了,會(huì)觸發(fā)超時(shí)重傳機(jī)制,重新發(fā)送該數(shù)據(jù)包。超時(shí)時(shí)間是根據(jù)網(wǎng)絡(luò)狀況和往返時(shí)間動(dòng)態(tài)調(diào)整的,如果網(wǎng)絡(luò)延遲較高或丟包較多,超時(shí)時(shí)間會(huì)相應(yīng)增加。
  • 快速重傳和超時(shí)重傳都是為了處理丟失的數(shù)據(jù)包,但觸發(fā)條件和重傳時(shí)機(jī)不同。快速重傳是基于連續(xù)收到重復(fù)確認(rèn)序號(hào)觸發(fā)的,可以更快地恢復(fù)丟失的數(shù)據(jù)包,減少了等待超時(shí)的時(shí)間。而超時(shí)重傳是基于超時(shí)時(shí)間觸發(fā)的,適用于網(wǎng)絡(luò)延遲較高或丟包較多的情況。

綜上所述,快速重傳和超時(shí)重傳是TCP協(xié)議中兩種常用的重傳機(jī)制,根據(jù)不同的情況選擇合適的重傳策略,以提高數(shù)據(jù)的可靠傳輸性能。


以上話題都是端到端,客戶端到服務(wù)端,服務(wù)端到客戶端,沒(méi)有考慮有網(wǎng)絡(luò)的,TCP也有網(wǎng)絡(luò)問(wèn)題方面的機(jī)制控制,就是擁塞控制。

2.13 擁塞控制

為什么會(huì)有擁塞控制?

  • 兩個(gè)主機(jī)在進(jìn)行TCP通信的過(guò)程中,偶爾出現(xiàn)個(gè)別數(shù)據(jù)包丟失的情況是很正常的,此時(shí)可以通過(guò)快速重傳或超時(shí)重傳來(lái)補(bǔ)發(fā)丟失的數(shù)據(jù)包。(TCP認(rèn)為是自己的問(wèn)題)
  • 但如果雙方在通信時(shí)出現(xiàn)大量數(shù)據(jù)包丟失的情況,這就不再是正?,F(xiàn)象了。(TCP認(rèn)為不是自己的問(wèn)題)

舉個(gè)例子:

  • 比如,高數(shù)考試,一個(gè)班有30人,考試下來(lái)全班只有張三一個(gè)人掛了,張三認(rèn)為這是自己的問(wèn)題
  • 那如果考試下來(lái),全班只有張三一個(gè)人通過(guò)了考試,其他全掛了,這時(shí)其他人認(rèn)為不是自己的問(wèn)題也不是張三的問(wèn)題,而是試卷的問(wèn)題
  • 同理TCP也是如此
  • 比如,客戶端發(fā)送了10000個(gè)報(bào)文,服務(wù)端收到了9999個(gè),只丟了一個(gè)報(bào)文,TCP認(rèn)為這是自己的問(wèn)題
  • 如果服務(wù)端只收到了10個(gè),丟了9990個(gè)報(bào)文,這時(shí)TCP就不會(huì)再認(rèn)為是自己的問(wèn)題(即不會(huì)進(jìn)行重傳報(bào)文),而是網(wǎng)絡(luò)的問(wèn)題

所以,TCP不僅考慮了通信雙端主機(jī)的問(wèn)題,同時(shí)也考慮了網(wǎng)絡(luò)的問(wèn)題。

  • 擁塞窗口:考慮雙方通信時(shí)網(wǎng)絡(luò)的問(wèn)題,如果發(fā)送的數(shù)據(jù)超過(guò)了擁塞窗口的大小,可能引起網(wǎng)絡(luò)擁塞。在雙方網(wǎng)絡(luò)通信時(shí),偶爾出現(xiàn)少量的丟包是允許的,但一旦出現(xiàn)大量的丟包,這就是量變引起質(zhì)變,此時(shí)TCP不再假設(shè)是雙方接收和發(fā)送數(shù)據(jù)的問(wèn)題,而是判斷雙方通信信道網(wǎng)絡(luò)出現(xiàn)了擁塞問(wèn)題。
  • 網(wǎng)絡(luò)擁塞:是指在計(jì)算機(jī)網(wǎng)絡(luò)中,當(dāng)網(wǎng)絡(luò)中的數(shù)據(jù)流量超過(guò)網(wǎng)絡(luò)鏈路或節(jié)點(diǎn)的處理能力時(shí),導(dǎo)致網(wǎng)絡(luò)性能下降、延遲增加、丟包率增加等現(xiàn)象。網(wǎng)絡(luò)擁塞通常發(fā)生在網(wǎng)絡(luò)的瓶頸點(diǎn),即網(wǎng)絡(luò)中的某些關(guān)鍵節(jié)點(diǎn)或鏈路無(wú)法處理大量的數(shù)據(jù)流量。

從另一個(gè)視角看待

  • 如果出現(xiàn)了網(wǎng)絡(luò)擁塞問(wèn)題,網(wǎng)絡(luò)擁塞不僅僅影響單個(gè)主機(jī),幾乎會(huì)影響網(wǎng)絡(luò)中的所有主機(jī)(其他主機(jī)也是會(huì)發(fā)生大量丟包)。
  • 所以,出現(xiàn)了網(wǎng)絡(luò)擁塞之后,雙方主機(jī)可以減少數(shù)據(jù)傳輸?shù)乃俾?,盡量少發(fā)數(shù)據(jù)甚至不發(fā)數(shù)據(jù),等待網(wǎng)絡(luò)狀況恢復(fù)后再逐漸恢復(fù)數(shù)據(jù)傳輸速率(減少網(wǎng)絡(luò)負(fù)擔(dān))
  • 如果出現(xiàn)了網(wǎng)絡(luò)擁塞,雙發(fā)出現(xiàn)了大量的丟包,雙方的主機(jī)還進(jìn)行重傳報(bào)文的,只會(huì)給網(wǎng)絡(luò)雪上加霜,網(wǎng)絡(luò)上其他主機(jī)也是如此(雪崩的時(shí)候沒(méi)有一片雪花是無(wú)辜的)
  • 因此,所有使用TCP傳輸控制協(xié)議的主機(jī)都需要執(zhí)行擁塞避免算法(擁塞控制),以統(tǒng)一減少發(fā)送窗口的大小,從而避免網(wǎng)絡(luò)擁塞問(wèn)題的進(jìn)一步惡化和傳播。
  • 因此,擁塞控制雖然看似只是針對(duì)單個(gè)主機(jī)的通信策略,但實(shí)際上是所有主機(jī)在網(wǎng)絡(luò)崩潰后都應(yīng)該遵守的策略。
  • 當(dāng)網(wǎng)絡(luò)發(fā)生擁塞時(shí),所有主機(jī)都需要執(zhí)行擁塞避免算法,這樣才能有效地緩解網(wǎng)絡(luò)擁塞問(wèn)題,防止雪崩效應(yīng)的發(fā)生,并盡快恢復(fù)網(wǎng)絡(luò)的正常運(yùn)行。

擁塞控制

雖然TCP有了滑動(dòng)窗口這個(gè)大殺器,能夠高效可靠的發(fā)送大量的數(shù)據(jù)。但是如果在剛開始階段就發(fā)送大量的數(shù)據(jù),仍然可能引發(fā)問(wèn)題

因?yàn)榫W(wǎng)絡(luò)上有很多的計(jì)算機(jī),可能當(dāng)前的網(wǎng)絡(luò)狀態(tài)就已經(jīng)比較擁堵,在不清楚當(dāng)前網(wǎng)絡(luò)狀態(tài)下,貿(mào)然發(fā)送大量的數(shù)據(jù),是很有可能引起雪上加霜

TCP引入 慢啟動(dòng)機(jī)制,先發(fā)少量的數(shù)據(jù),探探路,摸清當(dāng)前的網(wǎng)絡(luò)擁堵狀態(tài),再?zèng)Q定按照多大的速度傳輸數(shù)據(jù)

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
擁塞窗口:

  • 發(fā)送開始的時(shí)候,定義擁塞窗口大小為1
  • 每次收到一個(gè)ACK應(yīng)答,擁塞窗口加1
  • 每次發(fā)送數(shù)據(jù)包的時(shí)候,將擁塞窗口和接收端主機(jī)反饋的窗口大小做比較,取較小的值作為實(shí)際發(fā)送的窗口

即自己的滑動(dòng)窗口的大小 = min(擁塞窗口,對(duì)端窗口大小),兩者取較小值

  • 滑動(dòng)窗口:自己的
  • 擁塞窗口:網(wǎng)絡(luò)的
  • 窗口大?。簩?duì)端的,對(duì)端的接收能力

像上面這樣的擁塞窗口增長(zhǎng)速度,是指數(shù)級(jí)別的, “慢啟動(dòng)” 只是指初使時(shí)慢,但是增長(zhǎng)速度非???/p>

  • 為了不增長(zhǎng)的那么快,因此不能使擁塞窗口單純的加倍
  • 此處引入一個(gè)叫做慢啟動(dòng)的閾值
  • 當(dāng)擁塞窗口超過(guò)這個(gè)閾值的時(shí)候,不再按照指數(shù)方式增長(zhǎng),而是按照線性方式增長(zhǎng)
  • 當(dāng)TCP開始啟動(dòng)的時(shí)候,慢啟動(dòng)閾值等于對(duì)端窗口的最大值
  • 在每次超時(shí)重發(fā)的時(shí)候,慢啟動(dòng)閾值會(huì)變成原來(lái)的一半,同時(shí)擁塞窗口置回1

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

  • 指數(shù)增長(zhǎng)。剛開始進(jìn)行TCP通信時(shí)擁塞窗口的值為1,并不斷按指數(shù)的方式進(jìn)行增長(zhǎng)
  • 加法增大。慢啟動(dòng)的閾值初始時(shí)為對(duì)方窗口大小的最大值,圖中慢啟動(dòng)閾值的初始值為16,因此當(dāng)擁塞窗口的值增大到16時(shí)就不再按指數(shù)形式增長(zhǎng)了,而變成了的線性增長(zhǎng)。
  • 乘法減小。擁塞窗口在線性增長(zhǎng)的過(guò)程中,在增大到24時(shí)如果發(fā)生了網(wǎng)絡(luò)擁塞,此時(shí)慢啟動(dòng)的閾值將變?yōu)楫?dāng)前擁塞窗口的一半,也就是12,并且擁塞窗口的值被重新設(shè)置為1,所以下一次擁塞窗口由指數(shù)增長(zhǎng)變?yōu)榫€性增長(zhǎng)時(shí)擁塞窗口的值應(yīng)該是12。

少量的丟包,我們僅僅是觸發(fā)超時(shí)重傳;大量的丟包,我們就認(rèn)為網(wǎng)絡(luò)擁塞。

當(dāng)TCP通信開始后,網(wǎng)絡(luò)吞吐量會(huì)逐漸上升;隨著網(wǎng)絡(luò)發(fā)生擁堵,吞吐量會(huì)立刻下降。

擁塞控制,歸根結(jié)底是TCP協(xié)議想盡可能快的把數(shù)據(jù)傳輸給對(duì)方,但是又要避免給網(wǎng)絡(luò)造成太大壓力的折中方案。(擁塞控制也是為了保證可靠性和傳輸速率)

2.14 延遲應(yīng)答

如果接收數(shù)據(jù)的主機(jī)立刻返回ACK應(yīng)答, 這時(shí)候返回的窗口可能比較小
如果接收數(shù)據(jù)的主機(jī)收到數(shù)據(jù)后立即進(jìn)行ACK應(yīng)答,此時(shí)返回的窗口可能比較小。

  • 假設(shè)對(duì)方接收端緩沖區(qū)剩余空間大小為1M,對(duì)方一次收到500K的數(shù)據(jù)后,如果立即進(jìn)行ACK應(yīng)答,此時(shí)返回的窗口就是500K。
  • 但實(shí)際接收端處理數(shù)據(jù)的速度很快,10ms之內(nèi)就將接收緩沖區(qū)中500K的數(shù)據(jù)消費(fèi)掉了。
  • 在這種情況下,接收端處理還遠(yuǎn)沒(méi)有達(dá)到自己的極限,即使窗口再放大一些,也能處理過(guò)來(lái)。
  • 如果接收端稍微等一會(huì)再進(jìn)行ACK應(yīng)答,比如等待200ms再應(yīng)答,那么這時(shí)返回的窗口大小就是1M。

延遲應(yīng)答的目的不是為了保證可靠性,而是為了提高數(shù)據(jù)的傳輸效率。(保證在網(wǎng)絡(luò)不擁塞的情況下盡量提高傳輸效率)

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

那么所有的報(bào)文都可以延遲應(yīng)答么?

答案肯定也不是,延遲應(yīng)答會(huì)有以下兩個(gè)限制:

  • 數(shù)量限制:每隔N個(gè)包就應(yīng)答一次
  • 時(shí)間限制:超過(guò)最大延遲時(shí)間就應(yīng)答一次(不能比超時(shí)重傳的時(shí)間長(zhǎng))

具體的數(shù)量和超時(shí)時(shí)間,依操作系統(tǒng)不同也有差異;一般N2,超時(shí)時(shí)間取200ms

2.15 捎帶應(yīng)答

  • 主機(jī)A給主機(jī)B發(fā)送了一條消息,當(dāng)主機(jī)B收到這條消息后需要對(duì)其進(jìn)行ACK應(yīng)答,但如果主機(jī)B此時(shí)正好也要給主機(jī)A發(fā)生消息,此時(shí)這個(gè)ACK就可以搭順風(fēng)車,而不用單獨(dú)發(fā)送一個(gè)ACK應(yīng)答,此時(shí)主機(jī)B發(fā)送的這個(gè)報(bào)文既發(fā)送了數(shù)據(jù),又完成了對(duì)收到數(shù)據(jù)的響應(yīng)
  • 這就是捎帶應(yīng)答,捎帶應(yīng)答也是為了提高傳輸效率

「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

TCP連接不直接保證可靠性,但是會(huì)間接保證可靠性

  • TCP連接確實(shí)不直接保證可靠性,但它通過(guò)一系列機(jī)制間接保證了數(shù)據(jù)的可靠傳輸。
  • 比如:序號(hào)和確認(rèn)機(jī)制、滑動(dòng)窗口、擁塞控制、流量控制等,這些機(jī)制是直接保障可靠性,
  • 這些機(jī)制建立的基礎(chǔ)是已經(jīng)連接已經(jīng)建立成功,所以TCP連接確實(shí)不直接保證可靠性,而是間接保證可靠性

以上便是TCP所有的策略

2.16 面向字節(jié)流

當(dāng)創(chuàng)建一個(gè)TCP的socket時(shí),同時(shí)在內(nèi)核中會(huì)創(chuàng)建一個(gè)發(fā)送緩沖區(qū)和一個(gè)接收緩沖區(qū)。

  • 調(diào)用write函數(shù)就可以將數(shù)據(jù)寫入發(fā)送緩沖區(qū)中,此時(shí)write函數(shù)就可以進(jìn)行返回了,接下來(lái)發(fā)送緩沖區(qū)當(dāng)中的數(shù)據(jù)就是由TCP自行進(jìn)行發(fā)送的。
  • 如果發(fā)送的字節(jié)數(shù)太長(zhǎng),TCP會(huì)將其拆分成多個(gè)數(shù)據(jù)包發(fā)出。如果發(fā)送的字節(jié)數(shù)太短,TCP可能會(huì)先將其留在發(fā)送緩沖區(qū)當(dāng)中,等到合適的時(shí)機(jī)再進(jìn)行發(fā)送。
  • 接收數(shù)據(jù)的時(shí)候,數(shù)據(jù)也是從網(wǎng)卡驅(qū)動(dòng)程序到達(dá)內(nèi)核的接收緩沖區(qū),可以通過(guò)調(diào)用read函數(shù)來(lái)讀取接收緩沖區(qū)當(dāng)中的數(shù)據(jù)。
  • 而調(diào)用read函數(shù)讀取接收緩沖區(qū)中的數(shù)據(jù)時(shí),也可以按任意字節(jié)數(shù)進(jìn)行讀取。

這個(gè)緩沖區(qū)在前面已經(jīng)詳細(xì)談過(guò)了,這里就不展開說(shuō)了

由于緩沖區(qū)的存在,TCP程序的讀和寫不需要一一匹配(面向字節(jié)流),例如:

寫100個(gè)字節(jié)數(shù)據(jù)時(shí),可以調(diào)用一次write寫100字節(jié),也可以調(diào)用100次write,每次寫一個(gè)字節(jié)。
讀100個(gè)字節(jié)數(shù)據(jù)時(shí),也完全不需要考慮寫的時(shí)候是怎么寫的,既可以一次read100個(gè)字節(jié),也可以一次read一個(gè)字節(jié),重復(fù)100次。
于TCP來(lái)說(shuō),它并不關(guān)心發(fā)送緩沖區(qū)當(dāng)中的是什么數(shù)據(jù),在TCP看來(lái)這些只是一個(gè)個(gè)的字節(jié)數(shù)據(jù),它的任務(wù)就是將這些數(shù)據(jù)準(zhǔn)確無(wú)誤的發(fā)送到對(duì)方的接收緩沖區(qū)當(dāng)中就行了,而至于如何解釋這些數(shù)據(jù)完全由上層應(yīng)用來(lái)決定,這就叫做面向字節(jié)流

比對(duì)面向數(shù)據(jù)報(bào)

  • 應(yīng)用層交付給UDP多長(zhǎng)的報(bào)文,UDP就原樣發(fā)送,既不會(huì)拆分,也不會(huì)合并,這就叫做面向數(shù)據(jù)報(bào)
  • 比如用UDP傳輸100個(gè)字節(jié)的數(shù)據(jù),發(fā)送端調(diào)用一次發(fā)送函數(shù),發(fā)送100字節(jié),那么接收端也必須調(diào)用對(duì)應(yīng)的一次接收函數(shù),接收100個(gè)字節(jié);如果發(fā)送端調(diào)用十次發(fā)送函數(shù),則接收端也必須調(diào)用對(duì)應(yīng)的十次接收函數(shù),即UDP協(xié)議,發(fā)送函數(shù)的次數(shù) : 接收函數(shù)的次數(shù) = 1 : 1

2.17 粘包問(wèn)題

什么是粘包?(基于TCP的應(yīng)用層問(wèn)題)

  • 首先要明確,粘包問(wèn)題中的“包”,是指的應(yīng)用層的數(shù)據(jù)包。
  • 在TCP的協(xié)議頭中,沒(méi)有如同UDP一樣的“報(bào)文長(zhǎng)度”這樣的字段。
  • 站在傳輸層的角度,TCP是一個(gè)一個(gè)報(bào)文過(guò)來(lái)的,按照序號(hào)排好序放在緩沖區(qū)中。
  • 但站在應(yīng)用層的角度,看到的只是一串連續(xù)的字節(jié)數(shù)據(jù)。
  • 那么應(yīng)用程序看到了這么一連串的字節(jié)數(shù)據(jù),就不知道從哪個(gè)部分開始到哪個(gè)部分,是一個(gè)完整的應(yīng)用層數(shù)據(jù)包。

如何解決粘包問(wèn)題

要解決粘包問(wèn)題,本質(zhì)就是要明確報(bào)文和報(bào)文之間的邊界。

  • 對(duì)于定長(zhǎng)的包,保證每次都按固定大小讀取即可。
  • 對(duì)于變長(zhǎng)的包,可以在報(bào)頭的位置,約定一個(gè)包總長(zhǎng)度的字段,從而就知道了包的結(jié)束位置。
  • 對(duì)于變長(zhǎng)的包,還可以在包和包之間使用明確的分隔符。因?yàn)閼?yīng)用層協(xié)議是程序員自己來(lái)定的,只要保證分隔符不和正文沖突即可。

對(duì)于UDP協(xié)議來(lái)說(shuō),是否也存在 “粘包問(wèn)題” ?

  • 對(duì)于UDP,如果還沒(méi)有上層交付數(shù)據(jù),UDP的報(bào)文長(zhǎng)度仍然在,同時(shí),UDP是一個(gè)一個(gè)把數(shù)據(jù)交付給應(yīng)用層的,有很明確的數(shù)據(jù)邊界。
  • 站在應(yīng)用層的角度,使用UDP的時(shí)候,要么收到完整的UDP報(bào)文,要么不收,不會(huì)出現(xiàn)“半個(gè)”的情況。

因此UDP是不存在粘包問(wèn)題的,根本原因就是UDP報(bào)頭當(dāng)中的16位UDP長(zhǎng)度記錄的UDP報(bào)文的長(zhǎng)度,因此UDP在底層的時(shí)候就把報(bào)文和報(bào)文之間的邊界明確了,而TCP存在粘包問(wèn)題就是因?yàn)門CP是面向字節(jié)流的,TCP報(bào)文之間沒(méi)有明確的邊界。

2.18 TCP異常情況

(1)進(jìn)程終止

當(dāng)客戶端與服務(wù)端已經(jīng)建立好連接了,如果客戶端進(jìn)程突然終止,此時(shí)建立好的連接會(huì)怎么樣?

  • 當(dāng)一個(gè)進(jìn)程退出時(shí),該進(jìn)程曾經(jīng)打開的文件描述符都會(huì)自動(dòng)關(guān)閉,因此當(dāng)客戶端進(jìn)程退出時(shí),相當(dāng)于自動(dòng)調(diào)用了close函數(shù)關(guān)閉了對(duì)應(yīng)的文件描述符,此時(shí)雙方操作系統(tǒng)在底層會(huì)正常完成四次揮手,然后釋放對(duì)應(yīng)的連接資源。
  • 也就是說(shuō),進(jìn)程終止時(shí)會(huì)釋放文件描述符,TCP底層仍然可以發(fā)送FIN,和進(jìn)程正常退出沒(méi)有區(qū)別。

(2)機(jī)器重啟

當(dāng)客戶端與服務(wù)端已經(jīng)建立好連接了:

  • 選擇重啟主機(jī)時(shí),操作系統(tǒng)會(huì)先殺掉所有進(jìn)程然后再進(jìn)行關(guān)機(jī)重啟,因此機(jī)器重啟和進(jìn)程終止的情況是一樣的,此時(shí)雙方操作系統(tǒng)也會(huì)正常完成四次揮手,然后釋放對(duì)應(yīng)的連接資源。

(3)機(jī)器掉電(斷電源)/網(wǎng)線斷開

當(dāng)客戶端與服務(wù)端已經(jīng)建立好連接了:一端突然斷電或斷網(wǎng)了

比如是客戶端斷電或斷網(wǎng)后,服務(wù)器端在短時(shí)間內(nèi)無(wú)法知道客戶端掉線了,因此在服務(wù)器端會(huì)維持與客戶端建立的連接,但這個(gè)連接也不會(huì)一直維持,因?yàn)門CP是有?;畈呗缘?。

  • 服務(wù)器會(huì)定期發(fā)送保活探測(cè)報(bào)文給客戶端,以檢測(cè)客戶端的存在狀況。如果連續(xù)多次都沒(méi)有收到客戶端的應(yīng)答,服務(wù)器就會(huì)認(rèn)為客戶端已經(jīng)掉線,并關(guān)閉這條連接。
  • 此外,客戶端也可以定期向服務(wù)器發(fā)送心跳消息,以確保服務(wù)器知道自己的存在。如果服務(wù)器長(zhǎng)時(shí)間沒(méi)有收到客戶端的心跳消息,也會(huì)認(rèn)為客戶端已經(jīng)掉線,并關(guān)閉對(duì)應(yīng)的連接。
  • 綜上所述,TCP通過(guò)?;钐綔y(cè)和心跳消息機(jī)制,間接地檢測(cè)客戶端的在線狀態(tài),并在一定時(shí)間內(nèi)關(guān)閉掉線的連接,以保證連接的有效性和資源的合理利用。

此外,應(yīng)用層的某些協(xié)議,也有一些類似的檢測(cè)機(jī)制,例如基于長(zhǎng)連接的HTTP,也會(huì)定期檢測(cè)對(duì)方的存在狀態(tài)。

2.19 TCP小結(jié)

為什么TCP這么復(fù)雜?

因?yàn)橐WC可靠性,同時(shí)又盡可能的提高性能

可靠性:

  • 檢驗(yàn)和
  • 序列號(hào)
  • 確認(rèn)應(yīng)答
  • 超時(shí)重傳
  • 連接管理
  • 流量控制
  • 擁塞控制

提高性能:

  • 滑動(dòng)窗口
  • 快速重傳
  • 延遲應(yīng)答
  • 捎帶應(yīng)答

除此之外,還有一些定時(shí)器(超時(shí)重傳定時(shí)器, ?;疃〞r(shí)器, TIME_WAIT定時(shí)器等)

2.20 基于TCP應(yīng)用層協(xié)議

常見的基于TCP的應(yīng)用層協(xié)議如下:

  • HTTP(超文本傳輸協(xié)議)
  • HTTPS(安全數(shù)據(jù)傳輸協(xié)議)
  • SSH(安全外殼協(xié)議)
  • Telnet(遠(yuǎn)程終端協(xié)議)
  • FTP(文件傳輸協(xié)議)
  • SMTP(電子郵件傳輸協(xié)議)

當(dāng)然,也包括你自己寫TCP程序時(shí)自定義的應(yīng)用層協(xié)議

2.21 TCP/UDP對(duì)比

TCP是可靠連接, 那么是不是TCP一定就優(yōu)于UDP呢?

TCP和UDP之間的優(yōu)點(diǎn)和缺點(diǎn),不能簡(jiǎn)單絕對(duì)的進(jìn)行比較,不存在誰(shuí)好誰(shuí)不好的問(wèn)題,他們只是應(yīng)用場(chǎng)景不同:

  • TCP用于可靠傳輸?shù)那闆r,應(yīng)用于文件傳輸,重要狀態(tài)更新等場(chǎng)景
  • UDP用于對(duì)高速傳輸和實(shí)時(shí)性要求較高的通信領(lǐng)域。例如,早期的QQ,視頻傳輸?shù)?,另外UDP可以用于廣播

歸根結(jié)底,TCP和UDP都是程序員的工具,什么時(shí)機(jī)用,具體怎么用,還是要根據(jù)具體的需求場(chǎng)景去判定

如何用UDP實(shí)現(xiàn)可靠傳輸

參考TCP的可靠性機(jī)制,在應(yīng)用層實(shí)現(xiàn)類似的邏輯,例如:

  • 引入序列號(hào), 保證數(shù)據(jù)順序
  • 引入確認(rèn)應(yīng)答,確保對(duì)端收到了數(shù)據(jù)
  • 引入超時(shí)重傳,如果隔一段時(shí)間沒(méi)有應(yīng)答,就重發(fā)數(shù)據(jù)

三、TCP實(shí)驗(yàn):理解listen的第二個(gè)參數(shù)

listen函數(shù)的作用是設(shè)置套接字為監(jiān)聽狀態(tài),該函數(shù)的第二個(gè)參數(shù)之前沒(méi)有談,現(xiàn)在來(lái)談一下
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)

第二個(gè)參數(shù)backlog:全連接隊(duì)列的最大長(zhǎng)度。

如果有多個(gè)客戶端同時(shí)發(fā)來(lái)連接請(qǐng)求,此時(shí)未被服務(wù)器處理的連接就會(huì)放入連接隊(duì)列,該參數(shù)代表的就是這個(gè)全連接隊(duì)列的最大長(zhǎng)度,一般不能設(shè)置太大

下面進(jìn)行做實(shí)驗(yàn):

該實(shí)驗(yàn)不進(jìn)行accept獲取_listensock套接字新連接,什么也不干,只進(jìn)行監(jiān)聽連接的到來(lái),backlog設(shè)置為2

tcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

using namespace std;

static const int gbacklog = 2; // 全連接隊(duì)列大小

// 錯(cuò)誤類型枚舉
enum
{
    UAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};
class tcpServer
{
public:
    tcpServer(const uint16_t &port)
        : _listensock(-1), _port(port)
    {}
    // 初始化服務(wù)器
    void initServer()
    {
        // 1.創(chuàng)建套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock == -1)
        {
            cout << "create socket error" << endl;
            exit(SOCKET_ERR);
        }
        // 1.1 設(shè)置地址復(fù)用
        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

        // 2.綁定端口
        // 2.1 填充 sockaddr_in 結(jié)構(gòu)體
        struct sockaddr_in local;
        bzero(&local, sizeof(local));       // 把 sockaddr_in結(jié)構(gòu)體全部初始化為0
        local.sin_family = AF_INET;         // 未來(lái)通信采用的是網(wǎng)絡(luò)通信
        local.sin_port = htons(_port);      // htons(_port)主機(jī)字節(jié)序轉(zhuǎn)網(wǎng)絡(luò)字節(jié)序
        local.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY 就是  0x00000000

        // 2.2 綁定
        int n = bind(_listensock, (struct sockaddr *)&local, sizeof(local)); // 需要強(qiáng)轉(zhuǎn),(struct sockaddr*)&local
        if (n == -1)
        {
            cout << "bind socket error" << endl;
            exit(BIND_ERR);
        }
        // 3. 把_listensock套接字設(shè)置為監(jiān)聽狀態(tài)
        if (listen(_listensock, gbacklog) == -1)
        {
            cout << "listen socket error" << endl;
            exit(LISTEN_ERR);
        }
    }
    // 啟動(dòng)服務(wù)器
    void start()
    {
        for (;;)
        {
            sleep(1); // 什么也不做,不從_listensock套接字里面獲取新連接
        }
    }
    ~tcpServer()
    {}
private:
    int _listensock; // listen套接字,不是用來(lái)數(shù)據(jù)通信的,是用來(lái)監(jiān)聽鏈接到來(lái)
    uint16_t _port;  // 端口號(hào)
};

tcpServer.cc

#include "tcpServer.hpp"
#include <memory>

// 使用手冊(cè)
// ./tcpServer port
static void Uage(string proc)
{
    cout << "\nUage:\n\t" << proc << " local_port\n\n";
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Uage(argv[0]);
        exit(UAGE_ERR);
    }

    uint16_t port = atoi(argv[1]); // string to int

    unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer(); // 初始化服務(wù)器
    tsvr->start();      // 啟動(dòng)服務(wù)器

    return 0;
}

編譯運(yùn)行服務(wù)器,此時(shí)啟動(dòng) 3 個(gè)客戶端同時(shí)連接服務(wù)器, 用 netstat 查看服務(wù)器狀態(tài), 一切正常
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
但是啟動(dòng)第四個(gè)客戶端時(shí), 發(fā)現(xiàn)服務(wù)器對(duì)于第四個(gè)連接的狀態(tài)存在問(wèn)題了
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
客戶端狀態(tài)正常, 但是服務(wù)器端出現(xiàn)了SYN_RECV狀態(tài), 而不是ESTABLISHED狀態(tài)

這是因?yàn)? Linux內(nèi)核協(xié)議棧為一個(gè)tcp連接管理使用兩個(gè)隊(duì)列:

  • 半連接隊(duì)列(用來(lái)保存處于SYN_SENTSYN_RECV狀態(tài)的請(qǐng)求)
  • 全連接隊(duì)列(accpetd隊(duì)列)(用來(lái)保存處established狀態(tài),但是應(yīng)用層沒(méi)有調(diào)用accept取走的請(qǐng)求)

這個(gè)全連接隊(duì)列不能太長(zhǎng),也不能沒(méi)有:

  • 如果全連接隊(duì)列過(guò)長(zhǎng),會(huì)導(dǎo)致服務(wù)器資源的浪費(fèi)。每個(gè)連接請(qǐng)求都會(huì)占用一定的內(nèi)存和其他資源,如果隊(duì)列過(guò)長(zhǎng),服務(wù)器可能無(wú)法及時(shí)處理所有的連接請(qǐng)求,導(dǎo)致資源耗盡和服務(wù)質(zhì)量下降
  • 相反,如果全連接隊(duì)列沒(méi)有長(zhǎng)度,也會(huì)導(dǎo)致問(wèn)題。如果服務(wù)器無(wú)法及時(shí)處理所有的連接請(qǐng)求,就會(huì)導(dǎo)致連接請(qǐng)求被丟棄,客戶端無(wú)法建立連接。這會(huì)導(dǎo)致客戶端的請(qǐng)求失敗和連接超時(shí),影響用戶體驗(yàn)和服務(wù)可用性。

而全連接隊(duì)列的長(zhǎng)度會(huì)受到 listen 第二個(gè)參數(shù)的影響

  • 全連接隊(duì)列滿了的時(shí)候,就無(wú)法繼續(xù)讓當(dāng)前連接的狀態(tài)進(jìn)入 established 狀態(tài)了
  • 這個(gè)全連接隊(duì)列的長(zhǎng)度通過(guò)上述實(shí)驗(yàn)可知,全連接的長(zhǎng)度是 listen 的第二個(gè)參數(shù) + 1

上述實(shí)驗(yàn),我們?cè)O(shè)置的全連接隊(duì)列大小是2,前三次連接正常,但是到了第四次連接的處于半鏈接隊(duì)列,處于了SYN_RECV狀態(tài)
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
但是在客戶端看來(lái),連接已經(jīng)建立好了,但是在服務(wù)端看來(lái)沒(méi)有建立連接成功,因?yàn)榉?wù)端對(duì)于第三次握手的ACK進(jìn)行了忽略
「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解],# 網(wǎng)絡(luò)編程,網(wǎng)絡(luò),tcp/ip,學(xué)習(xí)
TCP內(nèi)容真多,終于完結(jié)了,TCP寫了差不多三萬(wàn)字
--------------------- END ----------------------文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-618965.html

「 作者 」 楓葉先生
「 更新 」 2023.7.30
「 聲明 」 余之才疏學(xué)淺,故所撰文疏漏難免,
          或有謬誤或不準(zhǔn)確之處,敬請(qǐng)讀者批評(píng)指正。

到了這里,關(guān)于「網(wǎng)絡(luò)編程」傳輸層協(xié)議_ TCP協(xié)議學(xué)習(xí)_及原理深入理解(二 - 完結(jié))[萬(wàn)字詳解]的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包