原因
TCP是一种有连接的协议,但是这个连接并不是指有一条实际的电路,而是一种虚拟的电路。TCP的建立连接和断开连接都是通过发送数据实现的,也就是我们常说的三次握手、四次挥手。TCP两端保存了一种数据的状态,就代表这种连接,TCP两端之间的路由设备只是将数据转发到目的地,并不知道这些数据实际代表了什么含义,也并没有在其中保存任何的状态信息,也就是说中间的路由设备没有什么连接的概念,只是将数据转发到目的地,只有数据的发送者和接受者两端真正的知道传输的数据代表着一条连接。
但是这就说明了一点,如果不发送数据那么是无法断开连接的。正常情况下当TCP的一端A调用了SOCKET的close或者进程结束,操作系统就会按照TCP协议发送FIN数据报文。B端收到后就会断开连接。但是当出现了上文所说的异常情况时:被拔掉网线或者断掉电源,总结起来就是没有机会发出断开的FIN数据报文。那么和A直连的路由设备虽然知道A设备已经断开了,但是路由设备并没有保存连接的状态信息,所以路由设备也就不可能去通知B端A端的断开。而B端没有收到断开的数据报文就会依然保持连接。所以A端拔掉网线或者断掉电源后B端是没办法收到断开连接的通知的。
游戏服务器常常有心跳包的设计。
我们的心跳包就是为了防止Socket断开连接,或是TCP的连接断开吗?
答案是否定的,TCP连接的通道是个虚拟的,连接的维持靠的是两端TCP软件对连接状态的维护。
TCP 连接自身有维护连接的机制,说白了就是自身有长时间没有数据包情况下的判断连接是否还存在的检测,清除死连接,即使在没有数据来往的时候,TCP也就可以(在启动TCP这个功能的前提下)自动发包检测是否连接正常,这个不需要我们处理。
服务端设计心跳包的目的:
探知对端应用是否存活,服务端客户端都可以发心跳包,一般都是客户端发送心跳包,服务端用于判断客户端是否在线,从而对服务端内存缓存数据进行清理(玩家下线等);问题在于,通过TCP四次握手断开的设定,我们也是可以通过Socket的read方法来判断TCP连接是否断开,从而做出相应的清理内存动作,那么为什么我们还需要使用客户端发送心跳包来判断呢?
第一种判断客户端是否在线策略:
直接监控TCP传输协议的返回值,通过返回值处理应用层的存活判断
比如在C++当中
使用poll的IO复用方法时:
if(fds[i].revents & POLLERR)
if(fds[i].events & POLLDHUP)
通过上述判断可以探知TCP连接的正确性从而在服务器也关闭对应的连接
close() on the file descriptor will release resources that are still being reserved on behalf of the socket. 此时调用close()函数才会释放相关的资源。
比如说进行如下处理:
/*如果客户端关闭连接,则服务器也关闭对应的连接,并将用户总数减1*/users[fds[i].fd] = users[fds[user_counter].fd];close(fds[i].fd);fds[i] = fds[user_counter];i--;user_counter--;printf("a client left\n");
又比如在Java中:
在Java的阻塞编程中:通过
ServerSocket ss = new ServerSocket(10021);
Socket so = ss.accept();// 获取相关流对象InputStream in = so.getInputStream();byte[] bytes = new byte[1024];int num = in.read(bytes)