使用recv()

时间:2017-03-30 19:22:46

标签: c++ windows sockets mfc

我对recv()函数有一种奇怪的行为。

使用WinSock的My C ++(MFC)应用程序实现了一个简单的HTTP客户端(非阻塞套接字),用于访问Web服务器上的HTML页面。其中一些页面需要几秒钟才能加载。在Windows 7上,这不是问题,因为recv()也返回部分数据。但是在Windows XP上,recv()函数总是返回SOCKET_ERROR,错误代码是WSAEWOULDBLOCK。只有在连接完成后,才会在一次访问中返回数据。

有谁知道这个问题?如何强制Windows XP也接收部分数据?

我将缓冲区大小(SO_RCVBUF)设置为1000字节。在Windows 7上,这也反映在TCP窗口大小上 - 在XP上没有。

我对此问题的真正问题是,我不知道如何检查连接是否仍然存在。如何检查连接是否仍然存在?或者如何指定超时(两次从服务器接收的数据包之间的最大时间)?

3 个答案:

答案 0 :(得分:1)

默认情况下,套接字以阻塞模式运行,因此唯一可以获得WSAEWOULDBLOCK错误的方法是明确将套接字置于非阻塞模式。这样做,即表示您同意处理WSAEWOULDBLOCK(否则,请勿使用非阻止模式)。

WSAEWOULDBLOCK不是真正的错误,它只是表示您尝试执行的操作当时无法完成,因为它会阻止调用线程。您需要检测此“错误”并稍后再次重试相同的操作,最好是在检测到套接字状态更改后。

对于recv()WSAEWOULDBLOCK只表示此时要读取的套接字上没有可用数据。在非阻止模式下,您应该使用select()(或WSAEventSelect(),或WSAAsyncSelect(),或重叠I / O或I / O完成端口)来检测入站数据然后阅读它。

话虽如此,您正在实施HTTP客户端,因此无论您使用的套接字缓冲区大小如何,必须正确遵循HTTP协议,无论您使用的套接字I / O模式如何。您必须遵循我在this answer上的another question中列出的伪代码逻辑:

  

必须遵循RFC 2616中列出的规则。即:

     
      
  1. 读取直到遇到"\r\n\r\n"序列。不要再阅读那些字节了。

  2.   
  3. 根据RFC 2616 Section 4.4中的规则分析收到的标头。它们会告诉您剩余响应数据的实际格式。

  4.   
  5. 按照#2中发现的格式读取数据。

  6.   
  7. 如果响应使用HTTP 1.1,则检查收到的标头是否存在Connection: close标头;如果响应使用HTTP 0.9或1.0,则检查缺少Connection: keep-alive标头。如果检测到,请关闭套接字连接的末尾,因为服务器正在关闭它的末尾。否则,保持连接打开并重新使用它以用于后续请求(除非您使用连接完成,在这种情况下请关闭它)。

  8.   
  9. 根据需要处理收到的数据。

  10.         

    简而言之,你需要做更像这样的事情(伪代码):

    string headers[];
    byte data[];
    
    string statusLine = read a CRLF-delimited line;
    int statusCode = extract from status line;
    string responseVersion = extract from status line;
    
    do
    {
        string header = read a CRLF-delimited line;
        if (header == "") break;
        add header to headers list;
    }
    while (true);
    
    if ( !((statusCode in [1xx, 204, 304]) || (request was "HEAD")) )
    {
        if (headers["Transfer-Encoding"] ends with "chunked")
        {
            do
            {
                string chunk = read a CRLF delimited line;
                int chunkSize = extract from chunk line;
                if (chunkSize == 0) break;
    
                read exactly chunkSize number of bytes into data storage;
    
                read and discard until a CRLF has been read;
            }
            while (true);
    
            do
            {
                string header = read a CRLF-delimited line;
                if (header == "") break;
                add header to headers list;
            }
            while (true);
        }
        else if (headers["Content-Length"] is present)
        {
            read exactly Content-Length number of bytes into data storage;
        }
        else if (headers["Content-Type"] == "multipart/byteranges")
        {
            string boundary = extract from Content-Type header;
            read into data storage until terminating boundary has been read;
        }
        else
        {
            read bytes into data storage until disconnected;
        }
    }
    
    if (!disconnected)
    {
        if (responseVersion == "HTTP/1.1")
        {
            if (headers["Connection"] == "close")
                close connection;
        }
        else
        {
            if (headers["Connection"] != "keep-alive")
                close connection;
        }
    }
    
    check statusCode for errors;
    process data contents, per info in headers list;
    

如您所见,HTTP需要读取CRLF分隔的文本行或固定长度的原始字节。为此,您必须在循环中调用recv(),直到遇到终止CRLF,或者已经收到预期的字节数,无论哪种情况。是否使用在循环时忽略WSAEWOULDBLOCK错误的同步循环,或者使用由异步事件/回调驱动的状态机,由您决定。这并不会改变您必须处理HTTP协议的方式。

这适用于所有版本的Windows(甚至所有使用BSD样式套接字API的平台)。你遇到的不是Windows漏洞。这是您理解如何正确有效地使用套接字I / O的潜在缺陷。

至于检查连接是否处于活动状态,如果服务器正常关闭连接,recv()将返回0,否则将报告错误(通常为WSAECONNABORTEDWSAECONNRESET,尽管如此可以是其他人)。但异常断开连接可能需要很长时间才能检测到,因此您应该在代码中实现超时。在同步模式下,您可以使用setsockopt(SO_RCVTIMEO)。在非阻止模式下,您可以使用select()。在异步(重叠)模式下,您可以在用于驱动状态机的任何事件/对象上使用WaitForSingleObject()

答案 1 :(得分:0)

您不能指望recv为您提供有关非阻塞套接字的任何数据。如果没有可用数据,则返回WOULDBLOCK。您只需再次致电recv(通常在select通知您某些数据可用后)。您是否获得第一个(或任何)呼叫的数据将取决于服务器发送它的速度。

当套接字关闭时,你会得到一个与recv不同的错误,比如WSAECONNRESET或WSAENOTCONN。 select也会在套接字关闭时通知您。

答案 2 :(得分:0)

这很奇怪。

今天我已将我的软件更改为使用阻塞套接字。但它仍然无法在Windows XP上运行。 Windows 7没问题。

所以我想:让我们尝试另一台电脑。在这台PC(也是Windows XP)上它确实有效。现在我尝试使用Windows XP的第三台PC,这里也可以使用。

我仍然不知道问题是什么,但我认为PC一定存在错误。