以前用perl写的一个网络程序,最近运行的时候频繁出错,tcpdump抓包分析的结果是连接没能正常关闭,因为连接是用RST中止的。
review了n次代码,除了正常的socket调用外,并没有显式设置SO_LINGER等socket选项;为了防止perl语言在后台偷偷地做些地下操作,又用strace跟踪了脚本的系统调用,同样也并没有发现任何异常;接着又用C语言重写了脚本逻辑,现象和perl的版本完全一致。排除了语言的差异,只能从操作系统着手分析了。
虽然Linux内核源码就在手边,但是不到万不得已,决不轻易碰触,看来今天是在所难免了。翻阅了TCP包接收部分的代码,没有发现任何异样。再次没了方向,联想到RST抢占的是FIN的位置,因此不妨从tcp_close入手分析,在其中发现可疑注释和代码:
/* As outlined in draft-ietf-tcpimpl-prob-03.txt, section
* 3.10, we send a RST here because data was lost. To
* witness the awful effects of the old behavior of always
* doing a FIN, run an older 2.1.x kernel or 2.0.x, start
* a bulk GET in an FTP client, suspend the process, wait
* for the client to advertise a zero window, then kill -9
* the FTP client, wheee... Note: timeout is always zero
* in such a case.
*/
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, GFP_KERNEL);
|
data_was_unread是在用close关闭socket的时候,仍然缓冲在socket缓冲区没被应用程序读取的数据的字节数,如果它不是0,则表示有些数据本端的应用程序没有收到,也就是网络传输出现了“故障”,因此用RST将这一情况通告给对端,而不是用FIN报告一切正常。从注释来看,此种情况下,老版本的内核确实是发送FIN的,文档的3.10小节详细分析了这个tcp实现问题。看来当时我用来测试的Linux内核的确够古老...
那么为什么会发生数据没有读完全的情况呢?为此,查阅了perl手册页中对recv的解释:
Returns the address of the sender if SOCKET's protocol supports this; returns an empty string otherwise. If there's an error, returns the undefined value.
|
这个函数的返回值让我大吃一惊,它的返回值与C语言接口中的recv的返回值简直是大相径庭,如此的不一致,以至于不能想当然,你说我能不汗颜么?
问题找到了,有的放矢的修补自然就轻松的很了!
通过这件事,我觉得有的时候我们还是笨一点儿好,不要为了可能的那么一点点儿“便利”,就挺而走险;还有就是对待不是很清楚的东西一定要注意,不要被表象所迷惑,不能想当然;perl中recv的这种不一致性也需要为此负责,所以我们以后做类似的事情的时候,尽可能要保证不标新立异,照顾到用户的望文生义和想当然!
阅读(2112) | 评论(2) | 转发(0) |