Сервер сокетов C #, как отменить поток, который застрял в socket.accept () - PullRequest
0 голосов
/ 11 сентября 2018

Я работаю над написанием действительно простой библиотеки RPC с использованием C # и сокетов для назначения классов. У меня есть работающая функция отправки, но я проверяю, как работает моя функция KillServer. Я реализовал много потоков в библиотеке сервера, и я не уверен, что нашел правильный способ отмены и мягкого уничтожения потоков.

Мой метод StartServerThread:

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

Моя проблема заключается в том, что поток попадает в строку _Socket.Accept (), которую он блокирует. Так что даже если я вызову Cancel в TokenSource, поток все еще застрянет в этой строке и не завершится. Мне трудно проектировать эту библиотеку таким образом, чтобы она использовала многопоточность и обеспечивала хорошую производительность при подключении нескольких клиентов.

1 Ответ

0 голосов
/ 12 сентября 2018

Существует несколько способов решения этой проблемы. Вы можете использовать уже упомянутые неблокирующие сокеты или асинхронные функции, такие как Socket.​Begin​Accept. Вот пример из MSDN.

Ниже приведен важный код.

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

Здесь событие ручного сброса allDone устанавливается в функции обратного вызова, когда принимается новое TCP-соединение, которое активируется во время цикла в предыдущем коде для повторного вызова Socket.​Begin​Accept.

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);
    ...

Теперь, если вы хотите, чтобы ваш поток не принимал новые вызовы, вы можете «неправильно использовать» событие allDone и настроить его на пробуждение вашего потока «BeginAccept», который ожидает событие allDone. Затем поток 'BeginAccept' может проверить TokenSource.IsCancellationRequested, должен ли цикл цикла продолжаться или завершиться.

// 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();  
}  

А вот код, который вы вызываете, когда хотите выйти из потока «BeginAccept».

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

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

Другая возможность - использовать 2 экземпляра событий. Как то так.

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

И хотя цикл из второго потока теперь можно остановить, выполнив.

exitThread.Set();

Код WaitHandle.WaitAny() ожидает, пока не будет сигнализировано любое из 2 событий, а когда сигнализируется exitThread (WaitHandle.Wait() возвращает 0), он выпадает из цикла.

Но теперь возникает другая проблема. Поскольку вы уже позвонили Socket.​Begin​Accept, когда вы звоните Socket.​Close - обратный вызов AcceptCallback сработал. И поскольку сокет уже закрыт, метод Socket.EndAccept будет выбрасывать ObjectDisposedException. Поэтому вы должны перехватить и игнорировать это исключение в методе обратного вызова AcceptCallback. Вот еще подробности о Socket.​Begin​Receive, что относится и к Socket.​Begin​Accept.

С появлением библиотеки .NET Task есть еще одно, возможно, более элегантное решение с использованием метода расширения Accept Async , но я не могу найти хороший пример в Интернете. И я лично предпочитаю асинхронную модель BeginXYZ () - EndXYZ () старой школы - возможно, потому что я слишком стар ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...