write 的含义
用write()
向 socket 描述符写入n
个字节,write()
返回n
,这意味着什么呢?你可能这样想,write()
返回n
,那不就表示对方已经收到n
个字节了吗?
然而,实际上write()
返回n
仅仅保证将n
个字节写入内核中sockfd
的发送缓冲区。但是内核是否已经将数据转发给网卡,对方是否收到了数据,就无从得知了。
TCP 的可靠关闭
既然调用write()
无法确保对方收到了数据,那么在调用write()
之后就调用close()
来关闭连接是否合适呢?RFC1122指出,由于 TCP 是全双工的,调用close()
时如果有可读数据停留在接收缓冲区,或者在close()
之后收到新的数据,数据就无法被读取,这时 TCP 将会发送 RST 通知对方数据已经丢失了。
让我们看一个例子,首先客户端发送1000000
个字节给服务端:
服务端在接收客户端的连接之后,首先write()
一行数据给客户端,接着读取从客户端发来的数据:
程序看起来似乎没有问题,但运行服务端程序就会发现,尽管客户端发送1000000
个字节,但服务端在收到961180
个字节之后,收到了 RST segment:
让我们分析一下上面的代码:
- 客户端调用
close()
时,它的接收缓冲区有一行数据未读取,这时close()
将导致客户端发送一个 RST 给服务端。 - 服务端调用
read()
返回-1
,错误信息为:Connection reset by peer
。
只要客户端在close()
之前,读取完接收缓冲区的数据,就不会出现错误了,具体可以这样做:
- 客户端写完所有数据,调用
shutdown(sockfd, SHUT_WR)
,这会导致客户端发送一个 FIN 给服务端。 - 服务端读取完接收缓冲区的数据之后,
read()
将会返回0
,这时服务端将会调用close()
关闭连接,导致一个 FIN 发送给客户端。 - 客户端读取完接收缓冲区的数据之后,
read()
返回0
,这时调用close()
关闭连接。
所以我们只需要修改客户端的几行代码,就可以实现连接的可靠关闭:
这次运行服务端就会发现数据接收正确: