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

WinSock多客户端连接问题:accept()始终返回SOCKET_ERROR

问题分析与解决方案

你遇到的accept()始终返回SOCKET_ERROR的问题,主要是由几个关键错误导致的,我帮你逐一排查并修正:

1. 函数名与WinSock系统函数重名

你自定义了accept()、send()、recv()函数,这和WinSock提供的系统函数完全同名,导致代码中调用这些函数时,实际调用的是你自己写的版本而非系统API,这会引发递归调用或者错误的函数行为。比如你的accept函数内部调用accept(sock, ...)时,其实是在调用你自己的函数,而不是WinSock的accept,这必然导致错误。

解决方案:给自定义函数加上前缀,比如改成my_accept()、my_send()、my_recv()。

2. 监听Socket的作用域冲突

你在全局定义了SOCKET sock,但在main()函数里又重新定义了一个局部的SOCKET sock,这导致全局的sock从未被初始化(绑定、监听的是局部的sock),而你的自定义accept函数使用的是全局的sock,这肯定无法正常接受连接。

解决方案:移除main()里的局部sock定义,改用一个命名更清晰的全局变量(比如listen_sock)来存储监听Socket。

3. 非阻塞Socket的错误处理不当

对于非阻塞模式的accept(),当没有新连接时,它会返回SOCKET_ERROR,但此时WSAGetLastError()会返回WSAEWOULDBLOCK,这是正常的状态,不是真的错误。你的代码没有区分这种情况,导致误判为连接失败。

解决方案:在调用accept后,检查错误码,如果是WSAEWOULDBLOCK就忽略,否则才判定为真的错误。

4. FD_SET的使用方式错误

你给每个客户端单独维护一个fd_set,这不是正确的用法。结合非阻塞Socket的多连接处理,我们只需要在收发数据时直接调用非阻塞的recv/send,并处理WSAEWOULDBLOCK的正常情况即可,无需每个客户端单独维护集合。


修正后的完整代码

#pragma comment(lib, "ws2_32.lib") 
#include <WinSock2.h> 
#include <stdio.h> 
#include <signal.h> 
#include <conio.h> 
#include <assert.h> 
#include <Windows.h> 
#include <iostream> 
#include <cstring>

// 常量定义
#define CLIENT_CON 10 
#define CLIENT_DIS 20 
#define BF_SZ 100 
#define MAX_CONS 5 

// 全局变量
SOCKET listen_sock; // 监听Socket,避免变量名冲突
int PR_CONS = 0; 

struct _client { 
    bool con; 
    sockaddr_in addr; 
    SOCKET cs; 
    int i; 
}; 

_client client[MAX_CONS]; 

// 重命名自定义函数,避免和系统API冲突
bool my_accept(_client* x); 
bool my_send(_client*, char*, int); 
bool my_recv(_client*, char*, int); 
void Server_Status(int); 
void chat_message(char*); 
void handle_clients(); 

