发起断开连接请求可以是客户端也可以是服务器,即主机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
版权声明:本文为博主原创文章,转载请附上博文链接!