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 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/qun_y/article/details/85259157
今日推荐