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

