关于windows sock的问题,如何解决?

最近在研究windows sock
dev c++跑起来后
打印了:
Connected to server

Error receiving data: 10057
客户端代码(隐蔽了服务器地址)

#include <Winsock2.h>
#include <iostream>
#include <string>
#include <conio.h>
#pragma comment(lib, "ws2_32.lib") //链接到winsock库
using namespace std;

int main()
{
    // 初始化 Winsock 库
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != NO_ERROR) {
        cerr << "Error initializing socket library" << endl;
        return 1;
    }

    // 创建用于通信的套接字
    SOCKET sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd == INVALID_SOCKET) {
        cerr << "\nSocket creation failed: "<< WSAGetLastError() << endl;
        WSACleanup();
        return 1;
    }

    // 将套接字设置为非阻塞模式
    unsigned long blocking_mode = 1; // 0=non-blocking, 1=blocking mode
    if (ioctlsocket(sockfd, FIONBIO, &blocking_mode) != NO_ERROR) {
        cerr << "\nFailed to set non-blocking mode" << endl;
        closesocket(sockfd);
        WSACleanup();
        return 1;
    }

    // 获取要连接的主机和端口号,这里以localhost为例
    string host = "43.138.236.72"; //localhost
    int port = 1160;

    // 连接服务器
    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(host.c_str());
    int connect_result = connect(sockfd, (sockaddr *)&server_addr, sizeof(server_addr));

    if (connect_result == SOCKET_ERROR) {
        int err = WSAGetLastError();
        if (err == WSAEWOULDBLOCK || err == WSAEINPROGRESS) {
            // 连接正在进行中,这是正常行为
        } else {
            cerr << "\nFailed to connect: " << err << endl;
            closesocket(sockfd);
            WSACleanup();
            return 1;
        }
    }

    cout << "\nConnected to server" << endl;
    string input;
    while(true) {
        // 检查是否有来自服务器的新消息
        char buffer[1024];
        int recv_len = recv(sockfd, buffer, sizeof(buffer), 0);
        if (recv_len > 0) {
            buffer[recv_len] = '\0'; // 在结尾添加 null 字符以便于打印字符串
            cout <<"\nServer says: " << buffer << endl;
            cout<<input;
        } else {
            int err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) {
                cerr << "\nError receiving data: " << err << endl;
                break;
            }
        }

        // 从标准输入读取信息并向服务器发送
        if(_kbhit())
        {
            char ch;
            ch=_getch(); 
            if(cin.fail()) { // 如果无法读取输入,则表示用户已经通过Ctrl+Z或Ctrl+C退出
                break;
            }
            if(ch) 
            {
                if(ch=='\n')
                {
                    int send_len = send(sockfd, input.c_str(), input.length(), 0);
                    if (send_len == SOCKET_ERROR) 
                    {
                        int err = WSAGetLastError();
                        if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) 
                        {
                            cerr << "\nFailed to send data: ";
                        }
                    }
                    input.clear();
                }
                else
                {
                    input=+ch;
                }
            }
        }
        
    }
}

服务端:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>

using namespace std;

