TCP 是可靠的?
发送端通过调用send函数之后,数据流并没有传输出去,数据会先存储到套接字的发送缓冲区中,通过网络协议栈决定何时发送、如何发送。当对应的数据发送给接收端,接收端回应ACK,这时存储到发送缓冲区的数据可以删除了,但发送端无法获取对应数据流的ACK情况(无法判断对端是否接收到数据流),如果想知道,必须在应用层添加处理逻辑,例如显示的报文确认机制。
从接收端来看,也不能保证ACK过的数据被应用程序处理,因为数据需要从接收端拷贝,可能发生接收过程中程序突然崩溃。
所以,TCP协议没有提供给上层应用程序过多的异常处理细节,反映链路异常的能力弱。(无人值守,自我恢复)
TCP连接之后,能感知链路方式只有read和write操作。
故障模式总结
2大类
1.网络中断造成的对端无FIN包
网络中断,TCP程序不能及时感到异常信息,除非路由器发出ICMP报文,说明目的网络或主机不可达,这个时候通过read或write调用就会返回Unereachable错误
如果没有ICMP报文的情况下,TCP程序不能理解应到连接异常,如果程序阻塞到read调用上,程序将无法恢复正常。不过可以给read操作设置超时来解决。
如果先调用write发送数据流,接下来阻塞在read调用上。TCP协议栈将发送缓冲区的数据发出去,大概重传12次、合计时间为9分钟,协议栈标识会连接异常,这是阻塞的read调用会返回TIMEOUT错误信息,如果程序还往这条连接写数据,写操作会立即失败,返回一个SIGPIPE信号给应用程序。
系统崩溃造成对端无FIN包
系统崩溃,比如断电,网络连接上来不及发出任何东西。这个和通过系统杀死应用程序不同的是,没有FIN包发出来。
情况1:没有ICMP报文情况下,TCP程序只能通过read和write调用得到网络连接异常信息,常见的是超时错误。
情况2:系统崩溃之后又重启,当重传的TCP分组到达重启后的系统,由于没有TCP分组对应的连接数据,系统会返回一个RST重置分节,TCP程序通过read和write调用可以对RST进行错误处理。
如果是阻塞read调用,会立即返回一个错误,错误为连接重置(Connection Reset)
如果是一次write操作,会立即失败,应用程序会返回一个SIGPIPE信号
对端有FIN包发出
如果有FIN包发出,可能场景是对端调用了close或shutdown显示关闭连接,也可能是对端应用程序崩溃,操作系统内核代为清理所发出的,从应用程序角度来看,无法区分是那种情形。
阻塞的read操作完成数据读取之后,FIN包会通过一个EOF来完成通知,此时,read调用返回值为0。read收到FIN包之后不会立即返回,他会往接收缓冲区放置一个EOF符号,之前已经在接收缓冲区的有效数据不会受到影响。
向一个已关闭连接连续写,最终导致 SIGPIPE
如果在服务端读取数据并处理过程中,突然杀死服务器进程,我们会看到客户端很快也会退出,并在屏幕上打印出“Connection reset by peer”的提示。
总结
TCP异常分两大类:一类是对端无FIN包,需要通过巡检或超时来发现;
另一类是对端无FIN包,需要通过巡检或超时来发现,另一类是对端有FIN包发出,需要通过增强read或write操作异常处理,帮助发现异常。