套接字编程:listen() 出错
Socket Programming: Error on listen()
我正在处理我的应用程序的服务器部分,我遇到了一个我似乎无法解决的问题。服务器初始化函数,是ConnectionManager
class的一部分,如下:
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()
时,您填写的 hints
结构不正确。
您正在将 SOCK_STREAM
分配给 ai_protocol
字段,并将 ai_socktype
字段设置为 0。SOCK_STREAM
定义为 1,这是相同的值如 IPPROTO_ICMP
,通常与 SOCK_DGRAM
套接字一起使用。因此,getaddrinfo()
可能会返回 addrinfo
个条目,其 ai_socktype
字段设置为 SOCK_DGRAM
。您不能在数据报套接字上使用 listen()
,因此您会看到 WSAEOPNOTSUPP
错误:
If no error occurs, listen
returns zero. Otherwise, a value of SOCKET_ERROR
is returned, and a specific error code can be retrieved by calling WSAGetLastError
.
...
WSAEOPNOTSUPP
The referenced socket is not of a type that supports the listen
operation.
您需要将 SOCK_STREAM
分配给 hints.ai_socktype
字段,并将 hints.ai_protocol
字段设置为 0 或 IPPROTO_TCP
(最好是后者)。
此外,getaddrinfo()
returns 一个错误代码,就像 WSAStartup()
一样。不要使用 WSAGetLastError()
来获取它的错误代码。
除此之外,我发现您的代码还有许多其他问题。
SO_REUSEADDR
需要 BOOL
(4 字节整数),而不是 char
(1 字节)。您正在传递一个指向单个 char
的指针,但告诉 setsockopt()
您正在传递一个指向 int
的指针。 setsockopt()
最终会尝试从您不拥有的堆栈内存中读取一个值。
当您的循环调用 closesocket()
时,您也应该将 sockfd
重置为 INVALID_SOCKET
,然后在循环之后您应该检查该条件而不是检查 ptr
是否为 NULL。
您应该在循环内而不是在循环之后调用 listen()
。仅仅因为你 bind()
一个套接字成功并不能保证你可以打开它分配的监听端口。你应该一直循环,直到你真正成功地打开一个监听端口。您还可以考虑在循环后添加一条额外的日志消息,以了解哪个本地 IP/Port 对实际上正在侦听,这样您就知道哪些客户端可以 connect()
到。
调用 WSAGetLastError()
时,总是在 Winsock 调用失败后立即调用它。如果您事先调用了其他任何东西,您就有重置错误代码的风险,因为 WSAGetLastError()
只是 GetLastError()
的一个别名,许多 API 使用它。
当调用 accept()
时,您在检查 m_exchfd
是否等于时使用 =
赋值运算符而不是 ==
比较运算符INVALID_SOCKET
。即使在你修复它之后,如果 accept()
成功,你就会泄漏 sockfd
,因为你忘记了它并且不调用它 closesocket()
。如果您希望只有 1 个客户端连接,请在接受客户端后关闭侦听套接字。否则,将侦听套接字存储在 class 中,并在关闭已接受的客户端套接字后将其关闭。
综上所述,试试这样的东西:
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;
}
我正在处理我的应用程序的服务器部分,我遇到了一个我似乎无法解决的问题。服务器初始化函数,是ConnectionManager
class的一部分,如下:
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()
时,您填写的 hints
结构不正确。
您正在将 SOCK_STREAM
分配给 ai_protocol
字段,并将 ai_socktype
字段设置为 0。SOCK_STREAM
定义为 1,这是相同的值如 IPPROTO_ICMP
,通常与 SOCK_DGRAM
套接字一起使用。因此,getaddrinfo()
可能会返回 addrinfo
个条目,其 ai_socktype
字段设置为 SOCK_DGRAM
。您不能在数据报套接字上使用 listen()
,因此您会看到 WSAEOPNOTSUPP
错误:
If no error occurs,
listen
returns zero. Otherwise, a value ofSOCKET_ERROR
is returned, and a specific error code can be retrieved by callingWSAGetLastError
....
WSAEOPNOTSUPP
The referenced socket is not of a type that supports thelisten
operation.
您需要将 SOCK_STREAM
分配给 hints.ai_socktype
字段,并将 hints.ai_protocol
字段设置为 0 或 IPPROTO_TCP
(最好是后者)。
此外,getaddrinfo()
returns 一个错误代码,就像 WSAStartup()
一样。不要使用 WSAGetLastError()
来获取它的错误代码。
除此之外,我发现您的代码还有许多其他问题。
SO_REUSEADDR
需要BOOL
(4 字节整数),而不是char
(1 字节)。您正在传递一个指向单个char
的指针,但告诉setsockopt()
您正在传递一个指向int
的指针。setsockopt()
最终会尝试从您不拥有的堆栈内存中读取一个值。当您的循环调用
closesocket()
时,您也应该将sockfd
重置为INVALID_SOCKET
,然后在循环之后您应该检查该条件而不是检查ptr
是否为 NULL。您应该在循环内而不是在循环之后调用
listen()
。仅仅因为你bind()
一个套接字成功并不能保证你可以打开它分配的监听端口。你应该一直循环,直到你真正成功地打开一个监听端口。您还可以考虑在循环后添加一条额外的日志消息,以了解哪个本地 IP/Port 对实际上正在侦听,这样您就知道哪些客户端可以connect()
到。调用
WSAGetLastError()
时,总是在 Winsock 调用失败后立即调用它。如果您事先调用了其他任何东西,您就有重置错误代码的风险,因为WSAGetLastError()
只是GetLastError()
的一个别名,许多 API 使用它。当调用
accept()
时,您在检查m_exchfd
是否等于时使用=
赋值运算符而不是==
比较运算符INVALID_SOCKET
。即使在你修复它之后,如果accept()
成功,你就会泄漏sockfd
,因为你忘记了它并且不调用它closesocket()
。如果您希望只有 1 个客户端连接,请在接受客户端后关闭侦听套接字。否则,将侦听套接字存储在 class 中,并在关闭已接受的客户端套接字后将其关闭。
综上所述,试试这样的东西:
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;
}