我有一个简单的服务器,用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
}
}
在从10.04到16.04的Ubuntu版本上,这种方法可以正常工作数年,当时通常一次只有1个或2个(有时是3个)客户端都处于活动状态,并且都通过以太网LAN连接。最近,我们一次运行了更多的客户端(仍然是个位数),并且增加的大部分是Windows客户端的副本,通常通过SOHO WiFi路由器连接到LAN。上个月,当我们有一个客户端从会议厅通过公共WiFi远程连接时,这种情况也出现了。
服务器每隔一周或两周就会停止向所有客户端发送邮件。当我使用netstat进行调查时,我发现一个或(通常)多个套接字卡在CLOSE_WAIT中,其Recv-Q为1,而Send-Q中为约13K。最终,服务器发出错误消息,指出由于errno 32(断开的管道)而导致关闭客户端连接,一切恢复正常。
我正在猜测 Windows-via-WiFi连接中存在一些古怪之处,这导致连接关闭顺序发生的方式有所不同,但这并不是一个受过良好教育的猜测。
我的问题(最后!)是我应该做些什么,以便在它变成服务器挂起之前检测到即将出现的问题,或者让Linux立即给我一个错误,而不是让我等待它决定放弃时。我发现服务器期望从客户端接收到数据的想法多种多样,但对于“只写”连接却毫无用处(嗯,一个答案是在每次写之前运行netstat并分析其输出,但这对于我们希望该系统在全面投入生产后能够将数百个传感器阵列的数据馈送到数十个客户)。我尝试添加一些代码以尝试使用仅Linux的SIOCOUTQ fcntl来检测它,以查找传输队列中堆积的数据,但是由于它很少在野外发生,因此未能获得良好的测试。而我尝试使客户端行为不佳的尝试并不顺利,因为客户端Linux很乐意在其接收队列中堆积足够的数据,以防止其在几天之内失败。因此,服务器永远不会在其侧面看到堆积物。
我是否错过了一些套接字或API调用选项,它们会显示“忘记耐心和重试:立即放弃并失败!”?我应该耐心等待几周,看看我的SIOCOUTQ修复程序是否已解决问题?还是我需要完善我的google关键字选择技能,以找到到目前为止我无法回答的答案?
谢谢
Ran
答案 0 :(得分:0)
我假设您不是在使用非阻塞套接字或SO_TIMEOUT。
由于客户行为不当,该send
呼叫可能会挂断很长时间。想象一下,如果我编写了一个连接到您的服务器但从未在我的客户端套接字上调用recv
的客户端。从字面上看:
int result = connect(sock, addr, addrlen);
while (1) {
sleep(1);
}
在对我的客户端进行足够数量的send
调用之后,将备份TCP管道,并且您的send
调用实际上可能永远被阻塞。因此,在上一个客户端完成或出错之前,其他客户端无法进行其他发送呼叫。这就是单线程服务器和阻塞套接字的本质。
更可能的情况是,如果客户端连接到您的服务器,则突然失去网络连接。这也可能会使您的服务器挂起几秒钟。
请考虑以下任何或所有条件来更新服务器:
无阻塞套接字-处理send
返回表示已发送部分数据的值的情况。您也可以使用recv
轮询套接字,以查看远程客户端是否退出或启动了1向关机。
每个客户端都有自己的线程和消息队列。当服务器要发送某些内容时,它将数据字节的副本放入每个客户端的消息队列中。每个线程负责发送。与一个线程相关联的行为异常的客户端不会阻止其他线程发送。
SO_LINGER。您可以尝试将每个套接字的延迟时间设置为零,以查看是否有帮助。