如何实现仅支持单连接的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; }
代码关键点说明
create_listen_socket函数:封装监听套接字的创建、绑定、监听逻辑,设置SO_REUSEADDR避免服务器重启时的端口占用问题。- 连接状态跟踪:用
has_active_connection布尔值记录当前是否存在活跃连接。 - 拒绝新连接的核心操作:当有活跃连接时,关闭监听套接字
listen_fd,此时内核会直接拒绝新的SYN请求,客户端调用connect时立刻触发ConnectionRefusedError。 - 非阻塞连接检测:用
recv配合MSG_PEEK | MSG_DONTWAIT参数,在不读取数据的前提下非阻塞检查客户端是否断开,避免主线程被阻塞。
测试效果
- 第一个Python客户端运行后,会输出
Client connected successfully to the server. - 此时启动第二个Python客户端,会立刻输出
Connection refused: Unable to connect to the server. - 当第一个客户端休眠结束(或手动终止),服务器会重新开启监听,后续客户端可以正常连接。
内容的提问来源于stack exchange,提问作者Mika
相关产品推荐
相关产品推荐

