【问题标题】:How to handle TCP client disconnect in C如何在 C 中处理 TCP 客户端断开连接
【发布时间】:2021-03-25 05:56:48
【问题描述】:

我正在尝试编写一个将串行数据流式传输到客户端的基本 TCP 服务器。服务器将连接到串行设备,从该设备读取数据,然后将其作为字节流传输给客户端。写TCP服务器没问题。问题是当客户端断开连接时服务器会崩溃。在其他语言中,例如 Python,我可以简单地将 write() 语句包装在 try-catch 块中。程序将尝试写入套接字,但如果客户端已断开连接,则会引发异常。在另一个项目中,这段代码 sn-p 为我工作:

try:
  client_socket.send(bytes(buf, encoding='utf8'))
except Exception as e:
  logger.info("Client disconnected: %s", client_id)

我可以在我的 C 代码中处理客户端断开连接,但只能通过首先从套接字读取并检查读取是否等于 0。如果是,那么我的客户端已断开连接,我可以照常进行。这个解决方案的问题是我的客户端在每次写入后都必须 ping 回服务器,这不太理想。

有谁知道如何在 C 中优雅地处理 TCP 客户端断开连接?我的示例代码如下所示。谢谢!

// Define a TCP socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// Allow for the backlog of 100 connections to the socket  
int backlog = 100;  

// Supply a port to bind the TCP server to
short port = 9527;

// Set up server attributes
struct sockaddr_in servaddr;  
servaddr.sin_family = AF_INET;  
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
servaddr.sin_port = htons(port);  
    
// Set the socket so that we can bind to the same port when we exit the program
int flag = 1;  
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) {  
    perror("setsockopt fail");  
}  
    
// Bind the socket to the specified port
int res = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  
if (res < 0) {
    perror("bind fail");  
    exit(1); 
}  
    
// Listen for incoming connections
if (listen(sockfd, backlog) == -1) {  
    perror("listen fail");  
    exit(1); 
} else {
    printf("Server listening on port\n", port); 
}

for(;;) {
    // Wait for incoming connection  
    struct sockaddr_in cliaddr;  
    socklen_t len = sizeof(cliaddr);  
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);  
    if (-1 == connfd) {  
        perror("Could not accept incoming client");
        continue;   
    }  
        
    //Resolving Client Address  
    char buff[INET_ADDRSTRLEN + 1] = {0};  
    inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);  
    uint16_t cli_port = ntohs(cliaddr.sin_port);  
    printf("connection from %s, port %d\n", buff, cli_port);

    for(;;) {
        // Read from serial device into variable here, then send
        if(send(connfd, "Data...Data...Data\n", 19, 0) < 0) {
            printf("Client disconnected...\n"); 
            break; 
        }
    }
}

【问题讨论】:

  • 试一试? C? C 没有例外。
  • 对。我是说在 Python 这样的另一种语言中,我可以使用 try-catch 来处理客户端断开连接。 C 没有这种机制,所以我想知道是否有一种替代方案不需要客户端一直 ping 回服务器。
  • 如果没有持续不断的通信,就无法知道连接是否仍然存在。
  • 通常服务器会使用selectpoll 来查看哪些客户端发送了新数据。断开连接算作发送新数据,但当您尝试读取数据时,read 将返回 0。
  • @user253751 -- 我明白了。那么这是否意味着处理客户端断开连接的唯一方法是让客户端不断发送新数据,然后让服务器从套接字读取?令我感到奇怪的是,大多数语言在底层都是用 C 构建的,它们可以处理客户端断开连接而无需从套接字读取。但是 C 本身无法处理它,除非客户端不断 ping 服务端

标签: c sockets server tcp stream


【解决方案1】:

看起来像重复的 of thisthisthis

长话短说,除非您对该连接执行一些写入(或读取)操作,否则您无法检测到断开连接。更准确地说,即使看起来 send 没有返回错误,也不能保证客户端确实发送和接收了此操作。原因是套接字操作被缓冲,发送的有效负载只是排队,以便内核稍后调度它。

根据上下文、要求和假设,您可以做更多的事情。 例如,如果您假设您将以恒定频率发送定期消息,您可以使用 select 和 timeout 方法来检测异常。 换句话说,如果您在过去 3 分钟内没有收到任何内容,则您认为存在问题。

如您所见,thisthis 是该主题的好读物。 看看那个以获得更详细的解释和其他想法。

您所说的 ping(旨在为每个收到的数据包发送的消息)更类似于通常所说的 ACK。

如果您还想确保客户端收到并处理该消息,则只需要类似的内容 (ACK/NACK)。

【讨论】:

  • 感谢您的回复和所有链接!我通常是一名 Golang/Python 开发人员,所以这对我来说都是全新的。我会阅读链接。再次感谢!
【解决方案2】:

感谢@emmanuaf,这是符合我的项目标准的解决方案。我缺少的是 MSG_NOSIGNAL 标志,引用 here

我使用Mashpoe's C Vector Library 创建一个新向量,它将保存我所有传入的客户端连接。

int* client_array = vector_create(); 

然后我生成一个 pthread,它不断地从串行设备读取数据,将该数据存储在一个变量中,然后将其发送到客户端列表中的每个客户端

void* serve_clients(int *vargp) {

    for(;;) {

        // Perform a microsleep
        sleep(0.1);

        // Read from the Serial device

        // Get the size of the client array vector
        int client_vector_size = vector_size(vargp); 

        for(int i = 0 ; i < client_vector_size ; i++) {
            
            // Make a reference to the socket
            int* conn_fd = &vargp[i]; 

            /*
                In order to properly handle client disconnects, we supply a MSG_NOSIGNAL 
                flag to the send() call. That way, if the client disconnects, we will 
                be able to detect this, and properly remove them from the client list. 

                Referenced from: https://beej.us/guide/bgnet/html//index.html#sendman
            */
            if (send(*conn_fd, "Reply from server\n", 18, MSG_NOSIGNAL) < 0) {
                printf("Client disconnected...\n"); 

                // Close the client connection
                close(*conn_fd); 

                // Remove client socket from the vector
                vector_remove(vargp, i); 

                // Decrement index and client_server_size by 1
                i--; 
                client_vector_size--; 

            }             
        }
    }
}

生成 pthread:

// Spawn the thread that serves clients
pthread_t serving_thread; 
pthread_create(&serving_thread, NULL, serve_clients, client_array);

当有新连接进来时,我只需将新连接添加到客户端向量中

while(1) {
    // Wait for incoming connection  
    struct sockaddr_in cliaddr;  
    socklen_t len = sizeof(cliaddr);  
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);  
    if (-1 == connfd) {  
        perror("Could not accept incoming client");
        continue;   
    }  
        
    //Resolving Client Address  
    char buff[INET_ADDRSTRLEN + 1] = {0};  
    inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);  
    uint16_t cli_port = ntohs(cliaddr.sin_port);  
    printf("connection from %s:%d -- Connfd: %d\n", buff, cli_port, connfd); 

    // Add client to vector list
    vector_add(&client_array, connfd);
}

In the end, we have a TCP server that can multiplex data to many clients, and handle when those clients disconnect. 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 1970-01-01
    • 2013-02-19
    相关资源
    最近更新 更多