客户端断开连接时套接字服务器崩溃

Posted

技术标签:

【中文标题】客户端断开连接时套接字服务器崩溃【英文标题】:Socket server crashes while client has disconnected 【发布时间】:2021-12-28 14:04:51 【问题描述】:

练习 C 语言和套接字编程。

服务器代码在检查实时客户端并且客户端已断开连接(例如连接断开)时崩溃。我没有阅读 GDB 转储的经验。有人可以指出我在这里缺少什么吗?

这是服务器代码。客户端连接和断开连接。服务器通过发送一条小消息并等待回复来检查断开连接的客户端。收到损坏的管道错误后,代码崩溃。

void * client_hartbeat()

    int ret = 0, i;
    char send_msg[50] = "e";
    char recv_msg[50];
    while(1)
    
        sleep(3);
        printf("\nchecking for disconnected clients\n");
        memset(&recv_msg,0,sizeof(recv_msg));
        for(i=0;i<CLIENTS;i++)
            if(client_pool[i]!=0)
                printf("TEST0, socket: %i thread: %i\n", client_pool[i],pthread_self());
                if(send(client_pool[i],send_msg,sizeof(send_msg),0) < 0)
                    printf("send error: %s\n", strerror(errno));
                else if(recv(client_pool[i],recv_msg,sizeof(recv_msg),0) < 0)
                    printf("receive error: %s\n", strerror(errno));
                
                printf("TEST1, socket: %i thread: %d\n", client_pool[i],pthread_self());
            
        
    
    printf("TEST1\n");
    pthread_exit(&th2);

这是 GDB 转储:

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
waiting connections...
[New Thread 0x7ffff7dba640 (LWP 22567)]
[New Thread 0x7ffff75b9640 (LWP 22568)]

checking for disconnected clients

checking for disconnected clients
TEST0, socket: 4 thread: -144992704
TEST1, socket: 4 thread: -144992704

checking for disconnected clients
TEST0, socket: 4 thread: -144992704
TEST1, socket: 4 thread: -144992704

checking for disconnected clients
TEST0, socket: 4 thread: -144992704

Thread 3 "a.out" received signal SIGPIPE, Broken pipe.
[Switching to Thread 0x7ffff75b9640 (LWP 22568)]
__libc_send (flags=<optimized out>, len=50, buf=0x7ffff75b8e10, fd=4) at ../sysdeps/unix/sysv/linux/send.c:28
28      ../sysdeps/unix/sysv/linux/send.c: No such file or directory.
(gdb)

【问题讨论】:

您可能应该忽略该信号。 signal(SIGPIPE, SIG_IGN);。无关:你发送sizeof(send_msg)(50字节)即使我怀疑你只发送1字节(e)。 我之前确实尝试过 1 个字节,也尝试过 strlen(send_msg) 而不是 sizeof(send_msg) 并且仍然代码崩溃 是的,那是一条不相关的评论。您是否尝试忽略信号?如果您尝试在关闭的套接字上使用 send/recv,您还可以在 sendrecv 调用中使用标志 MSG_NOSIGNAL 以不生成信号。 我用我的建议写了一个答案 好主意,刚刚尝试使用 MSG_NOSIGNAL 现在服务器代码没有崩溃,从服务器的角度来看,客户端仍然连接,不幸的是,这并不能解决这个问题。服务器应该知道客户端已断开连接。 【参考方案1】:

这些选项中的任何一个都应该可以避免SIGPIPE 信号杀死您的程序:

忽略SIGPIPE
#include <signal.h>

int main() 
    signal(SIGPIPE, SIG_IGN);
    ...
send/recv 尝试通过提供MSG_NOSIGNAL 标志对关闭的套接字进行操作时,不要生成SIGPIPE 信号:
if (send(client_pool[i], send_msg, strlen(send_msg), MSG_NOSIGNAL) < 0) 
    printf("send error: %s\n", strerror(errno));
 else if (recv(client_pool[i], recv_msg, sizeof(recv_msg), MSG_NOSIGNAL) < 0) 
    printf("receive error: %s\n", strerror(errno));

来自sendman 页面:

MSG_NOSIGNAL(Linux 2.2 起)

如果面向流的套接字上的对等方已关闭连接,则不要生成SIGPIPE 信号。仍然返回 EPIPE 错误。这提供了与使用sigaction(2) 来忽略SIGPIPE 类似的行为,但是,虽然MSG_NOSIGNAL 是一个每次调用的功能,但忽略SIGPIPE 会设置一个影响进程中所有线程的进程属性。 p>


您还需要在使用send 后检测套接字的状态,因为它可能会在客户端死亡后的第一个send 上返回成功。

你可以添加一个辅助函数:

int checksock(int sock)  // 0 on success, -1 on failure
    int error = 0;
    socklen_t len = sizeof error;

    int rv = getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len);
    if (rv == 0 && error) 
        errno = error;     // set the thread local errno
        rv = -1;
    
    return rv;

然后您的发送部分可以这样做:

if (send(client_pool[i], send_msg, strlen(send_msg), MSG_NOSIGNAL) < 0
    || checksock(client_pool[i]))     // added check

   printf("send error: %s\n", strerror(errno));   // Broken pipe
 else if (recv(client_pool[i], recv_msg, sizeof(recv_msg), MSG_NOSIGNAL) < 0) 
   printf("receive error: %s\n", strerror(errno));

【讨论】:

检查SO_ERROR 通常只能在非阻塞操作之后进行(例如当connect() 返回EINPROGRESS/EWOULDBLOCK 然后select() 报告套接字是可写的,即@ 987654347@操作完成,然后使用SO_ERROR查看socket是否真正连接)。否则,在检查返回值是否失败后,请改用errno @RemyLebeau 不同之处在于,由于send 在客户端死亡后的第一个send 上返回成功,所以errno0。另一方面,用getsockopt 检查SO_ERROR 会返回错误,因此可以在第一个send 上检测到断开连接。 如果send()“成功”,则无法保证SO_ERROR 会更新为错误代码,这样做也没有任何意义。 @RemyLebeau 不,我不知道为什么它在实践中似乎有效,但我阅读了不止一个建议来检查SO_ERROR(以及打开keepalive)。在我做的非常小的测试中,它在第一次send 之后从未检测到它,但在仅检查来自send 的返回时它一直未能检测到它。也许getsockoptSO_ERROR 不完全是被动的?

以上是关于客户端断开连接时套接字服务器崩溃的主要内容,如果未能解决你的问题,请参考以下文章

如何处理与 python 套接字的断开连接? (连接重置错误)

断开Qt后连接到服务器时客户端程序崩溃

为啥当客户端断开连接时这个简单的 websocket 代码会抛出?

socket.io 服务器在注册客户端断开连接时非常延迟

Perl-我如何知道套接字客户端何时断开连接(当用户关闭窗口/浏览器时)

异步套接字和“静默”断开连接