我对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上没有。
我对此问题的真正问题是,我不知道如何检查连接是否仍然存在。如何检查连接是否仍然存在?或者如何指定超时(两次从服务器接收的数据包之间的最大时间)?
答案 0 :(得分:1)
默认情况下,套接字以阻塞模式运行,因此唯一可以获得WSAEWOULDBLOCK
错误的方法是明确将套接字置于非阻塞模式。这样做,即表示您同意处理WSAEWOULDBLOCK
(否则,请勿使用非阻止模式)。
WSAEWOULDBLOCK
不是真正的错误,它只是表示您尝试执行的操作当时无法完成,因为它会阻止调用线程。您需要检测此“错误”并稍后再次重试相同的操作,最好是在检测到套接字状态更改后。
对于recv()
,WSAEWOULDBLOCK
只表示此时要读取的套接字上没有可用数据。在非阻止模式下,您应该使用select()
(或WSAEventSelect()
,或WSAAsyncSelect()
,或重叠I / O或I / O完成端口)来检测入站数据然后阅读它。
话虽如此,您正在实施HTTP客户端,因此无论您使用的套接字缓冲区大小如何,必须正确遵循HTTP协议,无论您使用的套接字I / O模式如何。您必须遵循我在this answer上的another question中列出的伪代码逻辑:
您必须遵循RFC 2616中列出的规则。即:
读取直到遇到
"\r\n\r\n"
序列。不要再阅读那些字节了。根据RFC 2616 Section 4.4中的规则分析收到的标头。它们会告诉您剩余响应数据的实际格式。
按照#2中发现的格式读取数据。
如果响应使用HTTP 1.1,则检查收到的标头是否存在
Connection: close
标头;如果响应使用HTTP 0.9或1.0,则检查缺少Connection: keep-alive
标头。如果检测到,请关闭套接字连接的末尾,因为服务器正在关闭它的末尾。否则,保持连接打开并重新使用它以用于后续请求(除非您使用连接完成,在这种情况下请关闭它)。- 醇>
根据需要处理收到的数据。
简而言之,你需要做更像这样的事情(伪代码):
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,否则将报告错误(通常为WSAECONNABORTED
或WSAECONNRESET
,尽管如此可以是其他人)。但异常断开连接可能需要很长时间才能检测到,因此您应该在代码中实现超时。在同步模式下,您可以使用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一定存在错误。