0

我在理解send (2)我的 linux x86 机器上的系统调用时遇到了一些麻烦。考虑我在我的应用程序中与 LAN 中的其他主机建立了 SSH 连接。然后我放下网络(例如拔下电缆)并调用函数(从我的应用程序)通过连接发送一些 SSH 数据包。这个函数里面调用send

w = send(s->fd_out,buffer, len, 0);

在调试器中我发现send返回len(即w == len在调用之后)。如果网络不可达怎么办?当我打电话netstat时,它说我的 SSH 连接处于状态ESTABLISHED,即使网络已关闭。

无法理解为什么send正常执行并且不返回任何错误(如EPIPEor ECONNRESET)。可能是 SSH 连接在网络关闭后仍然存在一段时间?

谢谢大家。

4

2 回答 2

3

这是由于 TCP 的实现(而 ssh 使用 TCP)。您的 send() 只是写入一个套接字,这只是一个文件描述符,返回意味着此操作成功。这并不意味着数据已发送。毕竟,文件描述符只是一些带有内核状态的指针。它在内核中实现,以在会话失败之前将 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 和算法。拥塞控制也会影响缓冲区的存在时间。维基百科是一个很好的起点,但这是一个完整的学期”

于 2012-10-04T05:24:08.837 回答
2

使用零标志参数,send() 等效于 write(2)。它会将您的数据写入文件描述符(存储在内核空间中以提供)。

您必须使用其他类型的标志:MSG_CONFIRM 可能会帮助您。

于 2012-10-05T06:42:00.747 回答