【问题标题】:Linux: Detecting CLOSE_WAIT on a write-only TCP socketLinux:在只写 TCP 套接字上检测 CLOSE_WAIT
【发布时间】:2018-12-16 05:41:12
【问题描述】:

我有一个简单的服务器,用 C 编写,它接受来自各种来源的传感器和状态信息,然后将其合并并重新格式化为客户端的 ASCII 文本行流。客户端通过侦听器套接字连接,然后读取消息流并对其执行任何操作,直到用户关闭应用程序。由于这是一种单向协议,因此服务器从不费心检查待处理的接收数据。

每当有消息要发送给所有活跃用户时,它都会经过一个简单的循环:

bufflen = strlen(tcp_buff);
for (next_client_ix = 0; next_client_ix < MAX_TCP_CONNECTIONS; next_client_ix++)
    if (TCP_client_sd[next_client_ix] != 0)
        {
        rc = send(TCP_client_sd[next_client_ix], tcp_buff, bufflen, MSG_NOSIGNAL);
        if (rc != bufflen)
            {
            errno_hold = errno;
            s = inet_ntoa(Tcp_client_sin[next_client_ix].sin_addr);
            remote_port = htons(Tcp_client_sin[next_client_ix].sin_port);
            sprintf(log_buff, "Error %d (%s) sending alert to %s:%d. Closing\n", errno_hold, strerror(errno_hold), s, remote_port);
            log_message(SB_ALERT_TEXT_ERROR, log_buff);
            close(TCP_client_sd[next_client_ix]);
            TCP_client_sd[next_client_ix] = 0;        // Free the socket for the next client
            }
        }

多年来,这在 Ubuntu 10.04 到 16.04 版本上运行良好,当时我们通常一次只有 1 或 2 个(偶尔 3 个)客户端处于活动状态,所有客户端都通过以太网 LAN 连接。最近,我们一直在一次运行更多的客户端(仍然是个位数),并且大部分增加是 Windows 客户端的副本,通常通过 SOHO WiFi 路由器连接到 LAN。上个月,当我们有一个客户端通过公共 WiFi 从会议厅远程连接时,这种情况也出现了。

每两周一次,服务器会停止向所有客户端发送几分钟。当我使用 netstat 进行调查时,我发现一个或(通常)多个套接字卡在 CLOSE_WAIT 中,Recv-Q 为 1,Send-Q 中大约有 13K。最终,服务器吐出一条错误消息,指出由于 errno 为 32(Broken Pipe),它正在关闭客户端连接,然后一切恢复正常。

猜测 Windows-via-WiFi 连接中存在一些怪癖,导致连接关闭顺序发生不同,但这是一个不太受过教育的猜测。

我的问题(终于!)是我应该做些什么来在问题变成服务器挂起之前检测到即将到来的问题,或者让 Linux 立即给我一个错误,而不是让我在它决定放弃时等待。对于期望从客户端传入数据的服务器,我发现了各种想法,但对于“只写”连接却没有(嗯,一个答案是在每次写入之前运行 netstat 并分析其输出,但这对于我们希望该系统在全面生产时能够将来自数百个传感器阵列的数据提供给数十个客户)。我已经尝试添加一些代码来尝试使用仅限 Linux 的 SIOCOUTQ fcntl 来检测它,以查找在传输队列中堆积的数据,但由于它在野外很少发生,所以无法进行良好的测试。我尝试创建一个行为不端的客户端并不顺利,因为客户端 Linux 很高兴地在其接收队列中堆积了足够的数据,以防止它在几天内失败。因此,服务器永远不会在其一侧看到堆积物。

是否有一些我错过的套接字或 API 调用选项会显示“忘记耐心并重试:现在放弃并失败!”?我是否应该耐心等待几周,看看我的 SIOCOUTQ 修复是否解决了问题?还是我需要改进我的 google 关键字选择技巧才能找到迄今为止我一直无法找到的答案?

谢谢,

【问题讨论】:

  • 我有一个挂起的“堆”CLOSE_WAIT 连接,但最近的事件只有一个。这使混乱更加复杂:如果写入单个“未准备好接收更多数据”套接字会导致挂起,我是如何设法在 CLOSE_WAIT 中获得 8 个套接字的?

标签: linux sockets server freeze


【解决方案1】:

我假设您没有使用非阻塞套接字或 SO_TIMEOUT。

send 呼叫可能由于客户端行为不端而挂起很长时间。想象一下,如果我编写了一个连接到您的服务器但从未在我的客户端套接字上调用 recv 的客户端。字面意思是:

int result = connect(sock, addr, addrlen);
while (1) {
    sleep(1);
}

在对我的客户端进行足够数量的send 调用后,TCP 管道将得到备份,而您的send 调用可能会永远阻塞。因此,在前一个完成或出错之前,其他客户端不会发生其他发送调用。这就是单线程服务器和阻塞套接字的本质。

更可能的情况是,如果客户端连接到您的服务器,然后突然失去网络连接。这也可能会使您的服务器挂起几秒钟。

考虑以下任何或所有更新您的服务器:

  • 非阻塞套接字 - 并处理send 返回值指示已发送部分数据的情况。您还可以使用recv 轮询套接字,以查看远程客户端是否已退出或启动单向关闭。

  • 每个客户端都有自己的线程和消息队列。当服务器有东西要发送时,它会将数据字节的副本放入每个客户端的消息队列中。每个线程负责发送。与一个线程关联的行为不端的客户端不会阻止其他线程发送。

  • SO_LINGER。您可以尝试将每个套接字上的逗留时间设置为零,看看是否有帮助。

【讨论】:

  • 我试着像这样写一个行为不端的客户端来测试我可能的修复。我没有意识到它运行的 Linux 对它的接收队列有 huge(多天的价值)限制,所以我中止了测试,而不是冒新的服务器出现硬故障的风险我不在的时候写代码。
  • 我认为您更改为非阻塞套接字的想法是正确的:数据量足够低以至于部分发送可能只会在我的“问题”情况下发生,所以我不不需要复杂的恢复代码。我会试试看。谢谢。
猜你喜欢
  • 1970-01-01
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-02
  • 2011-10-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多