int main() {
    // 创建Socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Failed to create socket");
        return -1;
    }

    // 绑定地址和端口号
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;                        // IPv4协议族
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);         // 自动获取IP地址
    server_addr.sin_port = htons(1160);                      // 端口号为12345

    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {//绑定套接字
        perror("Failed to bind socket");
        close(sockfd);
        return -1;
    }

    // 开始监听
    if (listen(sockfd, 2) < 0) {           //监听 二三参分别为创建套接字,允许建立连接队列长度。
        perror("Failed to listen on socket");
        close(sockfd);
        return -1;
    }
  
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);//设置为非阻塞模式

    // 创建用于accept的变量,接受客户端连接
    int client_sockfd[2] = {0,0};
    sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);
    int connected_clients = 0;
    cout<<"server ok"<<endl;
    while (connected_clients < 2) {
        fd_set readset;
        FD_ZERO(&readset);
        FD_SET(sockfd, &readset);

        struct timeval tv;
        tv.tv_sec = 0.1; // 超时时间为0.1秒
        tv.tv_usec = 0;

        int ret = select(sockfd + 1, &readset, NULL, NULL, &tv); //在select函数阻塞期间内核会持续监控输入参数中描述符上的读事件是否发生
        if(connected_clients!=0)
        {
            if (ret == -1) {    //错误处理
                perror("Failed to select on socket");
                return -1;
            } else if (ret == 0) {   //超时处理
                printf("Timeout occurred. There are %d clients connected.\n", connected_clients);
                continue;// 回到while循环开始等待新的套接字连接
            }
    
            // 如果有客户端发起了连接请求
            if (FD_ISSET(sockfd, &readset)) {
                int new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &length); //接受客户端连接,返回一个新的socket描述符
                if (new_sockfd < 0) {   //错误处理
                    perror("Failed to accept socket");
                } else {
                    printf("Client %d connected.\n", connected_clients+1);
                    client_sockfd[connected_clients] = new_sockfd;//保存客户端套接字描述符
                    connected_clients++;
                    if(connected_clients==1)
                    {
                        continue;
                    }
                }
            }
        }
        
    }

    // 接收和转发消息
    while (true) {
        fd_set readset;
        FD_ZERO(&readset);//清空集合

        for (int i = 0; i < 2; i++) {
            FD_SET(client_sockfd[i], &readset);//将两个客户端的描述符加入监听集合
        }

        struct timeval tv;
        tv.tv_sec = 0.1;     // 超时时间为0.1秒
        tv.tv_usec = 0;

        int ret = select(client_sockfd[1] + 1, &readset, NULL, NULL, &tv); //在select函数阻塞期间内核会持续监控输入参数中描述符上的读事件是否发生
        if (ret == -1) {    //错误处理
            perror("Failed to select on socket");
            break;
        } else if (ret == 0) {   //超时处理
            printf("Timeout occurred. Waiting for new messages...\n");
            continue;
        }

        // 如果有客户端发送了新消息
        for (int i = 0; i < 2; i++) {
            if (FD_ISSET(client_sockfd[i], &readset)) {
                char buffer[1024];
                memset(buffer, 0, sizeof(buffer));
                int len = recv(client_sockfd[i], buffer, sizeof(buffer), 0);
                if (len <= 0) {    //连接关闭
                    printf("Client %d closed.\n", i+1);
                    close(client_sockfd[i]);
                    client_sockfd[i] = 0;
                } else {
                    printf("Received from client %d: %s", i+1, buffer);
                    if (i == 0 && client_sockfd[1] != 0) { //转发给另一个客户端
                        send(client_sockfd[1], buffer, strlen(buffer), 0);
                    } else if (i == 1 && client_sockfd[0] != 0) {
                        send(client_sockfd[0], buffer, strlen(buffer), 0);
                    }
                }
            }
        }
    }

    // 关闭套接字
    for (int i = 0; i < 2; i++) {
        if (client_sockfd[i] != 0) {
            close(client_sockfd[i]);
        }
    }
    close(sockfd);

    return 0;
}

根据错误码10057,该错误是一个“socket已经连接但不能发送/接收”的错误。这可能是由于套接字设置为非阻塞模式并且正在等待另一个操作完成,例如connect()或accept()。建议将客户端和服务器端套接字都改回阻塞模式,以便避免此问题。可以使用以下代码进行修改:

// 将套接字设置为阻塞模式
unsigned long blocking_mode = 0; // 0=blocking mode, 1=non-blocking
if (ioctlsocket(sockfd, FIONBIO, &blocking_mode) != NO_ERROR) {
cerr << "\nFailed to set blocking mode" << endl;
closesocket(sockfd);
WSACleanup();
return 1;
}

类似的,也需要将服务器端套接字设置为阻塞模式,如下所示:

int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK); // 取消非阻塞模式

注意,代码中的这种阻塞方式可能会大幅降低性能,因此在实际使用时,应谨慎选择是否要使用阻塞模式或非阻塞模式。

