1. 程式人生 > >TCP四次揮手和伺服器主動斷開

TCP四次揮手和伺服器主動斷開

發起斷開連線請求可以是客戶端也可以是伺服器,即主機1,主機2可以是客戶端也可以是伺服器。 

ACK : TCP協議規定,只有ACK=1時有效,也規定連線建立後所有傳送的報文的ACK必須為1。 
FIN (finis)即完,終結的意思, 用來釋放一個連線。當 FIN = 1 時,表明此報文段的傳送方的資料已經發送完畢,並要求釋放連線。 
傳送序列號:Sequence Number 
確認序列號:Acknowledgment Number

FIN_WAIT_1:表示等待對方的FIN報文。當SOCKET在ESTABLISHED狀態時,它想主動關閉連線,向對方傳送了FIN報文,此時該SOCKET進入到FIN_WAIT_1 狀態 

FIN_WAIT_2:也表示等待對方的FIN報文。FIN_WAIT_2狀態下的SOCKET,表示半連線,也即有一方要求close連線,但另外還告訴對方,我暫時還有點資料需要傳送給你,稍後再關閉連線。 

CLOSE_WAIT: 這種狀態的含義其實是表示在等待關閉。你回覆一個ACK給對方,並進入CLOSE_WAIT狀態。接下來就是檢視你是否還有資料要傳送給對方,如果沒有,就可以close這個socket,併發送FIN給對方,即關閉連線。 

CLOSING:表示主機1給主機2傳送FIN後,並沒有收到主機2迴應的ACK,而收到了主機2傳送的FIN。表示雙方同時close一個socket,出現同時傳送FIN現象。 

LAST_ACK: 傳送FIN報文後,等待對方的ACK報文,當收到ACK報文後,進入到CLOSED狀態。 

TIME_WAIT: 表示收到了對方的FIN報文,併發送出了ACK確認,等2MSL後即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標誌和ACK標誌的報文時,可以直接進入到TIME_WAIT狀態。

第一次揮手:主機1向主機2,傳送FIN報文段,表示關閉資料傳送,並主機1進入FIN_WAIT_1狀態,表示沒有資料要傳輸了。

 第二次揮手:主機2收到FIN報文段後進入CLOSE_WAIT狀態(被動關閉),然後傳送ACK確認,表示同意你關閉請求了,主機到主機的資料鏈路關閉,主機進入FIN_WAIT_2狀態 。

第三次揮手:主機2等待主機1傳送完資料,傳送FIN到主機1請求關閉,主機2進入LAST_ACK狀態 。

第四次揮手:主機1收到主機2傳送的FIN後,回覆ACK確認到主機2,主機1進入TIME_WAIT狀態。主機2收到主機1的ACK後就關閉連線了,狀態為CLOSED。主機1等待2MSL,仍然沒有收到主機2的回覆,說明主機2已經正常關閉了,主機1關閉連線。

孤兒連線 
連續停留在FIN_WAIT2狀態可能發生,客戶端執行半關閉狀態後,未等伺服器關閉連線就直接退出了,此時客戶端連線由核心接管。Linux為防止孤兒連線長時間存在核心中,定義了兩個變數指定孤兒連線數目和生存時間。

為什麼要四次揮手而不是三次
當主機2傳送ACK確認主機1的FIN時,並不代表主機2的資料傳送完畢,主機1傳送完FIN處於半關閉狀態(不能傳送資料,但可以接收資料),所以要等待主機2的資料傳送完畢後,發出FIN關閉連線請求時,主機2才進入CLOSED狀態,主機1再ACK確認關閉進入CLOSE狀態。

為什麼TIME_WAIT 狀態要等2MSL才進入CLOSED狀態 
MSL(Maximum Segment Lifetime):報文最大生存時間,是任何報文段被丟棄前在網路內的最長時間。當主機1回覆主機2的FIN後,等待(2-4分鐘),即使兩端的應用程式結束。

如果主機1直接進入CLOSED狀態,由於IP協議不可靠性或網路問題,導致主機1最後發出的ACK報文未被主機2接收到,那麼主機2在超時後繼續向主機1重新發送FIN,而主機1已經關閉,那麼找不到向主機1傳送FIN的連線,主機2這時收到RST並把錯誤報告給高層,不符合TCP協議的可靠性特點。

 如果主機1直接進入CLOSED狀態,而主機2還有資料滯留在網路中,當有一個新連線的埠和原來主機2的相同,那麼當原來滯留的資料到達後,主機1認為這些資料是新連線的。等待2MSL確保本次連線所有資料消失。

TIME_WAIT狀態過多會佔用大量的埠號,處理方法: 
修改核心引數 
儘可能被動關閉連線 
將長連線改為短連線

close和shutdown 
只要TCP棧的讀緩衝裡還有未讀取(read)資料,則呼叫close時會直接向對端傳送RST 
close把描述字的引用計數減1,僅在該計數變為0的時候才關閉套介面。而使用shutdown可以不管引用計數的值是多少都能激發TCP的正常連線終止序列,即傳送FIN。 
close終止資料傳送的兩個方向讀和寫。 
shutdown函式進行關閉某一方向的操作。比如,有時我們只是需要告訴對方資料傳送完畢,只需要關閉資料傳送的一個通道,但還是要接受對方發過來的資料。
 

