了解一些边界条件,通过观察这些情形,弄清在网络层次发生什么以及它们如何反映到套接字api,这将更多地理解这些层次的工作原理,体会如何编写应用程序来处理这些情形。
//--------------------------------1.刚连接马上断开----------------------------------
当三TCP路握手完成从而连接建立之后,客户TCP却发送了RST,在服务器端看来,就在该连接已由TCP排队,等着服务器进程调用accept的时候RST到达。
如何处理这种中止的连接依赖于不同的实现,源自Berkley的实现完全在内核中处理中止的连接,服务器进程根本看不到。大多数SVR4实现则返回一个错误给服务器进程,作为accept的返回结果。
//--------------------------------2.服务器进程终止----------------------------------
验证一切正常后,找到服务器子进程的进程id,执行kill杀死子进程id,作为进程终止处理的部分工作,子进程中所有打开着的描述符都被关闭,这就导致向客户发送一个FIN,而客户TCP则响应以一个ACK,这就是TCP连接终止的工作的前半部分。
而客户阻塞在fgets调用上,等待从终端接收一行文本。于是键入另外一行文本,客户TCP接着把数据发送给服务器,TCP允许这么做,因为客户TCP接收到FIN只是表示服务器进程已经关闭了连接的服务器端,从而进程不再往其中发送任何数据而已(而非并非告知客户TCP服务器进程已经终止)。
当服务器TCP接收到来自客户的数据时,既然先前打开那个套接字的进程已经终止,于是响应一个RST(可通过tcpdump命令验证)。
接着的情况分为2种,得看时序了
1.因为客户在刚刚调用writen后,立即调用readline,并且由于之前TCP接收到的FIN(RST还没到),所以readline立即返回0,即受到未预期的EOF,于是以出错信息(server terminated prematurely,服务器过早终止)。
2.如果readline发生在收到RST之后,这时候客户既收到了FIN,又收到了RST,则readline返回一个ECONNRESET,即connection rest by peer,对方复位连接的错误。
//--------------------------------------3.SIGPIPE-------------------------------------------
当进程向某个已收到RST的套接字执行写操作时,内核该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止,但无论该进程是捕获该信号并从其信号处理函数返回还是简单忽略该信号,写操作都将返回EPIPE错误。第一次写操作引发收到RST,第二次引发SIGPIPE信号,写一个已经接收到FIN的套接字不成问题,但是写一个已经接收到RST的套接字则是个错误。
如果SIGPIPE信号出现时需要采取特殊措施,可能需要在日志文件中登记,那么就必须捕获该信号。
//------------------------------------4.服务器主机崩溃---------------------------------------
当服务器和客户端确认正常后,然后从网络上断开服务器主机,并在客户键入另外一行文本,这样也模拟了当客户发送数据时服务器主机不可达的情形,即建立连接后某些中间路由器不工作的情形。
客户writen后,随后阻塞于readline的调用,如果用tcpdump观察网络就会发现,客户TCP持续重传数据分节,试图从服务器上接收一个ACK。源自Berkley的实现重传该数据分节12次,共等待约9分钟才放弃重传。当客户TCP最后终于放弃时,给客户进程返回一个错误。既然客户阻塞在readline函数调用上,那么该调用将返回一个错误。假设服务器追已崩溃,从而对客户的数据分节根本没有响应,那么所返回的错误时是ETIMEDOUT,然后如果某个中间路由器判定服务器主机已不可达,从而响应一个destination unreachable的ICMP消息,那么所返回的错误是EHOSTUNREACH或ENETUNREACH。
尽管我们客户最终还是可以发现对端主机已经崩溃或者不可达,不过有时候我们需要比不得不等待9分钟更快地检测出这种情况。所用的方法就是对readline调用设置一个超时。
//------------------------------------5.服务器崩溃后重启----------------------------------------
如上的情况下,如果在服务器主机崩溃时客户不主动给服务器发送数据,那么客户将不会知道服务里主机已经崩溃(没使用KEEP_ALIVE套接字心跳包选项)。
连接建立正常后,服务器主机崩溃并重启,接着在客户上键入一行文本,它将作为一个TCP数据分节发送到服务器主机。
当服务器主机崩溃后重启时,它的TCP丢失了崩溃前的所有连接信息,因此服务器TCP对于所收到的来自客户的数据分节响应以一个RST,当客户TCP收到该RST时,客户正阻塞于readline调用,导致该调用返回ECONNRESET错误。
//----------------------------------6.服务器主机关机----------------------------------------
当服务器进程正在运行时,服务器主机被操作员关机时将会发生什么呢,Unix 系统关机时,init进程通常先给所有进程发送SIGTERM信号(该信号可被捕获),等待一段固定时间(往往是5~20秒之间),然后给所有仍在运行的进程发送SIGKILL信号(该信号不能被捕获)。这么做留给所有运行的进程一小段的时间来清除和终止。如果我们不捕获SIGTERM信号并终止,我们的服务器将由SIGKILL信号终止。接下来和第2条一样了。

本文探讨了网络编程中常见的边界条件及其处理方式,包括连接突然断开、服务器进程终止、服务器主机崩溃等情形,深入剖析了这些场景下TCP/IP协议的行为特征及套接字API的响应机制。

被折叠的 条评论
为什么被折叠?



