昨天在测试socket的传输时,发现有时候recv会会返回接收字节的长度为0,
开始没有在意,但是过了一会系统重启了。这样一来带来的麻烦就大了。
因为发现每次系统重启时,都在send函数附近,为了验证在send时系统重启,于是在send函数前后各加了一个打印输出,发现每次都是在第一个打印有输出,第二个没有。
于是猜测是send时,阻塞导致给看门狗喂狗的线程没有切换执行,造成了系统复位重启。
(这里没有考虑内存溢出的错误,是因为设置了SIGSEGV信号的处理函数。 如果发送内存访问错误,会捕捉到,并打印出提示信息,但是这里的复位重启并没有打印信息)
每次出现这样的问题前,recv总会返回接受字节为0。因为使用了select,如果没有数据到来是不会接收的,但是为什么接收了,而且收到的字节长度还是0。所以考虑怎么recv会返回0,而且又为什么send的发送导致系统的看门狗竟然长时间无法喂狗。
在APUE上recv返回值为0的描述:
如果发送者已经调用shutdown来结束传输,或者网络协议支持默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv返回0.
后来man recv 返回值为0的描述:
These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an
orderly shutdown.
显然,当recv返回值为零的时候,对方已经调用shutdown了,关闭连接。
orderly shutdown.
由此,当recv返回值为0时,如果想继续双向连接,就需要重新建立连接了。一般很少用到shutdown,实现所谓的半关闭(半关闭,TCP提供了连接的一端在结束它的发送后还能收来自另一端数据的能力)。
recv返回值为零的问题,是弄明白了,但是为什么send会阻塞,导致看门狗复位呢?
因为send只是把数据拷贝到内核,
APUE上的描述:
当send成功返回时,并不必然表示连接的另一端的进程接收数据。所保证的仅是当send成功是,数据已经无误地发送到网络上了。
man send:
When the message does not fit into the send buffer of the socket,
send() normally blocks, unless the socket has been placed in non-block-
ing I/O mode. In non-blocking mode it would return EAGAIN in this
case. The select(2) call may be used to determine when it is possible
to send more data.
send() normally blocks, unless the socket has been placed in non-block-
ing I/O mode. In non-blocking mode it would return EAGAIN in this
case. The select(2) call may be used to determine when it is possible
to send more data.
如果send在这里阻塞,就应该表明现在数据没有拷贝到内核,没有成功的发送到网络上,所以在这里阻塞等待。这样导致看门狗复位的原因应该就是由于发送缓冲区已满,则send就要阻塞,直到数据数据可以发送,以至于线程没有切换,造成没有喂狗。可思议是有功select先判断是否可写,然后再调用send函数,进行发送。
依然没有解决的几个问题:
为什么send阻塞了,看门狗喂狗线程就没有切换执行呢?线程的切换不应该是这样的。
因为send阻塞时,会导致当前线程挂起,那么对于内核来说切换到其他线程是很简单的。还有可能的就是,发送时传入的数据长度有问题,那么就需要把它打印出来看看了。
如果recv返回值是0,那么也就是说是shutdown的缘故,那么对于send函数应该不会有影响,因为对方虽然关闭了一个连接但是还有一个连接可以用来接收还是可以的。send函数发送时出现阻塞,应该是send buffer被占满了,那么因为每次发送的数据都不大,只有不到一百个字节,不应该是一次发送导致send buffer出问题。比较合理的是,发送了多次但是没有发送成功(发送到网络上),导致数据累积在send buffer中,但是为什么会没有发送成功(发送到网络上)呢?