1

I am working on writing a really simple RPC library using C# and sockets for a class assignment. I have the send feature working which is nice but I am testing how my KillServer function works. I have implemented a lot of threading in the Server library and I am not sure that I have gotten down the proper way of canceling and softly killing the threads.

My StartServerThread method:

public void StartServer()
        {
            ServerThread = new Task(()=> 
            {
                _Socket.Listen(100);
                // Enter a loop waiting for connection requests
                // Or because I am RDM I don't need connections
                while (!_TokenSource.IsCancellationRequested)
                {
                    try
                    {
                        var newClient = _Socket.Accept();

                        _Clients.Add(newClient, ClientThread(newClient));
                    }
                    catch (OperationCanceledException)
                    {
                        Debug.WriteLine("Canceled");
                    }
                }


                foreach (var client in _Clients)
                {
                    client.Key.Disconnect(false);
                }

            },_TokenSource.Token, TaskCreationOptions.LongRunning);

            ServerThread.Start();
        }

My problem here is once the thread hits the _Socket.Accept() line it is blocking. So even if I call Cancel on the TokenSource the thread is still stuck at that line and doesn't nicely end. I am having a hard time designing this library in such a way that uses threading and allows good performance with multiple clients connected.

Bailey Miller
  • 1,376
  • 2
  • 20
  • 36
  • Have you tried a non-blocking socket? https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.blocking?view=netframework-4.7.2 – CPerson Sep 11 '18 at 20:41
  • have you tried to call _Socket.Close() ? this full close the listener and release all resources – Lineker Sep 11 '18 at 20:43
  • 1
    Look at [Thread.Interrupt](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.interrupt?redirectedfrom=MSDN&view=netframework-4.7.2#System_Threading_Thread_Interrupt) method , and Close method of socket both of them unblock clientThread – saeed Sep 12 '18 at 04:48

2 Answers2

1

There are several possibilities how to solve this problem. You can use already mentioned non-blocking sockets or you can use asynchronous functions like Socket.​Begin​Accept. Here is some example from MSDN.

The important code follows.

while (true)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made before continuing.  
    allDone.WaitOne();  
}  

Here the manual reset event allDone is set in callback function when new TCP connection is accepted that wakes up while cycle in previous code to call Socket.​Begin​Accept once again.

public static void AcceptCallback(IAsyncResult ar)    
{  
    // Signal the main thread to continue.  
    allDone.Set();  
    // Get the socket that handles the client request.  
    Socket listener = (Socket) ar.AsyncState;  
    Socket handler = listener.EndAccept(ar);
    ...

Now, if you want to stop your thread from accepting new calls you can 'misuse' event allDone and set it to wake up your 'BeginAccept' thread that is waiting for event allDone. Then the 'BeginAccept' thread can check TokenSource.IsCancellationRequested if loop cycle should continue or exit.

// MODIFY while contition.
while (!_TokenSource.IsCancellationRequested)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made before continuing.  
    allDone.WaitOne();  
}  

And here is the code you call when you want to exit 'BeginAccept' thread.

 // Don't continue with the while cycle.
 _TokenSource.Cancel();

 // Wake up thread calling BeginAccept().
 allDone.Set()

Another possibility is using 2 instances of events. Something like this.

// Declared somewhere to be accessible from both threads.
ManualResetEvent exitThread;


while (true)
{  
    // Set the event to nonsignaled state.  
    allDone.Reset();  

    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

    // Wait until a connection is made or thread is exiting 
    if (WaitHandle.WaitAny(new WaitHandle[]{exitThread,allDone})==0)
    {
        break;      
    }
}  

And while cycle from second thread can now be stopped by executing.

exitThread.Set();

Code WaitHandle.WaitAny() is waiting until any of 2 events is signalized and when exitThread is signalized (WaitHandle.Wait() returns 0) it jumps out of loop.

But now another problem arises. Because you have already called Socket.​Begin​Accept when you call Socket.​Close - callback AcceptCallback is fired. And because socket has already been closed, method Socket.EndAccept will throw ObjectDisposedException. So you must catch and ignore this exception in callback method AcceptCallback. Here are more details about Socket.​Begin​Receive which applies to Socket.​Begin​Accept too.

With the arrival of .NET Task library there is another maybe more elegant solution by using extension method Accept​Async but I can't find some nice example on Internet. And I personally prefer 'old-school' BeginXYZ()-EndXYZ() asynchronous model - maybe because I'm too old...

Timmy_A
  • 1,102
  • 12
  • 9
0

Create local connection in the same application to unlock this call:

Socket handler = listener.Accept();

Code will look like this:

Socket clientToUnlockAccept = new Socket(AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp);

clientToUnlockAccept.Connect(localEndPoint) 
yW0K5o
  • 913
  • 1
  • 17
  • 32