客户端关闭后 accept() 不阻塞(使用 Ctrl+C)错误 10093
accept() doesn't block after client close (with Ctrl+C) error 10093
我正在使用 winsock2
开发一个简单的客户端-服务器应用程序,其中我从客户端发送一个整数值,服务器接收它。
当我发送一个(或多个) ) integer 并且客户端正确关闭套接字,服务器知道客户端已经关闭连接并转到 accept()
函数等待另一个连接。
但是,当我停止客户端(例如使用组合 Ctrl+C
)时,accept()
不会停止并继续服务器返回错误的主循环 10093
每次循环 (与 WSAStartup()
相关)。
我认为在某种程度上我必须管理发送到服务器的信号,比如 SIPIPE
在 Linux 或类似的东西,但我不知道如何。
解决此问题的最佳方法是什么?
这里是我的接受实现:
bool Network::Accept() {
caddrlen = sizeof(clientAddr);
int ret;
if ((ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen) ) == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
closesocket(listeningSocket);
WSACleanup();
return false;
}
else {
//save client ip address in a string
getpeername(listeningSocket, (SOCKADDR *)&clientAddr, (int *)sizeof(clientAddr));
char ip[20];
inet_ntop(AF_INET, (sockaddr*)&clientAddr.sin_addr, ip, 20);
clientIPaddr.assign(ip);
connectedSocket = ret;
return true;
}
}
Winsock 错误 10093 是 WSANOTINITIALISED
:
Successful WSAStartup not yet performed.
Either the application has not called WSAStartup or WSAStartup failed. The application may be accessing a socket that the current active task does not own (that is, trying to share a socket between tasks), or WSACleanup has been called too many times.
当 accept()
因任何原因失败时,您的 Network::Accept()
方法正在调用 WSACleanup()
。 Network::Accept()
根本不应该这样做。它也不应该关闭监听套接字。从 Network::Accept()
中删除这两行,如果 return 为假,则让主循环停止调用 Network::Accept()
,然后根据需要清理监听套接字。
您的 Network::Accept()
代码还有其他问题:
accept()
return 是 SOCKET
,不是 int
。
当 accept()
成功时,您正在使用错误的参数值调用 getpeername()
。您传递的是监听套接字而不是已接受的客户端套接字,并且传递的是其 namelen
参数的无效指针(您需要传递指向 caddrlen
值的指针,而不是类型转换sizeof()
的 return 值)。就此而言,调用 getpeername()
无论如何都是多余的,因为 accept()
已经为您提供了与 getpeername()
相同的地址。
当调用 inet_ntop()
时,您将客户端地址的 sin_addr
字段类型转换为 sockaddr*
,这是错误的。但在这种情况下,编译器会接受它,因为 pAddr
参数是 void*
。您根本不需要类型转换。
如果 listeningSocket
是一个 AF_INET
(IPv4) 套接字,那么在调用 inet_ntop()
时硬编码 AF_INET
是可以的,因为接受客户端将始终使用 IPv4 地址 (sockaddr_in
)。但是,如果您 want/need 支持 IPv6(并且您应该),那么您应该检查客户端的实际地址族以了解该地址是否使用 sockaddr_in
或 sockaddr_in6
然后通过参数相应地 inet_ntop()
。
话虽如此,请尝试更像这样的事情:
如果您仅支持 IPv4:
bool Network::Accept() {
// declare clientAddr as sockaddr_in...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &(clientAddr.sin_addr), ip, INET_ADDRSTRLEN);
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}
如果您仅支持 IPv6:
bool Network::Accept() {
// declare clientAddr as sockaddr_in6...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET6_ADDRSTRLEN] = {0};
inet_ntop(AF_INET6, &(clientAddr.sin6_addr), ip, INET6_ADDRSTRLEN);
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}
如果您同时支持 IPv4 和 IPv6:
bool Network::Accept() {
// declare clientAddr as SOCKADDR_STORAGE...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET6_ADDRSTRLEN] = {0};
switch (clientAddr.ss_family)
{
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)&clientAddr)->sin_addr), ip, INET_ADDRSTRLEN);
break;
case AF_INET6:
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&clientAddr)->sin6_addr), ip, INET6_ADDRSTRLEN);
break;
}
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}
我正在使用 winsock2
开发一个简单的客户端-服务器应用程序,其中我从客户端发送一个整数值,服务器接收它。
当我发送一个(或多个) ) integer 并且客户端正确关闭套接字,服务器知道客户端已经关闭连接并转到 accept()
函数等待另一个连接。
但是,当我停止客户端(例如使用组合 Ctrl+C
)时,accept()
不会停止并继续服务器返回错误的主循环 10093
每次循环 (与 WSAStartup()
相关)。
我认为在某种程度上我必须管理发送到服务器的信号,比如 SIPIPE
在 Linux 或类似的东西,但我不知道如何。
解决此问题的最佳方法是什么?
这里是我的接受实现:
bool Network::Accept() {
caddrlen = sizeof(clientAddr);
int ret;
if ((ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen) ) == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
closesocket(listeningSocket);
WSACleanup();
return false;
}
else {
//save client ip address in a string
getpeername(listeningSocket, (SOCKADDR *)&clientAddr, (int *)sizeof(clientAddr));
char ip[20];
inet_ntop(AF_INET, (sockaddr*)&clientAddr.sin_addr, ip, 20);
clientIPaddr.assign(ip);
connectedSocket = ret;
return true;
}
}
Winsock 错误 10093 是 WSANOTINITIALISED
:
Successful WSAStartup not yet performed.
Either the application has not called WSAStartup or WSAStartup failed. The application may be accessing a socket that the current active task does not own (that is, trying to share a socket between tasks), or WSACleanup has been called too many times.
当 accept()
因任何原因失败时,您的 Network::Accept()
方法正在调用 WSACleanup()
。 Network::Accept()
根本不应该这样做。它也不应该关闭监听套接字。从 Network::Accept()
中删除这两行,如果 return 为假,则让主循环停止调用 Network::Accept()
,然后根据需要清理监听套接字。
您的 Network::Accept()
代码还有其他问题:
accept()
return 是SOCKET
,不是int
。当
accept()
成功时,您正在使用错误的参数值调用getpeername()
。您传递的是监听套接字而不是已接受的客户端套接字,并且传递的是其namelen
参数的无效指针(您需要传递指向caddrlen
值的指针,而不是类型转换sizeof()
的 return 值)。就此而言,调用getpeername()
无论如何都是多余的,因为accept()
已经为您提供了与getpeername()
相同的地址。当调用
inet_ntop()
时,您将客户端地址的sin_addr
字段类型转换为sockaddr*
,这是错误的。但在这种情况下,编译器会接受它,因为pAddr
参数是void*
。您根本不需要类型转换。如果
listeningSocket
是一个AF_INET
(IPv4) 套接字,那么在调用inet_ntop()
时硬编码AF_INET
是可以的,因为接受客户端将始终使用 IPv4 地址 (sockaddr_in
)。但是,如果您 want/need 支持 IPv6(并且您应该),那么您应该检查客户端的实际地址族以了解该地址是否使用sockaddr_in
或sockaddr_in6
然后通过参数相应地inet_ntop()
。
话虽如此,请尝试更像这样的事情:
如果您仅支持 IPv4:
bool Network::Accept() {
// declare clientAddr as sockaddr_in...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &(clientAddr.sin_addr), ip, INET_ADDRSTRLEN);
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}
如果您仅支持 IPv6:
bool Network::Accept() {
// declare clientAddr as sockaddr_in6...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET6_ADDRSTRLEN] = {0};
inet_ntop(AF_INET6, &(clientAddr.sin6_addr), ip, INET6_ADDRSTRLEN);
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}
如果您同时支持 IPv4 和 IPv6:
bool Network::Accept() {
// declare clientAddr as SOCKADDR_STORAGE...
caddrlen = sizeof(clientAddr);
SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
if (ret == INVALID_SOCKET) {
myFormatMessage(WSAGetLastError());
return false;
}
//save client ip address in a string
char ip[INET6_ADDRSTRLEN] = {0};
switch (clientAddr.ss_family)
{
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)&clientAddr)->sin_addr), ip, INET_ADDRSTRLEN);
break;
case AF_INET6:
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&clientAddr)->sin6_addr), ip, INET6_ADDRSTRLEN);
break;
}
clientIPaddr.assign(ip);
// declare connectedSocket as SOCKET...
connectedSocket = ret;
return true;
}