Winsock阻止recv()不返回,而WireShark说TCP流中有传入的段

时间:2015-12-31 23:48:17

标签: tcp winsock wireshark

我正在使用VC ++ 2013社区在Windows 10专业版上使用某些协议,基本上包括三个步骤:

  • 客户端发送GET标头(例如身份验证等)
  • 服务器返回HTTP标头(例如状态代码200,如果一切正常)
  • 然后服务器继续在HTTP标头
  • 之后发送二进制数据流

我发送标头,并在阻塞模式下调用recv()以通过TCP流从服务器接收数据。但是,recv()阻止,永远不会返回。

我使用WireShark来"关注" TCP流,它显示服务器确实继续发送二进制数据,我确实看到来自客户端的ACK消息,以确认它收到的每个段。但是,recv()仍会阻塞,永远不会返回。

我试图使用:

  • WinSock上的纯C实现
  • C#使用TcpClient
  • 使用Boost Asio的C ++
  • 非阻塞WinSock(如this article
  • 第一个版本是在WinHTTP中实现的,最终得到了Timeout。

他们都不能收到任何数据。但是,WireShark仍然可以告诉服务器继续发送二进制数据。

我试图关闭防火墙,但问题仍然存在。

最奇怪的是,我的第一个实现实际上是在两天前成功从recv()获取数据。在那一天,recv()返回三次,然后再次被阻止。第二天,相同的实现,recv()永远不能返回任何东西。

我真的很困惑。谢谢!

以下是阻止Winsock版本的代码:

WSADATA wsaData;
int iResult;

SOCKET ConnectSocket = INVALID_SOCKET;
struct sockaddr_in clientService;


char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;

//----------------------
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
}

//----------------------
// Create a SOCKET for connecting to server
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    WSACleanup();
    return 1;
}

//----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port of the server to be connected to.
clientService.sin_family = AF_INET;

auto ip = gethostbyname(name);
clientService.sin_addr = *(reinterpret_cast<struct in_addr *>(ip->h_addr));
clientService.sin_port = htons(DEFAULT_PORT);

//----------------------
// Connect to server.
iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    printf("Unable to connect to server: %ld\n", WSAGetLastError());
    WSACleanup();
    return 1;
}

// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("Bytes Sent: %ld\n", iResult);

// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

// Receive until the peer closes the connection
do {

    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Bytes received: %d\n", iResult);
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());

} while (iResult > 0);

// cleanup
closesocket(ConnectSocket);
WSACleanup();

return 0;

1 个答案:

答案 0 :(得分:0)

由于关机代码是来自微软页面(https://msdn.microsoft.com/en-us/library/windows/desktop/ms740121(v=vs.85).aspx)的复制/粘贴 - 我想这就是他们指示发送已完成的方式。我相信这解释了您所遇到的问题(来自上页):

注意当发出阻塞的Winsock调用(例如recv)时,Winsock可能需要等待网络事件才能完成调用。在这种情况下,Winsock会执行可警告的等待,这可以通过在同一线程上安排的异步过程调用(APC)来中断。在APC内发出另一个阻塞Winsock调用,该调用在同一个线程上中断正在进行的阻塞Winsock调用将导致未定义的行为,并且永远不会被Winsock客户端尝试。

我实际上刚刚意识到我只使用过非阻塞套接字并且总是在自己的线程上拥有接收代码。所以尝试添加这个: iResult = ioctlsocket(m_socket,FIONBIO,&amp; iMode);

从这里: https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573(v=vs.85).aspx

注意:您将主要尝试从套接字接收数据的一系列失败尝试 - 因此您需要处理这些并且您也不希望紧密循环。