You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言TCP服务端关连接留端口监听及客户端重连方案咨询

TCP C语言服务端/客户端扩展问题解答

问题背景

我有一段来自GeeksforGeeks的C语言TCP服务端代码如下:

int main()
{
    int sockfd, connfd, len;
    struct sockaddr_in servaddr, cli;
   
    // socket create and verification
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        printf("socket creation failed...\n");
        exit(0);
    }
    else
        printf("Socket successfully created..\n");
    bzero(&servaddr, sizeof(servaddr));
   
    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);
   
    // Binding newly created socket to given IP and verification
    if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) {
        printf("socket bind failed...\n");
        exit(0);
    }
    else
        printf("Socket successfully binded..\n");
   
    // Now server is ready to listen and verification
    if ((listen(sockfd, 5)) != 0) {
        printf("Listen failed...\n");
        exit(0);
    }
    else
        printf("Server listening..\n");
    len = sizeof(cli);
   
    // Accept the data packet from client and verification
    connfd = accept(sockfd, (SA*)&cli, &len);
    if (connfd < 0) {
        printf("server accept failed...\n");
        exit(0);
    }
    else
        printf("server accept the client...\n");
   
    // Function for chatting between client and server
    func(connfd);
   
    // After chatting close the socket
    close(sockfd);
}

客户端代码如下:

int main()
{
    int sockfd, connfd;
    struct sockaddr_in servaddr, cli;
 
    // socket create and verification
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        printf("socket creation failed...\n");
        exit(0);
    }
    else
        printf("Socket successfully created..\n");
    bzero(&servaddr, sizeof(servaddr));
 
    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(PORT);
 
    // connect the client socket to server socket
    if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr))
        != 0) {
        printf("connection with the server failed...\n");
        exit(0);
    }
    else
        printf("connected to the server..\n");
 
    // function for chat
    func(sockfd);
 
    // close the socket
    close(sockfd);
}

我的需求是:服务端关闭当前TCP连接后,客户端能感知到断开并重新建立连接,最终实现服务端可处理多客户端的目标。我知道需要关闭connfd以允许新连接,现咨询以下两个问题:

  1. 客户端需如何实现重连?是否每次都要新建sockfd并重复完整连接流程?
  2. 后续计划实现多线程,如何让服务端持续等待新连接?

问题解答

1. 客户端重连实现

是的,每次重连都需要新建sockfd并重复完整的连接流程。

原因是:TCP连接断开后,原有的sockfd处于关闭状态,无法直接复用(即使开启端口复用,也需要重新创建socket对象)。具体实现思路如下:

  • 感知连接断开:在通信函数func中,通过read()/write()的返回值判断连接状态:
    • 若read()返回0,说明服务端主动关闭了连接;
    • 若返回-1且errno为ECONNRESET/EPIPE,说明连接异常断开。
      遇到上述情况时,退出func函数,触发重连逻辑。
  • 循环重连逻辑:将客户端的连接、通信逻辑放到一个无限循环中:
    1. 每次循环开始时,调用socket()创建新的套接字;
    2. 提前初始化好servaddr结构体,无需每次重新赋值;
    3. 调用connect()尝试连接服务端;
    4. 连接成功则进入func通信;通信结束(连接断开)后,关闭当前sockfd,回到循环开头重试;
    5. 连接失败时,添加适当延时(如sleep(3))避免频繁重试占用资源。

示例伪代码:

int main()
{
    struct sockaddr_in servaddr;
    // 提前初始化服务端地址
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(PORT);

    while(1) {
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1) {
            printf("socket creation failed...\n");
            sleep(3);
            continue;
        }
        printf("Socket successfully created..\n");

        if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) {
            printf("connection with the server failed... Retrying in 3s\n");
            close(sockfd);
            sleep(3);
            continue;
        }
        printf("connected to the server..\n");

        func(sockfd); // 通信直到连接断开
        close(sockfd);
        printf("Connection closed, retrying...\n");
        sleep(3);
    }
    return 0;
}

2. 服务端多线程实现持续等待新连接

要实现服务端持续处理多客户端,核心是把accept()放到无限循环中,每接收到一个新连接就创建独立线程处理该客户端的通信,主线程继续等待下一个连接。具体步骤:

  • 保留监听套接字:sockfd是监听套接字,仅需初始化一次(创建、绑定、监听),不能在处理完客户端后关闭,要一直保留到服务端退出。
  • 无限循环接受连接:将accept()调用放到while(1)循环中,每次成功获取connfd后,创建新线程处理该连接。
  • 线程处理通信:线程函数接收connfd作为参数,调用func()与客户端通信,通信结束后关闭connfd并退出线程。注意要使用pthread_detach()分离线程,避免产生僵尸线程。

示例伪代码:

// 线程处理函数
void* client_handler(void* arg) {
    int connfd = *(int*)arg;
    free(arg); // 释放传入的参数内存
    func(connfd);
    close(connfd);
    printf("Client connection closed\n");
    pthread_exit(NULL);
}

int main()
{
    int sockfd, len;
    struct sockaddr_in servaddr, cli;
   
    // socket create and verification
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        printf("socket creation failed...\n");
        exit(0);
    }
    printf("Socket successfully created..\n");
    bzero(&servaddr, sizeof(servaddr));
   
    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);
   
    // Binding
    if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) {
        printf("socket bind failed...\n");
        exit(0);
    }
    printf("Socket successfully binded..\n");
   
    // Listen
    if ((listen(sockfd, 5)) != 0) {
        printf("Listen failed...\n");
        exit(0);
    }
    printf("Server listening..\n");
    len = sizeof(cli);
   
    // 无限循环接受新连接
    while(1) {
        int* connfd = malloc(sizeof(int));
        *connfd = accept(sockfd, (SA*)&cli, &len);
        if (*connfd < 0) {
            printf("server accept failed...\n");
            free(connfd);
            continue;
        }
        printf("server accept the client...\n");
       
        // 创建线程处理客户端
        pthread_t tid;
        if (pthread_create(&tid, NULL, client_handler, connfd) != 0) {
            printf("Failed to create thread\n");
            close(*connfd);
            free(connfd);
        } else {
            pthread_detach(tid); // 分离线程,无需主线程join
        }
    }
    // 实际中服务端很少走到这里,可通过信号处理退出
    close(sockfd);
    return 0;
}

注意事项:

  • 线程函数的参数要通过动态分配内存传递,避免栈内存被覆盖;
  • 要处理线程创建失败的情况,及时关闭connfd避免资源泄漏;
  • 可根据需求调整监听队列长度(listen()的第二个参数),但不要设置过大。

内容的提问来源于stack exchange,提问作者a_confused_student

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 09:20:31