繁体   English   中英

Windows XP套接字错误与recv()

[英]Windows XP socket error with recv()

我使用recv()函数的行为异常。

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

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

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

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

默认情况下,套接字在阻塞模式下运行,因此,根本无法获得WSAEWOULDBLOCK错误的唯一方法是将套接字显式地置于非阻塞模式。 这样做,您同意处理WSAEWOULDBLOCK (否则,请不要使用非阻塞模式)。

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

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

话虽这么说,您正在实现一个HTTP客户端,所以无论您使用的套接字I / O模式如何,无论套接字缓冲区的大小如何,都必须正确地遵循HTTP协议。 您必须在另一个问题上遵循我在此答案中概述的伪代码逻辑:

必须遵循RFC 2616中概述的规则。 即:

  1. 读取直到遇到"\\r\\n\\r\\n"序列。 不要再读取更多的字节了。

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

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

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

  5. 根据需要处理接收到的数据。

简而言之,您需要执行以下操作(伪代码):

 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()

您不能期望recv在无阻塞套接字上提供任何数据。 如果没有可用数据,则返回WOULDBLOCK。 您只需要再次调用recv (通常在select通知您一些数据可用之后)。 是否在第一个(或任何一个)调用中获取数据将取决于服务器发送数据的速度。

关闭套接字后,您将收到与recv不同的错误,例如WSAECONNRESET或WSAENOTCONN。 当插座关闭时, select也会通知您。

真奇怪

今天,我已更改软件以使用阻止套接字。 但是它仍然不能在Windows XP上运行。 Windows 7没问题。

所以我想:让我们尝试另一台PC。 在此PC(也为Windows XP)上,它可以正常工作。 现在,我尝试了使用Windows XP的第三台PC,在这里也可以使用。

我仍然不知道问题出在哪里,但我认为PC肯定有错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM