我正在处理我的应用程序的服务器部分,我遇到了一个似乎无法解决的问题。服务器初始化函数是ConnectionManager
类的一部分,如下所示:
int ConnectionManager::init_server() {
// Log
OutputDebugString(L"> Initializing server...\n");
// Initialize winsock
WSAData wsa;
int code = WSAStartup(MAKEWORD(2, 2), &wsa);
if (code != 0) {
// Error initializing winsock
OutputDebugString(L"> Log: WSAStartup()\n");
output_error(code);
return -1;
}
// Get server information
struct addrinfo hints, *serverinfo, *ptr;
SOCKET sockfd = INVALID_SOCKET;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_protocol = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
if (getaddrinfo(NULL, PORT, &hints, &serverinfo) != 0) {
// Error when getting server address information
OutputDebugString(L"> Log: getaddrinfo()\n");
output_error(WSAGetLastError()); // Call Cleanup?
return -1;
}
for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
// Create socket
if ((sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == INVALID_SOCKET) {
// Error when creating a socket
OutputDebugString(L"> Log: socket()\n");
output_error(WSAGetLastError()); // Call Cleanup?
continue;
}
// Set options
const char enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) == SOCKET_ERROR) {
// Error when setting options
OutputDebugString(L"> log: setsockopt()\n");
output_error(WSAGetLastError()); // call cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
return -1;
}
// Bind socket
if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
// Error on binding
OutputDebugString(L"> Log: bind()\n");
output_error(WSAGetLastError()); // Call Cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
continue;
}
break;
}
freeaddrinfo(serverinfo);
if (ptr == NULL) {
OutputDebugString(L"Error: Failed to launch server.\n");
return -1;
}
// Listen
if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
OutputDebugString(L"> Log: listen()\n");
output_error(WSAGetLastError()); // Call Cleanup?;
return -1;
}
// Accept
struct sockaddr_storage clientinfo;
int size = sizeof(struct sockaddr_storage);
m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
if (m_exchfd = INVALID_SOCKET) {
// Error when accepting
OutputDebugString(L"> Log: accept()\n");
output_error(WSAGetLastError()); // Call Cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
return -1;
}
m_isConnected = true;
return 0;
}
output_error
函数只使用FormatMessage()
函数打印与错误对应的消息。但是,我得到以下输出:
> Log: listen()
> ERROR: The attempted operation is not supported for the type of object referenced.
因此,错误应该是由listen()
的调用引起的,这是令人困惑的。谁能解释一下问题的原因是什么?我打赌它应该是容易修复的东西,但我似乎没有看到它。
答案 0 :(得分:1)
问题的根源是,在调用getaddrinfo()
时,您正在错误地填写hints
结构。
您要将SOCK_STREAM
分配到ai_protocol
字段,并将ai_socktype
字段设置为0. SOCK_STREAM
定义为1,这与{的值相同{1}},通常与IPPROTO_ICMP
套接字一起使用。因此,SOCK_DGRAM
可能会返回getaddrinfo()
字段设置为addrinfo
的{{1}}条目。您无法在数据报套接字上使用ai_socktype
,因此您会看到SOCK_DGRAM
错误:
如果没有错误发生,
listen()
将返回零。否则,返回值WSAEOPNOTSUPP
,并且可以通过调用listen
来检索特定的错误代码。...
WSAEOPNOTSUPP
引用的套接字不属于支持SOCKET_ERROR
操作的类型。
您需要将WSAGetLastError
分配给listen
字段,并将SOCK_STREAM
字段设置为0或hints.ai_socktype
(最好是后者)。
此外,hints.ai_protocol
会返回错误代码,就像IPPROTO_TCP
一样。不要使用getaddrinfo()
来获取错误代码。
除此之外,我还看到了您的代码中的许多其他问题。
WSAStartup()
需要WSAGetLastError()
(4字节整数),而不是SO_REUSEADDR
(1字节)。您正在传递指向单个BOOL
的指针,但告诉char
您正在传递指向char
的指针。 setsockopt()
最终会尝试从您不拥有的堆栈内存中读取值。
当您的循环调用int
时,您应该将setsockopt()
重置为closesocket()
,然后在循环之后您应该检查该条件而不是检查{{1对于NULL。
您应该在循环中调用sockfd
而不是在循环之后。仅仅因为INVALID_SOCKET
套接字成功并不能保证您可以打开其指定的侦听端口。您应该保持循环,直到您实际成功打开侦听端口。您可能还会考虑在循环后添加额外的日志消息,以了解哪个本地IP /端口对实际正在侦听,因此您知道哪些客户端能够ptr
到。
调用listen()
时,在Winsock调用失败后,请务必立即将其命名为 。如果您事先调用其他任何内容,则可能会重置错误代码,因为bind()
只是许多API使用的connect()
的别名。
调用WSAGetLastError()
时,在检查WSAGetLastError()
是否等于{{1}时,您使用GetLastError()
赋值运算符而不是accept()
比较运算符}。即使您解决了这个问题,如果=
成功,您就会泄漏==
,因为您已经忘记了它并且没有在其上调用m_exchfd
。如果您只希望连接一个客户端,请在接受客户端后关闭侦听套接字。否则,将侦听套接字存储在类中,并在关闭接受的客户端套接字后将其关闭。
所有这些都说,尝试更像这样的事情:
INVALID_SOCKET