Server端如何感知客户端的状态
如果网络拥塞严重,chatserver端如何感知客户端在线还是掉线了?
客户端主动发送close(fd),相当于TCP的四次挥手,发送FIN包,进行挥手操作,对应服务端,就有一个响应:recv=0(判断客户端掉线,下线了),
这是我们在局域网的聊天服务器里面,网络是良好的,是不会出现问题的。
但是,如果放在真实的网络环境中,这个客户端发送的FIN包有可能服务器根本收不到,因为真实的网络环境是非常非常复杂的,有可能现在我们的客户端到服务端的中间网络节点的路由器里面的报文非常多,网络拥塞非常严重,导致FIN包到达不了服务端。
TCP协议下,发送的每一个包都会去等待这个包的响应,如果没有响应,会超时重传,但是超时重传是有一定的次数,如果超过次数,TCP就会reset把socket重置了,但是还是导致这个FIN包最终还是没有到达服务端,导致这个客户端已经下线了,但是服务端并没有感知到,客户端已经自己重置了,服务端却一直以为这个客户端在线,把这个客户端所给的socketfd没有释放,相关的其他资源也没有释放,就积累越来越多的僵客户端连接。
解决方法
心跳机制!!!
比如说,服务端(listen 8080),是TCP的端口,当然,不同的协议可以绑定在同一个端口,listen 8080是专门处理业务端口,接收我们发的json数据处理业务。
UDP 8080 是心跳业务处理
可以这样设计,服务端会给每一个connect成功的客户端分配1个心跳计数:
比如说:
服务端会启动一个通用定时器(心跳计数器),比如说:超时1秒,把所有客户的心跳计数加1,每超过1秒,就加1
如果客户的心跳计数超过5了,就判断客户端已经掉线了。
每一个客户端每隔1秒都会去发了一个心跳消息,给所属的心跳计数减1
也就是说,服务端在监听1个客户端,5秒之内都没有任何心跳的话,就判断这个客户端下线了,就拆除这个客户端所有的连接以及其他资源。客户端都是通过TCPsocket来和服务端进行通信,心跳为了不干扰我们的业务处理,专门用UDPsocket绑定8080端口,或者我们可以绑定其他的心跳端口,专门接收心跳消息,
当然,客户端发送的心跳消息要包括:userid:zhangsan1MessageType:heartbeat,服务端接收到这个心跳消息,就把zhangsan1对应的心跳计数减1,以此类推。正常,客户端的对应的心跳计数应该在-1,0,1之间不断的变化
如果客户端实时的发送心跳消息,服务端维护的这个客户端的心跳计数是不会超过5的,这样的机制可以预防网络拥塞严重,客户端的消息已经无法到达服务端了,这个消息自然也就无法到达服务端,这样一来,服务端维护的相应客户端的心跳计数,每超过1秒,没有收到心跳消息,就给心跳计数加1,加到超过5了,就认为这个客户端掉线了,说明此时的网络情况是非常非常的差,可以认为客户端不在线,因为客户端的消息过不来服务端,也有可能是发生了网络风暴,导致消息环回,到不了服务端了。
一般来说,C/S这种基于长连接的业务,都会在业务层实现心跳保持机制,用于监测对端是否依然在线
我们上面所说的是在应用层业务上自己实现的心跳机制。
在传输层,TCP协议有一个keepalive功能,保活功能。
因为TCP协议有一个缺陷,所以需要加上这个功能来弥补它的缺陷。
这个缺陷是:
TCP客户端和服务端连接成功了以后,如果客户端和服务端不交互,不发信息的话,一直连接链路,保持空闲,那么对于服务端来说,到底怎么去理解客户端的状态呢?客户端是在线,连接一直建立着,是客户端一直没有发消息,还是客户端掉线了?由于网络问题,客户端没有告诉服务端,服务端得不到通知,就像我们打电话一样,突然,都保持沉默了,你也不知道对方是掉线了还是没有说话,对于服务端来说,没有办法去感知所谓的事件创建的连接,当它里面一直沉默的话,到底这个连接还是不是有效的。
所以,TCP协议里,增加了keepalive这个功能:是在传输层添加的这个功能。和应用层没有关系。
有一些参数:
keepalive功能默认是关闭的,当我们去创建TCP的listen socket的时候,在这里,如果我们想启动keepalive功能,我们需要通过setsockopt来打开它的SO_KEEPALIVE,
它做的事情是:
net.ipv4.tcp_keepalive_time=7200:默认每隔2个小时,会发送一个空的报文段,探测对方是否在线:如果对方回复了,证明对方还在线,链路还是有效的。如果对方并没有响应,
net.ipv4.tcp_keepalive_invl=75:如果探测没有响应,延迟75秒继续发送保活的探测包。
net.ipv4.tcp_keepalive_probes=9:如果依旧是没有响应的话,最多重新探测9次,如果都没有响应,就拆除连接!!!证明这个连接确实是挂掉了。
也就是最多等待2小时+75x9 秒,如果一直没有响应,就把连接给拆除了
这个参数是可以更改的,可以把时间调小一点。
如果我们是靠TCP的keepalive来实现保活功能,在这里,如果连接中断的话,因为这是在传输层,只能把TCP的连接拆除,但是我们在应用层,一个聊天服务端检测到一个聊天客户端下线,仅仅是关闭sockfd???在我们的业务上,连接还记录着对应的connection对象存储在map表,我们都得删除啊!!!其次,因为这个keepalive是在传输层的,传输层的上面才是我们的应用层,假设我们应用层已经运行到死锁了,出问题了,已经做不了业务了,但是这只是应用层的死锁,传输层是属于操作系统内核的,内核是不会死锁的,socket在内核中依然可以和服务端发送keepalive探测包的,这就出问题了,导致服务端还是认为客户端在线的!!!