c++ - Windows XP 套接字错误与 recv()

标签 c++ windows sockets mfc

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

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

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

我将缓冲区大小 (SO_RCVBUF) 设置为 1000 字节。在 Windows 7 上,这也会反射(reflect)到 TCP 窗口大小 - 在 XP 上则不会。

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

最佳答案

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

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

对于recv()WSAEWOULDBLOCK 只是意味着此时套接字上没有数据可供读取。在非阻塞模式下,您应该使用 select()(或 WSAEventSelect(),或 WSAAsyncSelect(),或 Overlapped I/O ,或 I/O 完成端口)以在读取入站数据之前检测它。

也就是说,您正在实现一个 HTTP 客户端,因此您必须正确地遵循 HTTP 协议(protocol),无论您使用的套接字 I/O 模式如何,无论您的套接字缓冲区大小如何。您必须遵循我在 this answer 中概述的伪代码逻辑在 another question :

You must follow the rules outlined in RFC 2616. Namely:

  1. Read until the "\r\n\r\n" sequence is encountered. Do not read any more bytes past that yet.

  2. Analyze the received headers, per the rules in RFC 2616 Section 4.4. They tell you the actual format of the remaining response data.

  3. Read the data per the format discovered in #2.

  4. Check the received headers for the presence of a Connection: close header if the response is using HTTP 1.1, or the lack of a Connection: keep-alive header if the response is using HTTP 0.9 or 1.0. If detected, close your end of the socket connection because the server is closing its end. Otherwise, keep the connection open and re-use it for subsequent requests (unless you are done using the connection, in which case do close it).

  5. Process the received data as needed.

In short, you need to do something more like this instead (pseudo code):

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 协议(protocol)的方式。

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

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

关于c++ - Windows XP 套接字错误与 recv(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43126714/

相关文章:

c - 什么是 TpCallbackMayRunLong()?

c++ - 锁定多个线程

c++ - (C++内存编辑)将 "THREADSTACK0"转换为地址

windows - 您可以为 Windows CMD 命令设置键盘快捷键吗?

java - gcloud Preview 应用程序在 Windows 上运行 "bad port Access Denied"错误

linux - 怎么做像 "netstat -p",但更快?

java - 为什么两个 Java 进程可以绑定(bind)到 macOS 中的同一个套接字?

java - 通过套接字发送字符串。服务器没有收到

c++ - gmp 使用 mpz_pow 函数给出错误

c++ - 计算碰撞中两个正​​方形的重叠