Socket编程:Error()上的错误()
我正在使用我的应用程序的服务器部分,并且遇到了一个我似乎无法解决的问题。服务器初始化功能,这是ConnectionManager
类的一部分,如下:Socket编程:Error()上的错误()
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
函数简单地打印对应于使用函数的错误消息。不过,我得到以下输出:
> Log: listen()
> ERROR: The attempted operation is not supported for the type of object referenced.
因此,误差应该通过调用listen()
这是混乱引起的。任何人都可以解释一下问题的原因是什么?我敢打赌,这应该是一件容易解决的事情,但我似乎没有看到它。
问题的根源在于拨打getaddrinfo()
时,您正在填写错误的hints
结构。
要分配SOCK_STREAM
到ai_protocol
字段,和离开ai_socktype
字段设置为0。SOCK_STREAM
被定义为1,这是相同的值IPPROTO_ICMP
,其通常与SOCK_DGRAM
插座使用。因此,getaddrinfo()
很可能会返回addrinfo
条目,其ai_socktype
字段设置为SOCK_DGRAM
。你不能在数据报套接字使用listen()
,因此WSAEOPNOTSUPP
错误您所看到的:
如果没有出现错误,
listen
返回零。否则,将返回SOCKET_ERROR
的值,并且可以通过调用WSAGetLastError
来检索特定的错误代码。...
WSAEOPNOTSUPP
引用的插槽是支持listen
操作的类型不。
您需要分配SOCK_STREAM
到hints.ai_socktype
字段代替,并设置hints.ai_protocol
字段为0或IPPROTO_TCP
(优选后者)。
另外,getaddrinfo()
返回错误代码,就像WSAStartup()
一样。请勿使用WSAGetLastError()
来获取其错误代码。
除此之外,我还看到许多其他与您的代码有关的问题。
SO_REUSEADDR
需要BOOL
(4字节整数),而不是一个char
(1字节)。您正在传递一个指向单个的指针char
,但它告诉setsockopt()
您正在传递一个指向int
的指针。setsockopt()
将最终尝试从您不拥有的堆栈内存中读取值。当你的循环调用
closesocket()
,您应该重置sockfd
到INVALID_SOCKET
为好,然后在循环之后,你应该检查该条件,而不是检查ptr
为NULL。您应该在循环中调用
listen()
而不是在循环之后。仅仅因为你bind()
套接字成功并不能保证你可以打开其分配的侦听端口。您应该保持循环,直到您实际成功打开侦听端口。您也可以考虑在循环后添加额外的日志消息,以了解哪个本地IP /端口对实际上正在侦听,以便您知道哪些客户端可以使用connect()
。当致电
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,而不是它? –
当listen()失败时,'ptr-> ai_family','ptr-> ai_socktype'和'ptr-> ai_protocol'的实际值是多少?如果它们分别不是'AF_INET/6','SOCK_STREAM'和'IPPROTO_TCP',那么出现了问题。哦,等一下,我刚刚看到你的代码中有另一个错误。我编辑了我的答案。 –
阅读['setsockopt()'文档](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740476.aspx)。 'SO_REUSEADDR'需要一个'BOOL'。不要被'setsockopt()'签名'char *'指针所欺骗(在逻辑上,它应该使用'void *'来代替)。这是历史时期的遗留物。它实际上可以采取任何形式的指针,它只需要进行输入。就像其他一些套接字API函数一样,最明显的是那些被声明为使用'sockaddr *'指针但实际上可以使用'sockaddr_XX *'指针的函数。 –