Visual c++ 套接字编程:侦听()时出错

Visual c++ 套接字编程:侦听()时出错,visual-c++,winsock,Visual C++,Winsock,我正在开发应用程序的服务器部分,遇到了一个似乎无法解决的问题。服务器初始化函数是ConnectionManager类的一部分,如下所示: int ConnectionManager::init_server() { // Log OutputDebugString(L"> Initializing server...\n"); // Initialize winsock WSAData wsa; int code = WSAStartup(MAK

我正在开发应用程序的服务器部分,遇到了一个似乎无法解决的问题。服务器初始化函数是ConnectionManager类的一部分,如下所示:

int ConnectionManager::init_server() {

    // Log
    OutputDebugString(L"> Initializing server...\n");

    // Initialize winsock
    WSAData wsa;
    int code = WSAStartup(MAKEWORD(2, 2), &wsa);
    if (code != 0) {
        // Error initializing winsock
        OutputDebugString(L"> Log: WSAStartup()\n");
        output_error(code);
        return -1;
    }

    // Get server information
    struct addrinfo hints, *serverinfo, *ptr;
    SOCKET sockfd = INVALID_SOCKET;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_protocol = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;

    if (getaddrinfo(NULL, PORT, &hints, &serverinfo) != 0) {
        // Error when getting server address information
        OutputDebugString(L"> Log: getaddrinfo()\n");
        output_error(WSAGetLastError()); // Call Cleanup?
        return -1;
    }

    for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
        // Create socket
        if ((sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == INVALID_SOCKET) {
            // Error when creating a socket
            OutputDebugString(L"> Log: socket()\n");
            output_error(WSAGetLastError()); // Call Cleanup?
            continue;
        }

        // Set options
        const char enable = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) == SOCKET_ERROR) {
            // Error when setting options
            OutputDebugString(L"> log: setsockopt()\n");
            output_error(WSAGetLastError()); // call cleanup?
            if (closesocket(sockfd) != 0) {
                output_error(WSAGetLastError());
            }
            return -1;
        }

        // Bind socket
        if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
            // Error on binding
            OutputDebugString(L"> Log: bind()\n");
            output_error(WSAGetLastError()); // Call Cleanup?
            if (closesocket(sockfd) != 0) {
                output_error(WSAGetLastError());
            }
            continue;
        }    

        break;
    }
    freeaddrinfo(serverinfo);
    if (ptr == NULL) {
        OutputDebugString(L"Error: Failed to launch server.\n");
        return -1;
    }
    // Listen
    if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
        OutputDebugString(L"> Log: listen()\n");
        output_error(WSAGetLastError()); // Call Cleanup?;
        return -1;
    }
    // Accept
    struct sockaddr_storage clientinfo;
    int size = sizeof(struct sockaddr_storage);
    m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
    if (m_exchfd = INVALID_SOCKET) {
        // Error when accepting
        OutputDebugString(L"> Log: accept()\n");
        output_error(WSAGetLastError()); // Call Cleanup?
        if (closesocket(sockfd) != 0) {
            output_error(WSAGetLastError());
        }
        return -1;
    }
    m_isConnected = true;
    return 0;
}
output_error函数只是使用FormatMessage函数打印与错误对应的消息。但是,我得到以下输出:

> Log: listen()
> ERROR: The attempted operation is not supported for the type of object referenced.

因此,错误应该是由调用listen引起的,这会造成混淆。谁能解释一下问题的原因吗?我打赌它应该很容易修复,但我就是看不到。

问题的根源在于调用getaddrinfo时,您填写的提示结构不正确

您正在将SOCK_流分配给ai_协议字段,并将ai_socktype字段设置为0。SOCK_流定义为1,它与IPPROTO_ICMP的值相同,IPPROTO_ICMP通常与SOCK_DGRAM套接字一起使用。因此,getaddrinfo很可能返回其ai_socktype字段设置为SOCK_DGRAM的addrinfo条目。您不能在数据报套接字上使用侦听,因此您会看到WSAEOPNOTSUPP错误:

如果没有发生错误,listen返回零。否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError检索特定的错误代码

WSAEOPNOTPUP 引用的套接字的类型不支持侦听操作

您需要将SOCK_流指定给hints.ai_socktype字段,并将hints.ai_protocol字段设置为0或IPPROTO_TCP(最好是后者)

此外,getaddrinfo返回一个错误代码,就像WSAStartup一样。不要使用WSAGetLastError获取其错误代码

除此之外,我发现您的代码还存在许多其他问题

因此,REUSEADDR需要一个BOOL 4字节整数,而不是char 1字节。您正在传入一个指向单个字符的指针,但告诉setsockopt您正在传入一个指向int的指针。setsockopt最终将尝试从您不拥有的堆栈内存中读取一个值

