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

如何实现仅支持单连接的C服务器,触发客户端ConnectionRefusedError

实现单连接C服务器,直接拒绝新连接触发ConnectionRefusedError

可行性说明

完全可行。要让客户端触发ConnectionRefusedError,核心是让内核直接拒绝新的连接请求,而非服务器先accept再断开(后者只会让客户端连接成功后被中断,触发ConnectionResetError或ConnectionAbortedError)。

实现思路

  • 用标志位跟踪当前是否存在活跃连接
  • 无活跃连接时,保持监听套接字开启,正常accept新连接
  • 有活跃连接时,关闭监听套接字,此时内核会直接拒绝新的SYN请求,客户端调用connect时立刻触发ConnectionRefusedError
  • 活跃连接断开后,重新创建监听套接字,恢复接受新连接

示例C代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 12345
#define BUFFER_SIZE 1024

int create_listen_socket() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 设置端口复用,避免服务器重启时出现地址占用错误
    int opt = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    // backlog设为1,仅保留一个待处理连接(配合后续逻辑,确保只有一个活跃连接)
    if (listen(listen_fd, 1) < 0) {
        perror("listen failed");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);
    return listen_fd;
}

int main() {
    int listen_fd = create_listen_socket();
    int conn_fd = -1;
    _Bool has_active_connection = 0;

    while (1) {
        if (!has_active_connection) {
            // 无活跃连接,等待新连接
            struct sockaddr_in client_addr;
            socklen_t client_len = sizeof(client_addr);
            conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
            if (conn_fd < 0) {
                perror("accept failed");
                close(listen_fd);
                listen_fd = create_listen_socket();
                continue;
            }

            printf("Accepted connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
            has_active_connection = 1;
            // 关闭监听套接字,拒绝后续连接请求
            close(listen_fd);
        } else {
            // 有活跃连接,非阻塞检查连接是否断开
            char buffer[BUFFER_SIZE];
            ssize_t recv_len = recv(conn_fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT);
            if (recv_len == 0) {
                // 客户端已正常断开
                printf("Client disconnected\n");
                close(conn_fd);
                has_active_connection = 0;
                // 重新创建监听套接字,恢复接受连接
                listen_fd = create_listen_socket();
            } else if (recv_len < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
                // 连接出错,强制断开
                perror("recv error");
                close(conn_fd);
                has_active_connection = 0;
                listen_fd = create_listen_socket();
            }
            // 短暂休眠,避免CPU空转
            usleep(100000);
        }
    }

    return 0;
}

代码关键点说明

  1. create_listen_socket函数:封装监听套接字的创建、绑定、监听逻辑,设置SO_REUSEADDR避免服务器重启时的端口占用问题。
  2. 连接状态跟踪:用has_active_connection布尔值记录当前是否存在活跃连接。
  3. 拒绝新连接的核心操作:当有活跃连接时,关闭监听套接字listen_fd,此时内核会直接拒绝新的SYN请求,客户端调用connect时立刻触发ConnectionRefusedError。
  4. 非阻塞连接检测:用recv配合MSG_PEEK | MSG_DONTWAIT参数,在不读取数据的前提下非阻塞检查客户端是否断开,避免主线程被阻塞。

测试效果

  • 第一个Python客户端运行后,会输出Client connected successfully to the server.
  • 此时启动第二个Python客户端,会立刻输出Connection refused: Unable to connect to the server.
  • 当第一个客户端休眠结束(或手动终止),服务器会重新开启监听,后续客户端可以正常连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:05:10