以服务器一方发起断开过程为例。
首先,服务器一方的应用程序会调用Socket库的 close 程序。然后,服务器的协议栈会生成包含 断开信息的TCP头部 ,将控制位中的FIN比特设为1。接下来,协议栈会委托IP模块向客户端发送数据。同时,服务器的套接字中也会记录下断开 *** 作的相关信息。
当客户端收到服务器发来的FIN为1的TCP头部时,客户端的协议栈会 将自己的套接字标记为进入断开 *** 作状态 。然后,为了告知服务器已收到FIN为1的包,客户端会向服务器返回一个ACK号。 这些 *** 作完成后,协议栈就可以等待应用程序来取数据 了。应用程序就会调用read来读取数据。这时,协议栈不会向应用程序传递数据,而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。
客户端应用程序会调用 close 来结束数据收发 *** 作,这时客户端的协议栈也会和服务器一样,生成一个FIN比特为1的TCP包,然后委托IP模块发送给服务器。一段时间之后,服务器就会返回ACK号。到这里,客户端和服务器的通信就全部结束了。
和服务器的通信结束之后,用来通信的套接字就可以删除了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。
客户端先发起断开,则断开的顺序如下:
1)客户端发送FIN
2)服务器返回ACK号
3)服务器发送FIN
4)客户端返回ACK号
如果最后客户端返回的ACK号丢失了,结果会如何呢?这时,服务器没有接收到ACK号,可能会重发一次FIN。
如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。
这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号,而服务器重发的FIN正好到达,会怎么样呢?本来这个FIN是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个FIN就会错误地跑到新套接字里面,新套接字就开始执行断开 *** 作了。之所以不马上删除套接字,就是为了防止这样的误 *** 作。
至于具体等待多长时间, 这和包重传的 *** 作方式有关 。网络包丢失之后会进行重传,这个 *** 作通常要持续几分钟。
如果重传了几分钟之后依然无效,则停止重传。在这段时间内,网络中可能存在重传的包,也就有可能发生前面讲到的这种误 *** 作,因此需要等待到重传完全结束。协议中对于这个等待时间没有明确的规定,一般来说会等待几分钟之后再删除套接字。
本文摘取自周自恒翻译的户根勤编写的《网络是怎样连接的》可能是你没有处理好关闭连接,服务器程序如果出错退出,或者退出时没进行断开客户端的 *** 作,会造成客户端不知道服务器已停止工作,而继续保持虚连接,造成重连失效。
建议完善服务器程序设计,在服务器退出前,增加关闭所有客户端连接,并收回socket的 *** 作。一方意外中断另一方出现异常是肯定的啊,因为TCP要求是稳定的连接。
不报错是不可能的,只能是选择报错后整个进程都报错退出掉,还是忽略掉这个错误继续等待新的连接建立。
用 try - catch 结构就好了。法一: 当recv()返回值小于等于0时,socket连接断开。但是还需要判断 errno是否等于 EINTR,如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的,不应close掉socket连接。
法二:
struct tcp_info info;
int len=sizeof(info);
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t )&len); if((infotcpi_state==TCP_ESTABLISHED)) 则说明未断开 else 断开
法三: 若使用了select等系统函数,若远端断开,则select返回1,recv返回0则断开。其他注意事项同法一。
法四:
int keepAlive = 1; // 开启keepalive属性
int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数如果第1次探测包就收到响应了,则后2次的不再发
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void )&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void )&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void )&keepCount, sizeof(keepCount));
设置后,若断开,则在使用该socket读写时立即失败,并返回ETIMEDOUT错误
法五: 自己实现一个心跳检测,一定时间内未收到自定义的心跳包则标记为已断开。 参考:>TCP 是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本节将详细讨论一个TCP 连接是如何建立的以及通信结束后是如何终止的。
建立一个 TCP 连接
TCP使用三次握手 ( three-way handshake ) 协议来建立连接,图 3-10 描述了三次握手的报文序列。这三次握手为:
请求端(通常称为客户)发送一个 SYN 报文段( SYN 为 1 )指明客户打算连接的服务器的端口,以及初始顺序号( ISN )。
服务器发回包含服务器的初始顺序号的 SYN 报文段( SYN 为 1 )作为应答。同时,将确认号设置为客户的 ISN 加 1 以对客户的 SYN 报文段进行确认( ACK 也为 1 )。
客户必须将确认号设置为服务器的 ISN 加 1 以对服务器的 SYN 报文段进行确认( ACK 为 1 ),该报文通知目的主机双方已完成连接建立。
发送第一个 SYN 的一端将执行主动打开( active open ),接收这个 SYN 并发回下一个 SYN 的另一端执行被动打开( passive open )。另外, TCP 的握手协议被精心设计为可以处理同时打开( simultaneous open ),对于同时打开它仅建立一条连接而不是两条连接。因此,连接可以由任一方或双方发起,一旦连接建立,数据就可以双向对等地流动,而没有所谓的主从关系。
三次握手协议是连接两端正确同步的充要条件。因为 TCP 建立在不可靠的分组传输服务之上,报文可能丢失、延迟、重复和乱序,因此协议必须使用超时和重传机制。如果重传的连接请求和原先的连接请求在连接正在建立时到达,或者当一个连接已经建立、使用和结束之后,某个延迟的连接请求才到达,就会出现问题。采用三次握手协议(加上这样的规则:在连接建立之后 TCP 就不再理睬又一次的连接请求)就可以解决这些问题。
三次握手协议可以完成两个重要功能:它确保连接双方做好传输准备,并使双方统一了初始顺序号。初始顺序号是在握手期间传输顺序号并获得确认:当一端为建立连接而发送它的 SYN 时,它为连接选择一个初始顺序号;每个报文段都包括了顺序号字段和确认号字段,这使得两台机器仅仅使用三个握手报文就能协商好各自的数据流的顺序号。一般来说, ISN 随时间而变化,因此每个连接都将具有不同的 ISN 。
关闭一个 TCP 连接
TCP 连接建立起来后,就可以在两个方向传送数据流。当 TCP 的应用进程再没有数据需要发送时,就发关闭命令。 TCP 通过发送控制位 FIN=1 的数据片来关闭本方数据流,但还可以继续接收数据,直到对方关闭那个方向的数据流,连接就关闭。
TCP 协议使用修改的三次握手协议来关闭连接, 如图 3-11 所示,即终止一个连接要经过 4 次握手。这是因为 TCP 的半关闭( half-close )造成的。由于一个 TCP 连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。关闭的原则就是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接。当一端收到一个 FIN ,它必须通知应用层另一端已经终止了那个方向的数据传送。发送 FIN 通常是应用层进行关闭的结果。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)