How can I stop/restart listening and accepting on a server socket in Winsock2 C++?

Sefu picture Sefu · Aug 7, 2011 · Viewed 17.3k times · Source

I made a socket (Winsock2) in Visual Studio Pro C++ to listen on a port for connections (TCP). It works perfectly, but I let it run in its own thread, and I want to be able to shut it down with the hopes of restarting it later. I can terminate the thread without problems, but doing so does not stop the socket from accepting new clients (that is, it lingers on the accepts it was doing before I closed the thread). I can connect new clients to it but nothing happens... it just accepts and that's it. What I want is to stop it from listening and accepting and then be able to tell it to start up again later on the same port. Attempting to restart it now just tells me the port is already taken.

Here is the listen thread function:

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;
}

Here are the relevant functions in 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;
}

I have tried both closesocket() and shutdown() but both of those threw errors.

Thank you all for your time and help!

Answer

Nikolai Fetissov picture Nikolai Fetissov · Aug 7, 2011

First, make sure you set SO_REUSEADDR option on the server socket to be able to re-start listening.

Then, I'm guessing, your problem is that accept() blocks and you cant stop it when you need to, so you are killing the thread. Bad solution. The right answer here is asynchronous I/O, i.e. select() or poll() or their Windows counterparts. Take a look at Advanced Winsock Samples.

A quick-and-dirty solution in a multithreaded app is to check some is_it_time_to_stop_accepting_connections flag before each accept(), then when it's time to stop, flip the flag and connect to the listening port (yes, in the same program). That will unblock the accept() and allow you to do proper closesocket() or whatever.

But seriously, read up on asynchronous I/O.