我有一个包裹winsock的对象,这是一个简化版:
struct Listener
{
using accept_callback = void(*)(Client &&);
~Listener() { WSACleanup(); }
Listener(const std::basic_string<TCHAR> &a_ip, std::uint16_t a_port, accept_callback a_callback)
{
// Error control ommited for brevity
m_on_connection_accepted = a_callback;
WSADATA sockets_data{};
WSAStartup(MAKEWORD(2, 2), &sockets_data);
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in service { .sin_family = AF_INET, .sin_port = htons(a_port) };
InetPton(service.sin_family, a_ip.data(), &service.sin_addr);
bind(m_socket, reinterpret_cast<SOCKADDR *>(&service), sizeof(service));
listen(m_socket, SOMAXCONN);
m_listen_thread = std::jthread([&]
{
while (m_socket != INVALID_SOCKET)
{
if (SOCKET client = accept(m_socket, NULL, NULL); client == INVALID_SOCKET)
break;
else
m_on_connection_accepted(std::move(Client{ client }));
}
});
}
void stop()
{
closesocket(m_socket);
m_socket = INVALID_SOCKET;
if (m_listen_thread.joinable())
m_listen_thread.join();
m_listen_thread = {};
}
private:
accept_callback m_on_connection_accepted{ nullptr };
SOCKET m_socket{ INVALID_SOCKET };
std::jthread m_listen_thread{};
};
我的目标是在客户端连接后立即停止监听器,这样一次连接的客户端不会超过一个:
void on_client_connected(sockets::Client &&a_new_client)
{
// … do things …
m_listener.stop();
}
此代码会导致
abort()
调用,因为接受客户端会导致监听线程从线程本身的调用中加入:thread loop → m_on_connection_accepted(…)
→ on_client_connected(…)
→ Listener::stop()
→ jthread::join()
.
我以为结果会是:
m_socket
设置为 INVALID_SOCKET
.m_on_connection_accepted
调用后继续运行。很明显我错了,我应该怎么做才能达到我的目标?
如果我一次只想为一个客户服务,我根本不会创建一个线程来为客户服务。
看来你想要的是这样一个序列:
for (;;) {
auto client = listen_for_client_connection();
service(client);
}
在一个线程中监听并在另一个线程中为客户端提供服务的全部意义在于,当一个客户端连接时,您可以为该客户端提供服务,和立即监听新连接,因此您可以同时为多个客户端提供服务。
既然您一次只想为一个客户提供服务,那么……就不要做任何专门为多个并发客户提供服务的“事情”。由于您一次只想处理一个客户端,因此您只需要一个执行线程。