Socket编程:Error()上的错误()

问题描述:

我正在使用我的应用程序的服务器部分,并且遇到了一个我似乎无法解决的问题。服务器初始化功能,这是ConnectionManager类的一部分,如下:Socket编程:Error()上的错误()

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函数简单地打印对应于使用​​函数的错误消息。不过,我得到以下输出:

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

因此,误差应该通过调用listen()这是混乱引起的。任何人都可以解释一下问题的原因是什么?我敢打赌,这应该是一件容易解决的事情,但我似乎没有看到它。

问题的根源在于拨打getaddrinfo()时,您正在填写错误的hints结构。

要分配SOCK_STREAMai_protocol字段,和离开ai_socktype字段设置为0。SOCK_STREAM被定义为1,这是相同的值IPPROTO_ICMP,其通常与SOCK_DGRAM插座使用。因此,getaddrinfo()很可能会返回addrinfo条目,其ai_socktype字段设置为SOCK_DGRAM。你不能在数据报套接字使用listen(),因此WSAEOPNOTSUPP错误您所看到的:

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

...

WSAEOPNOTSUPP
引用的插槽是支持listen操作的类型不。

您需要分配SOCK_STREAMhints.ai_socktype字段代替,并设置hints.ai_protocol字段为0或IPPROTO_TCP(优选后者)。

另外,getaddrinfo()返回错误代码,就像WSAStartup()一样。请勿使用WSAGetLastError()来获取其错误代码。

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

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

  • 当你的循环调用closesocket(),您应该重置sockfdINVALID_SOCKET为好,然后在循环之后,你应该检查该条件,而不是检查ptr为NULL。

  • 您应该在循环中调用listen()而不是在循环之后。仅仅因为你bind()套接字成功并不能保证你可以打开其分配的侦听端口。您应该保持循环,直到您实际成功打开侦听端口。您也可以考虑在循环后添加额外的日志消息,以了解哪个本地IP /端口对实际上正在侦听,以便您知道哪些客户端可以使用connect()

  • 当致电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; 
} 
+0

我试图修改根据您的意见我的程序,非常感谢他们,但我仍然得到同样的错误,现在在两个地址找到。另外,为什么我们在setsockopt()函数中使用BOOL?根据它的原型,应该使用char,而不是它? –

+1

当listen()失败时,'ptr-> ai_family','ptr-> ai_socktype'和'ptr-> ai_protocol'的实际值是多少?如果它们分别不是'AF_INET/6','SOCK_STREAM'和'IPPROTO_TCP',那么出现了问题。哦,等一下,我刚刚看到你的代码中有另一个错误。我编辑了我的答案。 –

+1

阅读['setsockopt()'文档](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740476.aspx)。 'SO_REUSEADDR'需要一个'BOOL'。不要被'setsockopt()'签名'char *'指针所欺骗(在逻辑上,它应该使用'void *'来代替)。这是历史时期的遗留物。它实际上可以采取任何形式的指针,它只需要进行输入。就像其他一些套接字API函数一样,最明显的是那些被声明为使用'sockaddr *'指针但实际上可以使用'sockaddr_XX *'指针的函数。 –