非阻塞sock,最好使用select进行事件监听,当可写/可读时,再进行对应的操作。
参考示例如下:


#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib") // 链接ws2_32.lib库文件

#define MAX_CLIENT_NUM 10 // 最大客户端数量

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET serverSocket, clientSocket;
    SOCKADDR_IN serverAddr, clientAddr;
    int addrLen = sizeof(SOCKADDR_IN);
    int recvBytes, sendBytes;
    char recvBuf[1024], sendBuf[1024];
    fd_set readSet, writeSet;
    int maxFd, clientNum = 0;
    SOCKET clientSockets[MAX_CLIENT_NUM];

    // 初始化WSA
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("WSAStartup failed!\n");
        return -1;
    }

    // 创建socket
    serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        printf("create socket failed!\n");
        return -1;
    }

    // 绑定地址和端口
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(12345);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
    {
        printf("bind failed!\n");
        closesocket(serverSocket);
        return -1;
    }

    // 监听socket
    if (listen(serverSocket, 5) == SOCKET_ERROR)
    {
        printf("listen failed!\n");
        closesocket(serverSocket);
        return -1;
    }

    printf("server is listening...\n");

    // 初始化readSet和writeSet
    FD_ZERO(&readSet);
    FD_ZERO(&writeSet);
    FD_SET(serverSocket, &readSet);
    maxFd = serverSocket;

    while (1)
    {
        // 调用select函数
        if (select(maxFd + 1, &readSet, &writeSet, NULL, NULL) == SOCKET_ERROR)
        {
            printf("select failed!\n");
            break;
        }

        // 检查serverSocket是否可读
        if (FD_ISSET(serverSocket, &readSet))
        {
            // 接受新连接
            clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);
            if (clientSocket == INVALID_SOCKET)
            {
                printf("accept failed!\n");
                break;
            }

            // 将新连接加入clientSockets数组
            if (clientNum < MAX_CLIENT_NUM)
            {
                clientSockets[clientNum++] = clientSocket;
                printf("new client connected, clientNum=%d\n", clientNum);

                // 将新连接加入readSet
                FD_SET(clientSocket, &readSet);
                if (clientSocket > maxFd)
                    maxFd = clientSocket;
            }
            else
            {
                // 客户端数量达到上限,关闭新连接
                printf("client number is full, refuse new connection!\n");
                closesocket(clientSocket);
            }
        }

        // 检查客户端socket是否可读
        for (int i = 0; i < clientNum; i++)
        {
            if (FD_ISSET(clientSockets[i], &readSet))
            {
                // 接收客户端数据
                recvBytes = recv(clientSockets[i], recvBuf, sizeof(recvBuf), 0);
                if (recvBytes == SOCKET_ERROR || recvBytes == 0)
                {
                    // 客户端断开连接
                    printf("client disconnected, clientNum=%d\n", clientNum - 1);
                    closesocket(clientSockets[i]);

                    // 从clientSockets数组中删除该客户端
                    for (int j = i; j < clientNum - 1; j++)
                        clientSockets[j] = clientSockets[j + 1];

                    clientNum--;

                    // 将该客户端从readSet中删除
                    FD_CLR(clientSockets[i], &readSet);

                    // 更新maxFd
                    if (clientSockets[i] == maxFd)
                    {
                        maxFd = serverSocket;
                        for (int j = 0; j < clientNum; j++)
                        {
                            if (clientSockets[j] > maxFd)
                                maxFd = clientSockets[j];
                        }
                    }
                }
                else
                {
                    // 处理客户端请求
                    printf("recv from client: %s\n", recvBuf);

                    // 发送响应
                    sprintf(sendBuf, "Hello, %s!", recvBuf);
                    sendBytes = send(clientSockets[i], sendBuf, strlen(sendBuf), 0);
                    if (sendBytes == SOCKET_ERROR)
                    {
                        printf("send failed!\n");
                        break;
                    }
                }
            }
        }
    }

    // 关闭socket
    closesocket(serverSocket);

    // 清理WSA
    WSACleanup();

    return 0;
}