Visual c++ 套接字编程:侦听()时出错
我正在开发应用程序的服务器部分,遇到了一个似乎无法解决的问题。服务器初始化函数是ConnectionManager类的一部分,如下所示:Visual c++ 套接字编程:侦听()时出错,visual-c++,winsock,Visual C++,Winsock,我正在开发应用程序的服务器部分,遇到了一个似乎无法解决的问题。服务器初始化函数是ConnectionManager类的一部分,如下所示: int ConnectionManager::init_server() { // Log OutputDebugString(L"> Initializing server...\n"); // Initialize winsock WSAData wsa; int code = WSAStartup(MAK
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引起的,这会造成混淆。谁能解释一下问题的原因吗?我打赌它应该很容易修复,但我就是看不到。问题的根源在于调用getaddrinfo时,您填写的提示结构不正确 您正在将SOCK_流分配给ai_协议字段,并将ai_socktype字段设置为0。SOCK_流定义为1,它与IPPROTO_ICMP的值相同,IPPROTO_ICMP通常与SOCK_DGRAM套接字一起使用。因此,getaddrinfo很可能返回其ai_socktype字段设置为SOCK_DGRAM的addrinfo条目。您不能在数据报套接字上使用侦听,因此您会看到WSAEOPNOTSUPP错误: 如果没有发生错误,listen返回零。否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError检索特定的错误代码 WSAEOPNOTPUP 引用的套接字的类型不支持侦听操作 您需要将SOCK_流指定给hints.ai_socktype字段,并将hints.ai_protocol字段设置为0或IPPROTO_TCP(最好是后者) 此外,getaddrinfo返回一个错误代码,就像WSAStartup一样。不要使用WSAGetLastError获取其错误代码 除此之外,我发现您的代码还存在许多其他问题 因此,REUSEADDR需要一个BOOL 4字节整数,而不是char 1字节。您正在传入一个指向单个字符的指针,但告诉setsockopt您正在传入一个指向int的指针。setsockopt最终将尝试从您不拥有的堆栈内存中读取一个值 当您的循环调用closesocket时,您也应该将sockfd重置为无效的_SOCKET,然后在循环之后,您应该检查该条件,而不是检查ptr是否为NULL 您应该在循环内调用listen,而不是在循环后调用。成功绑定套接字并不保证可以打开其分配的侦听端口。在实际成功打开侦听端口之前,应该一直循环。您还可以考虑在循环之后添加一个额外的日志消息,以知道哪个本地IP /端口对实际上在监听,因此您知道客户端可以连接到什么。 调用WSAGetLastError时,始终在Winsock调用失败后立即调用它。如果您事先调用了其他任何东西,则可能会重置错误代码,因为WSAGetLastError只是许多API使用的GetLastError的别名 调用accept时,在检查m_exchfd是否等于INVALID_SOCKET时,使用的是=赋值运算符而不是==比较运算符。即使在修复该问题之后,如果accept成功,也会泄漏sockfd,因为您无法跟踪它,并且没有对其调用closesocket。如果只希望连接一个客户端,请在接受该客户端后关闭侦听套接字。否则,将侦听套接字存储在类中,并在关闭已接受的客户端套接字后将其关闭 话虽如此,不妨尝试以下方式:
void OutputWinsockError(LPCWSTR funcName, int errCode)
{
std::wostringstream msg;
msg << L"> Log: " << funcName << L"()\n";
OutputDebugStringW(msg.str().c_str());
output_error(errCode);
}
void OutputWinsockError(LPCWSTR funcName)
{
OutputWinsockError(funcName, WSAGetLastError());
}
int ConnectionManager::init_server() {
// Log
OutputDebugString(L"> Initializing server...\n");
// Initialize winsock
WSAData wsa;
int err = WSAStartup(MAKEWORD(2, 2), &wsa);
if (err != 0) {
// Error initializing winsock
OutputWinsockError(L"WSAStartup", err);
return -1;
}
// Get server information
struct addrinfo hints, *serverinfo, *ptr;
SOCKET sockfd = INVALID_SOCKET;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo(NULL, PORT, &hints, &serverinfo);
if (err != 0) {
// Error when getting server address information
OutputWinsockError(L"getaddrinfo", err);
// Call Cleanup?
return -1;
}
for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
// Create socket
sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (sockfd == INVALID_SOCKET) {
// Error when creating a socket
OutputWinsockError(L"socket");
continue;
}
// Set options
const BOOL enable = TRUE;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(BOOL)) == SOCKET_ERROR) {
// Error when setting options
OutputWinsockError(L"setsockopt");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
// Bind socket
if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
// Error on binding
OutputWinsockError(L"bind");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
// Listen on port
if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
// Error on listening
OutputWinsockError(L"listen");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(serverinfo);
if (sockfd == INVALID_SOCKET) {
OutputDebugString(L"Error: Failed to launch server.\n");
// Call Cleanup?
return -1;
}
// Accept
struct sockaddr_storage clientinfo;
int size = sizeof(clientinfo);
m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
if (m_exchfd == INVALID_SOCKET) {
// Error when accepting
OutputWinsockError(L"accept");
if (closesocket(sockfd) == SOCKET_ERROR) {
OutputWinsockError(L"closesocket");
}
// Call Cleanup?
return -1;
}
m_isConnected = true;
// is not storing sockfd, close it
// m_listenfd = sockfd;
if (closesocket(sockfd) == SOCKET_ERROR) {
OutputWinsockError(L"closesocket");
}
return 0;
}
我试着根据你的评论修改我的程序,非常感谢你的评论,但是我仍然得到同样的错误,现在在两个地址上都找到了。另外,为什么在setsockopt函数中使用BOOL?根据它的原型,应该使用char,不是吗?当侦听失败时,ptr->ai_系列、ptr->ai_socktype和ptr->ai_协议的实际值是多少?如果它们分别不是AF_INET/6、SOCK_STREAM和IPPROTO_TCP,那么就出了问题。哦,等等,我刚刚在你的代码中看到另一个错误。我已经编辑了我的答案。请阅读。所以博士希望有一场战争。不要被setsockopt的签名所愚弄,它在逻辑上使用了char*指针,它应该使用void*。这是历史遗留下来的。它实际上可以接受任何类型的指针,它只需要类型转换。与其他几个套接字API函数一样,最明显的是那些声明为接受sockaddr*指针但实际上可以接受sockaddr_XX*指针的函数。将ai_协议更改为ai_socktype似乎解决了这个问题。谢谢!