我在非阻塞模式下使用套接字,有时WSAConnect函数返回WSAEINVAL错误。 我调查了一个问题并发现,如果之间没有暂停(或者它非常小),就会发生这种情况 WSAConnect函数调用。 有谁知道如何避免这种情况? 您可以在下面找到重现问题的源代码。如果我将Sleep函数中的参数值增加到50或者很大 - 问题消失 附:此问题仅在Windows XP上重现,在Win7上运行良好。
#undef UNICODE
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <iostream>
#include <windows.h>
#pragma comment(lib, "Ws2_32.lib")
static int getError(SOCKET sock)
{
DWORD error = WSAGetLastError();
return error;
}
void main()
{
SOCKET sock;
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
fprintf(stderr, "Socket Initialization Error. Program aborted\n");
return;
}
for (int i = 0; i < 1000; ++i) {
struct addrinfo hints;
struct addrinfo *res = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
if (0 != getaddrinfo("172.20.1.59", "8091", &hints, &res)) {
fprintf(stderr, "GetAddrInfo Error. Program aborted\n");
closesocket(sock);
WSACleanup();
return;
}
struct addrinfo *ptr = 0;
for (ptr=res; ptr != NULL ;ptr=ptr->ai_next) {
sock = WSASocket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol, NULL, 0, NULL); //
if (sock == INVALID_SOCKET)
int err = getError(sock);
else {
u_long noblock = 1;
if (ioctlsocket(sock, FIONBIO, &noblock) == SOCKET_ERROR) {
int err = getError(sock);
closesocket(sock);
sock = INVALID_SOCKET;
}
break;
}
}
int ret;
do {
ret = WSAConnect(sock, ptr->ai_addr, (int)ptr->ai_addrlen, NULL, NULL, NULL, NULL);
if (ret == SOCKET_ERROR) {
int error = getError(sock);
if (error == WSAEWOULDBLOCK) {
Sleep(5);
continue;
}
else if (error == WSAEISCONN) {
fprintf(stderr, "+");
closesocket(sock);
sock = SOCKET_ERROR;
break;
}
else if (error == 10037) {
fprintf(stderr, "-");
closesocket(sock);
sock = SOCKET_ERROR;
break;
}
else {
fprintf(stderr, "Connect Error. [%d]\n", error);
closesocket(sock);
sock = SOCKET_ERROR;
break;
}
}
else {
int one = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
fprintf(stderr, "OK\n");
break;
}
}
while (1);
}
std::cout<<"end";
char ch;
std::cin >> ch;
}
答案 0 :(得分:4)
这里有一大堆错误和可疑的设计和编码决策。我将不得不将它们分成两组:
我希望如果你修复了本节中的所有项目,你的症状就会消失,但我不想推测哪一个是关键修复:
在单个套接字上循环调用connect()
是完全错误的。
如果您要建立连接,将其删除并重新建立1000次,则需要在每次循环结束时调用closesocket()
,然后再次调用socket()
以获取新的套接字。您不能继续重新连接相同的套接字。可以把它想象成一个电源插头:如果要将其插入两次,则必须在两次之间拔掉插头(closesocket()
)。
如果您要建立1000个并发连接,则需要在每次迭代socket()
上分配一个带connect()
的新套接字,然后再次返回以获取另一个套接字。除了没有closesocket()
调用之外,它与前一个案例的循环基本相同。
请注意,由于XP是Windows的客户端版本,因此未针对处理数千个同步套接字进行优化。
再次致电connect()
对WSAEWOULDBLOCK
的正确回复:
if (error == WSAEWOULDBLOCK) {
Sleep(5);
continue; /// WRONG!
}
continue
代码有效地提交了与上面相同的错误,但更糟糕的是,如果您只修复了上一个错误并保留此错误,则此用法将使您的代码开始泄漏套接字。
WSAEWOULDBLOCK
is not an error.所有这意味着在非阻塞套接字上的connect()
之后,连接没有立即建立。堆栈将在何时通知您的程序。
您可以通过致电select()
,WSAEventSelect()
或WSAAsyncSelect()
来获取该通知。如果使用select()
,则在建立连接时,套接字将被标记为可写。使用其他两个,当连接建立时,您将收到FD_CONNECT
事件。
要调用这三个API中的哪一个取决于您首先想要非阻塞套接字的原因,以及程序的其余部分是什么样的。到目前为止我所看到的根本不需要非阻塞套接字,但我想你有一些未来的计划可以告知你的决定。我写了一篇文章 Which I/O Strategy Should I Use (the Winsock Programmers' FAQ的一部分),它将帮助您决定使用哪些选项;它可能会完全引导您进入另一个选项。
您不应在同一套接字上使用AI_PASSIVE
和connect()
。您对AI_PASSIVE
getaddrinfo()
的使用告诉您打算使用此套接字接收传入连接的堆栈。然后,您可以使用该套接字建立传出连接。
你基本上在这里撒谎。当你骗他们时,计算机会找到报复的方法。
Sleep()
永远不是解决Winsock问题的正确方法。堆栈中存在程序可以看到的内置延迟,例如TIME_WAIT
和the Nagle algorithm,但Sleep()
也不是解决这些问题的正确方法。
此部分适用于我不希望让您的症状消失的内容,但无论如何您应该考虑修复它们:
使用getaddrinfo()
的主要原因 - 与inet_addr()
等较旧,较简单的功能相反 - 是否必须支持IPv6。这与您支持XP的愿望存在冲突,因为在XP的当前版本作为其IPv4堆栈的时候,XP的IPv6堆栈并没有经过多次测试。我希望XP的IPv6堆栈仍然会有错误,即使你已经安装了所有补丁。
如果您确实不需要IPv6支持,那么以旧方式执行此操作可能会使您的症状消失。您可能最终需要仅针对XP的IPv4构建。
此代码:
for (int i = 0; i < 1000; ++i) {
// ...
if (0 != getaddrinfo("172.20.1.59", "8091", &hints, &res)) {
......效率低下。没有理由需要在每个循环中重新初始化res
。
即使有一些我没有看到的原因,你也不会在freeaddrinfo()
上拨打res
来泄露记忆。
在进入循环之前,您应该初始化一次,然后在每次迭代时重复使用它。
else if (error == 10037) {
为什么不在这里使用WSAEALREADY
?
您无需在此处使用WSAConnect()
。您正在使用Winsock与BSD套接字共享的3参数子集。你也可以在这里使用connect()
。
没有必要让你的代码变得比它更复杂。
为什么不为此使用switch
语句?
if (error == WSAEWOULDBLOCK) {
// ...
}
else if (error == WSAEISCONN) {
// ...
}
// etc.
You shouldn't disable the Nagle algorithm:
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, ...);