套接字编程:listen()出错

时间:2017-08-04 15:38:05

标签: visual-c++ winsock

我正在处理我的应用程序的服务器部分,我遇到了一个似乎无法解决的问题。服务器初始化函数是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()的调用引起的,这是令人困惑的。谁能解释一下问题的原因是什么?我打赌它应该是容易修复的东西,但我似乎没有看到它。

1 个答案:

答案 0 :(得分:1)

问题的根源是,在调用getaddrinfo()时,您正在错误地填写hints结构。

您要将SOCK_STREAM分配到ai_protocol字段,并将ai_socktype字段设置为0. SOCK_STREAM定义为1,这与{的值相同{1}},通常与IPPROTO_ICMP套接字一起使用。因此,SOCK_DGRAM可能会返回getaddrinfo()字段设置为addrinfo的{​​{1}}条目。您无法在数据报套接字上使用ai_socktype,因此您会看到SOCK_DGRAM错误:

  

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

     

...

     

WSAEOPNOTSUPP
  引用的套接字不属于支持SOCKET_ERROR操作的类型

您需要将WSAGetLastError分配给listen字段,并将SOCK_STREAM字段设置为0或hints.ai_socktype(最好是后者)。

此外,hints.ai_protocol会返回错误代码,就像IPPROTO_TCP一样。不要使用getaddrinfo()来获取错误代码。

除此之外,我还看到了您的代码中的许多其他问题。

  • WSAStartup()需要WSAGetLastError()(4字节整数),而不是SO_REUSEADDR(1字节)。您正在传递指向单个BOOL的指针,但告诉char您正在传递指向char的指针。 setsockopt()最终会尝试从您不拥有的堆栈内存中读取值。

  • 当您的循环调用int时,您应该将setsockopt()重置为closesocket(),然后在循环之后您应该检查该条件而不是检查{{1对于NULL。

  • 您应该在循环中调用sockfd而不是在循环之后。仅仅因为INVALID_SOCKET套接字成功并不能保证您可以打开其指定的侦听端口。您应该保持循环,直到您实际成功打开侦听端口。您可能还会考虑在循环后添加额外的日志消息,以了解哪个本地IP /端口对实际正在侦听,因此您知道哪些客户端能够ptr到。

  • 调用listen()时,在Winsock调用失败后,请务必立即将其命名为 。如果您事先调用其他任何内容,则可能会重置错误代码,因为bind()只是许多API使用的connect()的别名。

  • 调用WSAGetLastError()时,在检查WSAGetLastError()是否等于{{1}时,您使用GetLastError()赋值运算符而不是accept()比较运算符}。即使您解决了这个问题,如果=成功,您就会泄漏==,因为您已经忘记了它并且没有在其上调用m_exchfd。如果您只希望连接一个客户端,请在接受客户端后关闭侦听套接字。否则,将侦听套接字存储在类中,并在关闭接受的客户端套接字后将其关闭。

所有这些都说,尝试更像这样的事情:

INVALID_SOCKET