int main() { 
    int i = 1; 
    int port = 5150; 
    sockaddr_in ser; 
    WSADATA ws; 

    printf("\t Echo Server (Multiple client support)\n"); 

    // 初始化WinSock
    if (WSAStartup(MAKEWORD(2,2), &ws) != 0) {
        printf("WSAStartup failed: %d\n", WSAGetLastError());
        return 1;
    }

    // 创建监听Socket
    listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    if (listen_sock == INVALID_SOCKET) {
        printf("socket failed: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 设置SO_REUSEADDR选项
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i, sizeof(i)); 

    // 绑定地址
    ser.sin_family = AF_INET; 
    ser.sin_addr.S_un.S_addr = INADDR_ANY; 
    ser.sin_port = htons(port); 
    if (bind(listen_sock, (SOCKADDR*)&ser, sizeof(ser)) == SOCKET_ERROR) {
        printf("bind failed: %d\n", WSAGetLastError());
        closesocket(listen_sock);
        WSACleanup();
        return 1;
    }

    // 开始监听
    if (listen(listen_sock, 5) == SOCKET_ERROR) {
        printf("listen failed: %d\n", WSAGetLastError());
        closesocket(listen_sock);
        WSACleanup();
        return 1;
    }
    printf("listening on port %d\n", port); 

    // 设置监听Socket为非阻塞模式
    unsigned long b = 1; 
    if (ioctlsocket(listen_sock, FIONBIO, &b) == SOCKET_ERROR) {
        printf("ioctlsocket failed: %d\n", WSAGetLastError());
        closesocket(listen_sock);
        WSACleanup();
        return 1;
    }

    // 初始化客户端数组
    for(int i = 0; i < MAX_CONS; i++) { 
        client[i].con = false; 
    } 

    // 主循环处理客户端
    while(true) { 
        handle_clients(); 
        Sleep(10); // 避免CPU占用过高
    } 

    WSACleanup();
    return 0;
} 

bool my_accept(_client* x) { 
    int addr_len = sizeof(sockaddr_in); 
    x->cs = accept(listen_sock, (SOCKADDR*)&x->addr, &addr_len); 

    if(x->cs != INVALID_SOCKET ) { 
        std::cout << "New client connected, socket: " << x->cs << std::endl; 
        x->con = true; 
        Server_Status(CLIENT_CON); 
        return true; 
    } else {
        // 非阻塞模式下,没有新连接时返回WSAEWOULDBLOCK,这是正常情况
        int err = WSAGetLastError();
        if (err != WSAEWOULDBLOCK) {
            printf("accept failed: %d\n", err);
        }
    }
    return false; 
} 

bool my_send(_client* x, char* buffer, int sz) { 
    int bytes_sent = send(x->cs, buffer, sz, 0); 
    if(bytes_sent == SOCKET_ERROR || bytes_sent == 0 ) { 
        return false; 
    } 
    return true; 
} 

bool my_recv(_client *x, char* buffer, int sz) { 
    int bytes_recv = recv(x->cs, buffer, sz, 0); 
    if(bytes_recv == SOCKET_ERROR) {
        int err = WSAGetLastError();
        if (err != WSAEWOULDBLOCK) {
            // 客户端断开或出错
            return false;
        }
        return true; // 没有数据,正常返回
    } else if(bytes_recv == 0) { 
        // 客户端断开连接
        return false; 
    } 
    buffer[bytes_recv] = '\0'; // 确保字符串以null结尾
    return true; 
} 

void handle_clients() {
    // 处理新连接
    for(int i = 0; i < MAX_CONS; i++) { 
        if(!client[i].con) { 
            if(my_accept(&client[i])) { 
                break; // 找到一个空位置接受连接后,暂时退出循环
            }
        } 
    } 

    // 处理客户端数据
    char buffer[BF_SZ]; 
    for(int i = 0; i < MAX_CONS; i++) { 
        if(client[i].con) { 
            if(!my_recv(&client[i], buffer, BF_SZ-1)) { 
                // 客户端断开
                closesocket(client[i].cs); 
                client[i].con = false; 
                Server_Status(CLIENT_DIS); 
            } else if(strlen(buffer) > 0) { 
                if(buffer[0] == '/') { 
                    if(strcmp(buffer, "/server_bang") == 0) { 
                        chat_message("** Hi**"); 
                    } 
                } else { 
                    chat_message(buffer); 
                } 
                memset(buffer, 0, BF_SZ); // 清空缓冲区
            } 
        } 
    } 
}

void Server_Status(int msg) { 
    if(msg == CLIENT_CON) { 
        PR_CONS++; 
        printf("Client connected! Total clients: %d\n", PR_CONS); 
    } else if(msg == CLIENT_DIS) { 
        PR_CONS--; 
        printf("Client disconnected! Total clients: %d\n", PR_CONS); 
    } else { 
        printf("Unknown status message\n"); 
    } 
} 

void chat_message(char* s) { 
    int len = strlen(s); 
    for(int i = 0; i < MAX_CONS; i++) { 
        if(client[i].con) { 
            if(!my_send(&client[i], s, len)) { 
                // 发送失败,标记客户端断开
                closesocket(client[i].cs); 
                client[i].con = false; 
                Server_Status(CLIENT_DIS); 
            } 
        } 
    } 
}

额外说明

  • 我把监听Socket改名为listen_sock,彻底避免了变量名冲突;
  • 增加了WinSock初始化、Socket创建等步骤的错误检查,便于调试定位问题;
  • 在主循环中加入了Sleep(10),避免程序占用过多CPU资源;
  • 修正了recv函数的处理逻辑,确保字符串以null结尾,避免乱码;
  • 当发送或接收失败时,正确关闭客户端Socket并标记为未连接状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:32:18