如何在Winsock2 C++中停止/重新启动服务器套接字的监听和接受?

8
我在Visual Studio Pro C ++中创建了一个套接字(Winsock2)来监听端口连接(TCP)。它完美地运行,但我让它在自己的线程中运行,并希望能够关闭它,并希望稍后重新启动它。我可以终止线程而不出现问题,但这样做并不能阻止套接字接受新的客户端连接(也就是说,它仍然在接受之前关闭线程时正在进行的连接)。我可以将新的客户端连接到它,但没有其他反应......它只是接受,并且不执行任何操作。我希望能够停止它的监听和接受,并能够在同一端口上稍后告诉它重新启动。现在试图重新启动它只会告诉我该端口已被占用。

以下是listen线程函数:

DWORD WINAPI ListeningThread(void* parameter){
TCPServer *server = (TCPServer*)parameter;

try{
    server = new TCPServer(listen_port);
}catch(char* err){
    cout<<"ERROR: "<<err<<endl;
    return -1;
}

int result = server->start_listening();
if(result < 0){
    cout<<"ERROR: WSA Err # "<<WSAGetLastError()<<endl;
    return result;
}
cout<<"LISTENING: "<<result<<endl<<endl;
while(true){
    TCPClientProtocol *cl= new TCPClientProtocol(server->waitAndAccept());
    HANDLE clientThread = CreateThread(0, 0, AcceptThread, cl, 0, 0);
    cout<<"Connection spawned."<<endl;
}

return 0;
}

以下是TCPServer中相关的函数:

TCPServer::TCPServer(int port){
listening = false;
is_bound = false;

//setup WSA
int result = WSAStartup(MAKEWORD(2, 2), (LPWSADATA) &wsaData);
if(result < 0){
    throw "WSAStartup ERROR.";
    return;
}

//create the socket
result = (serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
if(result < 0){
    throw "Socket Connect ERROR.";
    return;
}

//bind socket to address/port
SOCKADDR_IN sin;
sin.sin_family = PF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = INADDR_ANY;

result = bind(serverSocket, (LPSOCKADDR) &sin, sizeof(sin));
if(result < 0){
    throw "Could not Bind socket - Make sure your selected PORT is available.";
    return;
}

is_bound = true;
}

int TCPServer::start_listening(){
int result = -1;
if(is_bound){
    //SOMAXCONN parameter (max) is a backlog:
    //  how many connections can be queued at any time.
    result = listen(serverSocket, SOMAXCONN);
    if(result >= 0)
        listening = true;
}
return result;
}

SOCKET TCPServer::waitAndAccept(){
if(listening)
    return accept(serverSocket, NULL, NULL);
else
    return NULL;
}

我尝试了closesocket()和shutdown(),但两者都报错。

非常感谢你们的时间和帮助!


Slosesocket(serverSocket) 应该是解决方案。您是如何调用它的,何时调用它的,以及产生了哪个错误?shutdown() 在这里是无关紧要的;它是用于在保持相反方向开放的情况下,在连接的 TCP 流上发送 EOF 的。 - hmakholm left over Monica
1个回答

10
首先,请确保在服务器套接字上设置了SO_REUSEADDR选项,以便重新开始监听。
然后,我猜您的问题是accept()阻塞并且无法在需要时停止它,因此您正在终止线程。这是一个不好的解决方案。正确的答案是异步I/O,即select()poll()或它们的Windows对应项。请参阅高级Winsock示例
在多线程应用程序中的一种快速而肮脏的解决方案是,在每个accept()之前检查一些is_it_time_to_stop_accepting_connections标志,然后当时间到达时,翻转标志并连接到监听端口(是的,在同一程序中)。这将取消阻止accept()并允许您执行适当的closesocket()或其他操作。
但是,认真阅读有关异步I/O的内容。

1
我同意他可能被accept调用阻塞并执行了一个邪恶的TerminateThread调用。但是,在他解决了这个问题之后,他真的需要使用SO_REUSEADDR吗?这不会潜在地隐藏任何其他与不正确关闭其监听套接字有关的错误吗?假设他忘记在启动另一个进程之前停止当前运行的服务器进程...... 我认为您更希望出现错误(因为另一个进程拥有该端口!)而不是尝试让两个进程侦听相同的端口,并具有您不希望出现的副作用。 - selbie
1
不关闭套接字将防止其他套接字绑定到同一端口和地址。SO_REUSEADDR的意义与您想象的有些不同。是的,99.9999%的套接字服务器需要设置此选项。https://dev59.com/eFnUa4cB1Zd3GeqPfvo- - Nikolai Fetissov
啊……所以如果我没有指定SO_REUSEADDR,那么如果之前的监听套接字产生了一个活动连接,绑定调用就会失败。对吗? - selbie
2
是的。即使监听套接字已关闭,但如果有从它派生的连接套接字(已接受),它将具有服务器端口作为源,您将无法绑定新的监听套接字。不设置 SO_REUSEADDR 也会防止在约2分钟内再次绑定端口(请谷歌TCP MSL)。 - Nikolai Fetissov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接