我在linux x86机器上理解send (2)
系统调用有些麻烦。
考虑我在我的应用程序中与局域网中的其他主机建立了SSH连接。然后我放下网络(例如拔掉电缆)并调用通过连接发送一些SSH数据包的功能(来自我的应用程序)。内部函数调用send
,如
w = send(s->fd_out,buffer, len, 0);
在调试器中,我发现send
会在调用后返回len
(即w == len
)。
如果网络无法访问,该怎么办?当我呼叫netstat
时,它表示我的SSH连接处于状态ESTABLISHED
,即使网络已关闭。
无法理解为什么send
正常执行且不会返回任何错误(例如EPIPE
或ECONNRESET
)。网络放下后,可能是SSH连接生存了一段时间?
感谢所有人。
答案 0 :(得分:3)
这是由于TCP的实现(以及ssh使用TCP)。你的send()只是写一个socket,它只是一个文件描述符,return表示这个操作成功。这并不意味着数据已被发送。文件描述符只是一些指针,其中包含内核的状态。它在内核中实现,以便在会话失败之前保持TCP状态更长一些。事实上,允许内核无限期地保持此会话,直到您明确调用close()或终止进程。因此,您的数据实际上会缓存在内核空间中,以便网卡稍后提供。
您可以执行以下快速实验: 编写一个在建立连接后继续接收消息的服务器
socket();
bind();
listen();
while (1) {
accept();
recv();
}
写一个客户端建立一个连接,接受cin输入,并在你点击返回时向服务器发送一条消息。
socket();
connect();
while (1) {
getline();
send();
}
小心不要在两边的while循环中调用close()。现在,如果您在建立连接后拔掉电缆,发送消息,重新连接并发送另一条消息,您将在服务器端找到这两条消息。
你永远不会注意到的是你在第一条消息之前收到第二条消息。你要么全部丢失,要么按顺序接收它们。
现在让我解释一下为什么它会像这样。这是TCP会话的状态图 https://dl.dropbox.com/u/17011409/TCP_State.png
您可以清楚地看到,在您明确调用close()之前,连接将始终处于已建立状态。这是TCP的预期行为。建立TCP连接是昂贵的,保持会话活动有利于提高性能。 (这部分是TCP DOS的工作原理。攻击者不断建立连接,直到服务器耗尽资源来保存TCP状态信息。)
在这种状态下,您的send()将被委托给内核进行实际发送。 TCP保证有序,可靠的传输,但网络可以随时丢失数据包。所以TCP有待缓冲你的数据包,并继续尝试。有一些算法来限制这种重试,但它在声明失败之前已经缓冲了相当长的时间。在Linux中,假设丢包的默认时间为3秒。但在失败后,TCP将重试。然后在几秒钟后再试一次。您拔掉电缆的事实与前往目的地的数据包丢失情况相同。再次插入电缆后,重试成功,TCP将开始按顺序发送剩余的消息。
我知道我必须彻底解释它。你真的需要知道TCP的细节来推断这种行为。它是TCP提供给你的属性所必需的。将内部实现暴露给程序员是不可接受的。 (有时发送的呼叫有时会在几毫秒内返回,有时会在10秒后返回?我打赌没有人会在他们的代码中想要这个性能炸弹。拥有TCP库的关键在于隐藏网络的这种丑陋性质。)实际上,您甚至需要了解TCP如何通过有损网络实现有序可靠传输的多种RFC和算法。拥塞控制也会影响缓冲区的存在时间。维基百科是一个很好的起点,但如果您真的想了解细节,这是一个完整学期的本科课程。
答案 1 :(得分:2)
使用零标志参数,send()等效于write(2)。它会将您的数据写入文件描述符(存储在内核空间中以便传递)。
你必须使用其他类型的标志:MSG_CONFIRM可以帮助你。