6 要认识到TCP是一个可靠的,但并不绝对可靠的协议
6.1 可靠性是啥
-
第一个可以讨论确保可靠传输问题的地方就是应用程序B所在主机的tcp层。当一个段抵达应用程序B所在主机的tcp层时,唯一可以确认的就是这个段已经到达了,但它可能损坏了,可能是重复的数据,可能是错序的,或者是由于其他一些原因无法接受的。注意,发送端tcp无法对这些抵达接收端tcp的段作出任何保证。
但接收端tcp要像发送端tcp确认,也就是说它ACK的数据以及在此数据之前到达的所有数据在tcp层都已经正确接收了,发送端tcp可以安全的删除这些数据的副本了。但是这并不意味着已经将数据传送,或者总是可以将数据传送给应用程序。比如,接收端主机可能在刚刚对数据进行了ACK,但应用程序还没有将其读取之前,就崩溃了。 -
另一个可以讨论确保可靠传输问题的地方就是应用程序B。我们知道,无法保证应用程序A发送的所有数据都会到达。tcp能够向应用程序B保证的是所有到达的数据都是按序且未受损的(未受损只是没有因特网校验和能够检测的错误,不能保证每个比特位都对的)
6.2 故障模式
- 网络中断
- 路由器或骨干链路损毁,在端点(主机)之外发生的损毁通常是临时性的,因为路由协议会发现问题,并使路由绕开出问题的节点。端点出问题时,通常没有备份的路径,所以问题会一直存在,直到端点修复为止。
- 中间路由器可能会向源端主机发送一条ICMP报文,说明目的网络或主机不可达,这种情况,有些实现会返回ENETUNREACH或EHOSTUNREACH。否则应用程序及其tcp/ip栈都无法立即获取中断的发生,这种情况下,发送端会最终超时,并重新发送多有未被确认的段。在发送端tcp放弃发送,丢弃连接并报告错误之前会一直持续这种操作,在传统的BSD栈中,发送端tcp会重传12次(大约9分钟)之后丢弃。如果读操作被挂起,会返回一条错误状况,并将error置为ETIMEDOUT。如果没有挂起的读操作,接下来的写操作就会失败,根据信号是忽略还是捕获,写操作失败时会携带一个SIGPIPE信号,或EPIPE错误。
- 对等实体崩溃
FIN是由TCP确认,而后让应用层read返回0
服务端回复RST,客户端再调read时,内核返回ECONNRESER
-
这种情况下,对等实体的tcp都会向我们的tcp发送FIN。FIN作为EOF使用,表示发送它的那一端已经没有数据发送了。
-
对等方崩溃和对等方调用close和exit是可不区分,在这两种情况下,对等方的TCP都是发送一个FIN消息给我们的TCP,FIN消息可以充当EOF,指示发送方没有任何数据可以发送了。如果此时有读操作挂起会立刻返回0,如果有写操作挂起那么会导致写操作成功,但是会导致对等方发送一个RST报文重置连接,那么当再次有写操作的时候, 应用程序会收到SIGPIPE信号,或者如果信号被捕获或忽略了,就会收到EPIPE错误。
服务器崩溃的时间线:
- 向服务端发送一条报文,在服务端处理过程中,杀死服务进程,这种情况,客户端会立即接收到错误消息,说明服务器已经终止了。(收到FIN时,客户端阻塞在对read的调用,而tcp只要让read返回0就可以立即通知客户端)
客户端读操作挂起时服务器崩溃:
- 对等实体的主机崩溃
-
这种情况,对等实体的tcp无法通过FIN通知我们的应用程序了,在对等实体重启之前,我们的应用程序tcp会持续传输未经确认的段,最终,如果对等实体主机没有重启,他就会放弃并向应用程序返回一条ETIMEDOUT错误。
-
如果在我们的tcp放弃并丢弃连接之前,对等实体主机重启了,这种情况,tcp技术规范要求接收端主机向发送端主机回送一个RST,这样发送端主机就会丢弃连接,应用程序会收到一条ECONNERESET错误(挂起读操作)。或者会在下一条写操作时,应用程序会收到SIGPIPE信号,或者如果信号被捕获或忽略了,就会收到EPIPE错误。