Proper way to stop TcpListener

2019-01-12 17:51发布

I am currently using TcpListener to address incoming connections, each of which are given a thread for handling the communication and then shutdown that single connection. Code looks as follows:

TcpListener listener = new TcpListener(IPAddress.Any, Port);
System.Console.WriteLine("Server Initialized, listening for incoming connections");
listener.Start();
while (listen)
{
     // Step 0: Client connection
     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

The listen variable is a boolean that is a field on the class. Now, when the program shuts down I want it to stop listening for clients. Setting listen to false will prevent it from taking on more connections, but since AcceptTcpClient is a blocking call, it will at minimum take the next client and THEN exit. Is there any way to force it to simply break out and stop, right then and there? What effect does calling listener.Stop() have while the other blocking call is running?

9条回答
Bombasti
2楼-- · 2019-01-12 18:26

Probably best to use the asynchronous BeginAcceptTcpClient function. Then you can just call Stop() on the listener as it won't be blocking.

查看更多
狗以群分
3楼-- · 2019-01-12 18:27

Don't use a loop. Instead, call BeginAcceptTcpClient() without a loop. In the callback, just issue another call to BeginAcceptTcpClient(), if your listen flag is still set.

To stop the listener, since you've not blocked, your code can just call Close() on it.

查看更多
Animai°情兽
4楼-- · 2019-01-12 18:28

There are 2 suggestions I'd make given the code and what I presume is your design. However I'd like to point out first that you should really use non-blocking I/O callbacks when working with I/O like network or filesystems. It's far FAR more efficient and your application will work a lot better though they are harder to program. I'll briefly cover a suggested design modification at the end.

  1. Use Using(){} for TcpClient
  2. Thread.Abort()
  3. TcpListener.Pending()
  4. Asynchronous rewrite

Use Using(){} for TcpClient

*** Note that you should really enclose your TcpClient call in a using(){} block to ensure that TcpClient.Dispose() or TcpClient.Close() methods are called even in the event of an exception. Alternately you can put this in the finally block of a try {} finally {} block.

Thread.Abort()

There are 2 things I see you could do. 1 is that if you have started this TcpListener thread from another you can simply call Thread.Abort instance method on the thread which will cause a threadabortexception to be thrown within the blocking call and walk up the stack.

TcpListener.Pending()

The second low cost fix would be to use the listener.Pending() method to implement a polling model. You would then use a Thread.Sleep to "wait" before seeing if a new connection is pending. Once you have a pending connection you'd call AcceptTcpClient and that would release the pending connection. The code would look something like this.

while (listen){
     // Step 0: Client connection
     if (!listener.Pending())
     {
          Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
          continue; // skip to next iteration of loop
     }

     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

Asynchronous Rewrite

Finally I would recommend that you really move to a non-blocking methodology for your application. Under the covers the framework will use Overlapped I/O and I/O completion ports to implement non-blocking I/O from your asynchronous calls. It's not terribly difficult either, it just requires thinking about your code a little differently.

Basically you would start your code with the BeginAcceptTcpClient method and keep track of the IAsyncResult that you are returned. You point that at a method whose responsible for getting the TcpClient and passing it off NOT to a new thread but to a thread off of the ThreadPool.QueueUserWorkerItem so you're not spinning up and closing a new thread for each client request (Note you may need to use your own thread pool if you have particularly long lived requests because the thread pool is shared and if you monopolize all the threads other parts of your application implemented by the system may be starved). Once the listener method has kicked off your new TcpClient to it's own ThreadPool request it calls BeginAcceptTcpClient again and points the delegate back at itself.

Effectively you're just breaking up your current method into 3 different methods that will then get called by the various parts. 1. to bootstrap everything, 2. to be the target to call EndAcceptTcpClient, kick off the TcpClient to it's own thread and then call itself again, 3. to process the client request and close it when finished.

查看更多
登录 后发表回答