当您的循环调用closesocket时,您也应该将sockfd重置为无效的_SOCKET,然后在循环之后,您应该检查该条件,而不是检查ptr是否为NULL

您应该在循环内调用listen,而不是在循环后调用。成功绑定套接字并不保证可以打开其分配的侦听端口。在实际成功打开侦听端口之前,应该一直循环。您还可以考虑在循环之后添加一个额外的日志消息,以知道哪个本地IP /端口对实际上在监听,因此您知道客户端可以连接到什么。 调用WSAGetLastError时,始终在Winsock调用失败后立即调用它。如果您事先调用了其他任何东西,则可能会重置错误代码,因为WSAGetLastError只是许多API使用的GetLastError的别名

调用accept时,在检查m_exchfd是否等于INVALID_SOCKET时,使用的是=赋值运算符而不是==比较运算符。即使在修复该问题之后,如果accept成功,也会泄漏sockfd,因为您无法跟踪它,并且没有对其调用closesocket。如果只希望连接一个客户端,请在接受该客户端后关闭侦听套接字。否则,将侦听套接字存储在类中,并在关闭已接受的客户端套接字后将其关闭

话虽如此,不妨尝试以下方式:

void OutputWinsockError(LPCWSTR funcName, int errCode)
{
    std::wostringstream msg;
    msg << L"> Log: " << funcName << L"()\n";
    OutputDebugStringW(msg.str().c_str());
    output_error(errCode);
}

void OutputWinsockError(LPCWSTR funcName)
{
    OutputWinsockError(funcName, WSAGetLastError());
}

int ConnectionManager::init_server() {

    // Log
    OutputDebugString(L"> Initializing server...\n");

    // Initialize winsock
    WSAData wsa;
    int err = WSAStartup(MAKEWORD(2, 2), &wsa);
    if (err != 0) {
        // Error initializing winsock
        OutputWinsockError(L"WSAStartup", err);
        return -1;
    }

    // Get server information
    struct addrinfo hints, *serverinfo, *ptr;
    SOCKET sockfd = INVALID_SOCKET;

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    err = getaddrinfo(NULL, PORT, &hints, &serverinfo);
    if (err != 0) {
        // Error when getting server address information
        OutputWinsockError(L"getaddrinfo", err);
        // Call Cleanup?
        return -1;
    }

    for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
        // Create socket
        sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (sockfd == INVALID_SOCKET) {
            // Error when creating a socket
            OutputWinsockError(L"socket");
            continue;
        }

        // Set options
        const BOOL enable = TRUE;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(BOOL)) == SOCKET_ERROR) {
            // Error when setting options
            OutputWinsockError(L"setsockopt");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }

        // Bind socket
        if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
            // Error on binding
            OutputWinsockError(L"bind");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }    

        // Listen on port
        if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
            // Error on listening
            OutputWinsockError(L"listen");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }

        break;
    }

    freeaddrinfo(serverinfo);

    if (sockfd == INVALID_SOCKET) {
        OutputDebugString(L"Error: Failed to launch server.\n");
        // Call Cleanup?
        return -1;
    }

    // Accept
    struct sockaddr_storage clientinfo;
    int size = sizeof(clientinfo);

    m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
    if (m_exchfd == INVALID_SOCKET) {
        // Error when accepting
        OutputWinsockError(L"accept");
        if (closesocket(sockfd) == SOCKET_ERROR) {
            OutputWinsockError(L"closesocket");
        }
        // Call Cleanup?
        return -1;
    }

    m_isConnected = true;

    // is not storing sockfd, close it

    // m_listenfd = sockfd;
    if (closesocket(sockfd) == SOCKET_ERROR) {
        OutputWinsockError(L"closesocket");
    }

    return 0;
}

我试着根据你的评论修改我的程序,非常感谢你的评论,但是我仍然得到同样的错误,现在在两个地址上都找到了。另外,为什么在setsockopt函数中使用BOOL?根据它的原型,应该使用char,不是吗?当侦听失败时,ptr->ai_系列、ptr->ai_socktype和ptr->ai_协议的实际值是多少?如果它们分别不是AF_INET/6、SOCK_STREAM和IPPROTO_TCP,那么就出了问题。哦,等等,我刚刚在你的代码中看到另一个错误。我已经编辑了我的答案。请阅读。所以博士希望有一场战争。不要被setsockopt的签名所愚弄,它在逻辑上使用了char*指针,它应该使用void*。这是历史遗留下来的。它实际上可以接受任何类型的指针,它只需要类型转换。与其他几个套接字API函数一样,最明显的是那些声明为接受sockaddr*指针但实际上可以接受sockaddr_XX*指针的函数。将ai_协议更改为ai_socktype似乎解决了这个问题。谢谢!