服務端主動斷開:心跳包

TCP socket心跳機制中,心跳包可以由伺服器傳送給客戶端,也可以由客戶端傳送給伺服器,不過比較起來,前者開銷可能更大。

 這裡實現的是由客戶端給伺服器傳送心跳包,基本思路是:

1、伺服器為每個客戶端儲存了IP和計數器count,即map<fd, pair<ip, count>>。服務端主執行緒採用 select 實現多路IO複用,監聽新連線以及接受資料包(心跳包),子執行緒用於檢測心跳:

如果主執行緒接收到的是心跳包,將該客戶端對應的計數器 count 清零;
在子執行緒中,每隔3秒遍歷一次所有客戶端的計數器 count: 
若 count 小於 5,將 count 計數器加 1;
若 count 等於 5,說明已經15秒未收到該使用者心跳包,判定該使用者已經掉線。

2、 客戶端則只是開闢子執行緒,定時給伺服器傳送心跳包(本示例中定時時間為3秒)。

Linux下一個socket心跳包的簡單實現:

#define BUFFER_SIZE 1024
enum Type {HEART, OTHER};
struct PACKET_HEAD 
{
    Type type;
    int length;
};

void* heart_handler(void* arg);

class Server 
{
private:
    struct sockaddr_in server_addr;
    socklen_t server_addr_len;
    int listen_fd;       
    int max_fd;         
    fd_set master_set;     
    fd_set working_set; 
    struct timeval timeout;
    map<int, pair<string, int> > mmap;   
public:
    Server(int port);
    ~Server();
    void Bind();
    void Listen(int queue_len = 20);
    void Accept();
    void Run();
    void Recv(int nums);
    friend void* heart_handler(void* arg);
};

Server::Server(int port) 
{
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(port);
    
    listen_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(listen_fd < 0) 
    {
        exit(1);
    }
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}

Server::~Server() 
{
    for(int fd=0; fd<=max_fd; ++fd) 
    {
        if(FD_ISSET(fd, &master_set)) 
        {
            close(fd);
        }
    }
}

void Server::Bind() 
{
    if(-1 == (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))) 
    {
        exit(1);
    }
}

void Server::Listen(int queue_len) 
{
    if(-1 == listen(listen_fd, queue_len)) 
    {
        exit(1);
    }
}

void Server::Accept() 
{
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int new_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
    if(new_fd < 0) {
        exit(1);
    }

    string ip(inet_ntoa(client_addr.sin_addr));    
    mmap.insert(make_pair(new_fd, make_pair(ip, 0)));

    FD_SET(new_fd, &master_set);
    if(new_fd > max_fd) 
    {
        max_fd = new_fd;
    }
}   

void Server::Recv(int nums) 
{
    for(int fd=0; fd<=max_fd; ++fd) 
    {
        if(FD_ISSET(fd, &working_set)) 
        {
            bool close_conn = false;  
            PACKET_HEAD head;
            recv(fd, &head, sizeof(head), 0);   
            if(head.type == HEART) 
            {
                mmap[fd].second = 0;        
            } 
            else 
            {
                // 資料包,通過head.length確認資料包長度 
            }   

            if(close_conn) 
            {
                close(fd);
                FD_CLR(fd, &master_set);
                if(fd == max_fd)  
                {
                    while(FD_ISSET(max_fd, &master_set) == false)
                        --max_fd;
                }
            }
        }
    }   
}

void Server::Run() 
{
    pthread_t id;    
    int ret = pthread_create(&id, NULL, heart_handler, (void*)this);
    if(ret != 0) {
        cout << "Can not create heart-beat checking thread.\n";
    }

    max_fd = listen_fd;   
    FD_ZERO(&master_set);
    FD_SET(listen_fd, &master_set);  

    while(1) 
    {
        FD_ZERO(&working_set);
        memcpy(&working_set, &master_set, sizeof(master_set));

        timeout.tv_sec = 30;
        timeout.tv_usec = 0;

        int nums = select(max_fd+1, &working_set, NULL, NULL, &timeout);
        if(nums < 0) {
            exit(1);
        }

        if(nums == 0) {
            continue;
        }

        if(FD_ISSET(listen_fd, &working_set))
            Accept();  
        else
            Recv(nums); 
    }
}

void* heart_handler(void* arg) 
{
    Server* s = (Server*)arg;
    while(1) 
    {
        map<int, pair<string, int> >::iterator it = s->mmap.begin();
        for( ; it!=s->mmap.end(); ) 
        {   
            if(it->second.second == 5) 
            {
                int fd = it->first;
                close(fd);            
                FD_CLR(fd, &s->master_set);
                if(fd == s->max_fd) 
                {
                    while(FD_ISSET(s->max_fd, &s->master_set) == false)
                        s->max_fd--;
                }
                s->mmap.erase(it++);  
            }
            else if(it->second.second < 5 && it->second.second >= 0) 
            {
                it->second.second += 1;
                ++it;
            } 
            else 
            {
                ++it;
            }
        }
        sleep(3);   
    }
}

--------------------- 
來源:CSDN 
原文:https://blog.csdn.net/lisonglisonglisong/article/details/51327695 
           https://blog.csdn.net/qq_34501940/article/details/51119726 
版權宣告:本文為博主原創文章,轉載請附上博文連結!