WSA UDP 套接字无法重用,因为它强制关闭连接
Posted
技术标签:
【中文标题】WSA UDP 套接字无法重用,因为它强制关闭连接【英文标题】:WSA UDP socket can't be reused as it forcibly closes the connection 【发布时间】:2020-12-09 22:20:31 【问题描述】:我需要关闭然后在我的应用程序中重用同一个套接字。套接字第一次连接时可以正常连接,但第二次尝试使用时,客户端从服务器收到 wsaerror 10054(现有连接被远程主机强制关闭),我看到服务器没有收到来自客户端的“syn”数据。这里似乎有什么问题?之前连接过的客户端能够再次连接到服务器,但是之前接收到连接的服务器无法接受新连接,因为它以某种方式导致了 10054。
连接管理器.hpp
#pragma once
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <iostream>
#include <string>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_PORT 27015
#define DEFAULT_BUFFER_LENGTH 64
class ConnectionManager
private:
fd_set fdset;
struct timeval client_wait_timeout;
struct timeval server_wait_timeout;
SOCKET sock = INVALID_SOCKET;
// This is where we'll be setting up connection parameters or where we'll be storing the parameters for a connection that's made.
SOCKADDR_IN connection_data;
int connection_data_len = sizeof(connection_data);
char receive_buffer[DEFAULT_BUFFER_LENGTH] = 0 ; // The object where the recieved data will be placed on.
public:
std::wstring server_ipv4;
bool is_connected = false;
std::string type = "none";
ConnectionManager();
void init(std::string connection_type);
void reset();
bool establish_first_connection();
bool await_first_connection();
std::string receive_data();
std::string send_data(std::string data);
;
连接管理器.cpp
#include "connection_manager.hpp"
ConnectionManager::ConnectionManager()
WSADATA wsadata;
int result;
// Initialize Windows Sockets library, version 2.2.
result = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (result != 0)
std::cerr << "WSAStartup failed, error: " << result << "\n";
connection_data.sin_family = AF_INET; // Using IPv4
connection_data.sin_port = htons(DEFAULT_PORT);
void ConnectionManager::init(std::string connection_type)
int result = 0;
if (connection_type == "server")
connection_data.sin_addr.s_addr = INADDR_ANY; // Bind the socket to all available interfaces - or in other words, accept connections from any IPv4 address. We'll change this after we establish our first connection with the client.
// Create a socket for the server to listen from client for data / send data to client.
sock = socket(connection_data.sin_family, SOCK_DGRAM, 0);
if (sock == INVALID_SOCKET)
std::cerr << "Error occured while creating server socket: " << WSAGetLastError() << "\n";
WSACleanup();
// Bind the listening socket.
result = bind(sock, (SOCKADDR*)&connection_data, connection_data_len);
if (result == SOCKET_ERROR)
std::cerr << "Listening socket bind failed with error: " << WSAGetLastError() << "\n";
closesocket(sock);
WSACleanup();
std::cout << "Awaiting connection..." << "\n";
if (!await_first_connection())
std::cerr << "Either no one connnected during the 60 second period, or there was a problem with the server. Last WSA error:" << WSAGetLastError() << "\n";
else
std::cout << "Connected successfully!" << "\n";
is_connected = true;
else if (connection_type == "client")
InetPton(connection_data.sin_family, (PCWSTR)(server_ipv4.c_str()), &connection_data.sin_addr.s_addr); // Set the IP address to connect to on the connection_data structure.
// Create a socket for sending data to server.
sock = socket(connection_data.sin_family, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET)
std::cerr << "Error occured while creating client socket: " << WSAGetLastError() << "\n";
WSACleanup();
std::wcout << "Attempting to connect to " << server_ipv4 << "..." << "\n";
if (!establish_first_connection())
std::cerr << "There was a problem connecting the server. Last WSA error: " << WSAGetLastError() << "\n";
else
std::wcout << "Successfully connected to " << server_ipv4 << "!" << "\n";
is_connected = true;
// Put the socket in non-blocking mode.
unsigned long mode = 1;
if (ioctlsocket(sock, FIONBIO, (unsigned long*)&mode) == SOCKET_ERROR)
std::cerr << "Error while putting the socket into non-blocking mode: " << WSAGetLastError() << "\n";
void ConnectionManager::reset()
is_connected = false;
closesocket(sock);
/*
Functions "establish_first_connection" and "await_first_connection" do something that's quite similar to the three-way handshake method of a TCP connection.
*/
bool ConnectionManager::establish_first_connection() // This will be used by the client.
// Set up the file descriptor set.
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
int send_result = INT32_MAX;
std::string syn_message = "SYN";
send_result = sendto(sock, syn_message.c_str(), syn_message.length(), 0, (SOCKADDR*)&connection_data, connection_data_len);
if (send_result == SOCKET_ERROR)
std::cerr << "Error occured while attempting to send SYN to server: " << WSAGetLastError() << "\n";
else
int result = 0;
int receive_result = 0;
// Set up the timeval struct for the timeout.
// We'll wait for 10 seconds for the server to respond, or else we'll call the connection off.
client_wait_timeout.tv_sec = 10; // seconds
client_wait_timeout.tv_usec = 0; // microseconds
// Wait until the timeout or until we receive data.
result = select(sock, &fdset, NULL, NULL, &client_wait_timeout);
if (result == 0)
std::cout << "Timeout." << "\n"; // todo
else if (result == -1)
std::cerr << "Error occured while awaiting first connection data from server. Last WSA error:" << WSAGetLastError() << "\n";
receive_result = recvfrom(sock, receive_buffer, DEFAULT_BUFFER_LENGTH, 0, (SOCKADDR*)&connection_data, &connection_data_len);
if (receive_result > 0) // If we received any data before the timeout, return true.
std::string client_ack_message = "ACK";
std::cout << receive_buffer << "\n";
sendto(sock, client_ack_message.c_str(), client_ack_message.length(), 0, (SOCKADDR*)&connection_data, connection_data_len);
return true;
return false;
bool ConnectionManager::await_first_connection() // This will be used by the server.
int result = 0;
int receive_result = 0;
int send_result = 0;
// Set up the file descriptor set.
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
// Set up the timeval struct for the timeout.
// We'll wait for 60 seconds for someone to connect and if someone doesn't connect, we'll cancel the server.
server_wait_timeout.tv_sec = 60; // seconds
server_wait_timeout.tv_usec = 0; // microseconds
// Wait until the timeout or until we receive data.
result = select(sock, &fdset, NULL, NULL, &server_wait_timeout);
if (result == 0)
std::cout << "Timeout." << "\n";
return false;
else if (result == -1)
std::cerr << "Error occured while awaiting first connection data from client. Last WSA error: " << WSAGetLastError() << "\n";
receive_result = recvfrom(sock, receive_buffer, DEFAULT_BUFFER_LENGTH, 0, (SOCKADDR*)&connection_data, &connection_data_len); // We set the first connected client as the only suitable connector from now on here.
if (receive_result > 0) // If we received any data before the timeout, let the client know that we acknowledge their request and return true.
std::string ack_message = "ACK";
send_result = sendto(sock, ack_message.c_str(), ack_message.length(), 0, (SOCKADDR*)&connection_data, connection_data_len); // Let the client know that we received their message.
if (send_result != SOCKET_ERROR)
return true;
return false;
std::string ConnectionManager::receive_data()
ZeroMemory(receive_buffer, DEFAULT_BUFFER_LENGTH); // Clean the receive buffer of any possibly remaining data.
int receive_result = 42;
u_long ioctl_result = 123;
while (true) // When ioctl with FIONREAD results 0, that means there's no datagram pending in the receive queue. We'll use this to grab only the last received package.
receive_result = recvfrom(sock, receive_buffer, DEFAULT_BUFFER_LENGTH, 0, (SOCKADDR*)&connection_data, &connection_data_len);
ioctlsocket(sock, FIONREAD, &ioctl_result);
if (ioctl_result == 0)
break;
// Handle errors.
if (receive_result > 0)
return std::string(receive_buffer, receive_result); // Using the built-in method of casting char to std::string.
else if (receive_result == 0)
return "RECEIVEDNOTHING";
else if (receive_result == SOCKET_ERROR)
switch (WSAGetLastError())
case WSAEWOULDBLOCK:
return "WOULDBLOCK";
break;
case WSAECONNRESET:
return "CONNRESET";
break;
case NTE_OP_OK:
break;
default:
std::cerr << "Unhandled error while receiving data: " << WSAGetLastError() << "\n";
return "NONE";
std::string ConnectionManager::send_data(std::string data)
int send_result = 666;
send_result = sendto(sock, data.c_str(), data.length(), 0, (SOCKADDR*)&connection_data, connection_data_len);
// Handle errors.
if (send_result == SOCKET_ERROR)
std::cerr << "Error while sending data: " << WSAGetLastError() << "\n";
return std::string("FAIL");
else
return std::string("OK");
main.cpp
#include <iostream>
#include <string>
#include "connectionmanager.hpp"
int main()
ConnectionManager connection_manager;
std::string connection_type;
std::cout << "server or client?" << "\n";
std::cin >> connection_type;
if (connection_type == "client")
std::wstring ipv4_addr;
std::cout << "ip address?" << "\n";
std::wcin >> ipv4_addr;
connection_manager.server_ipv4 = ipv4_addr;
connection_manager.type = connection_type;
connection_manager.init(); // this works fine
connection_manager.reset();
connection_manager.init(); // client returns wsaerror 10054, server receives no data
【问题讨论】:
包含的所有代码真的与您的问题相关吗?考虑使用minimal reproducible examples。 "我需要关闭然后在我的应用程序中重用同一个套接字" - 你不能重用关闭的SOCKET
句柄,除非你使用 Winsock 的扩展 @987654322带有TF_REUSE_SOCKET
标志的@ 函数,那么SOCKET
可以与AcceptEx()
/ConnectEx()
函数一起重用。如果使用closesocket()
,则必须新建一个SOCKET
另外,您对WSAStartup()
和WSACleanup()
的使用是错误的。这些需要移到您的main()
中。或者,至少,WSACleanup()
移出 ConnectionManager::init()
并进入 ~ConnectionManager()
析构函数。
@RemyLebeau 谢谢,我认为既然 `ConnectionManager::Init()" 重新初始化了套接字,这将使它再次可用。
@emredesu init()
正在创建一个新的套接字对象并将其分配给sock
成员。那是重用一个变量,而不是重用一个套接字对象。
【参考方案1】:
我可以通过将 sin_family 和 sin_port 初始化从构造函数移动到 ConnectionManager::init() 并编辑 ConnectionManager::reset() 来解决这个问题:
void ConnectionManager::reset()
puts("reset!");
is_connected = false;
closesocket(sock);
sock = INVALID_SOCKET;
memset(&connection_data, 0, sizeof(connection_data)); // Get rid of the data from the previous connection.
memset(&receive_buffer, 0, sizeof(receive_buffer));
【讨论】:
以上是关于WSA UDP 套接字无法重用,因为它强制关闭连接的主要内容,如果未能解决你的问题,请参考以下文章