Переполнение стека при использовании модели System.Net.Sockets.Socket.AcceptAsync - PullRequest
13 голосов
/ 17 октября 2010

Что касается C # и метода .NET System.Net.Sockets.Socket.AcceptAsync, потребуется обработать возвращаемое значение «ложь», чтобы обработать немедленно доступное состояние SocketAsyncEventArgs из синхронно обработанного соединения. , Microsoft предоставляет примеры (найденные на странице класса System.Net.Sockets.SocketAsyncEventArgs), которые вызовут переполнение стека, если имеется большое количество ожидающих соединений, которые могут быть использованы в любой системе, реализующей их модель обработки.

Другие идеи для решения этой проблемы заключаются в создании цикла, который вызывает метод-обработчик, с условием, что возвращаемое значение Socket.AcceptAsync равно false, и прерывании цикла (чтобы разрешить отложенную обработку), если значение указывает, что операция завершается асинхронно (true). Однако это решение также вызывает уязвимость переполнения стека из-за того факта, что обратный вызов, связанный с SocketAsyncEventArgs, переданным Socket.AcceptAsync, имеет в конце метода вызов Socket.AcceptAsync, который также имеет цикл для немедленного доступные, синхронно принятые, соединения.

Как видите, это довольно серьезная проблема, и мне еще предстоит найти хорошее решение, которое не включает System.Threading.ThreadPool и создание множества других методов и обработки планирования. Насколько я вижу, модель асинхронного сокета, относящаяся к Socket.AcceptAsync, требует большего, чем продемонстрировано в примерах на MSDN.

Есть ли у кого-нибудь чистое и эффективное решение для обработки немедленно ожидающих соединений, которые синхронно принимаются от Socket.AcceptAsync, без создания отдельных потоков для обработки соединений и без использования рекурсии?

Ответы [ 4 ]

7 голосов
/ 17 октября 2010

Я бы не использовал AcceptAsync, а скорее BeginAccept / EndAccept, и правильно реализовал бы общий асинхронный шаблон, то есть проверял CompletedSynchronously, чтобы избежать обратных вызовов в потоке обратных вызовов для завершенных операций.

См. Также AsyncCallBack CompletedSynchronously


Правка для использования AcceptAsync:

Документация MSDN явно говорит, что обратный вызов НЕ будет вызываться для операций, которые выполняются синхронно.Это отличается от обычного асинхронного шаблона, в котором обратный вызов всегда вызывается.

Возвращает true, если операция ввода-вывода находится в состоянии ожидания.Событие SocketAsyncEventArgs.Completed для параметра e будет вызвано после завершения операции.Возвращает false, если операция ввода-вывода завершена синхронно.Событие SocketAsyncEventArgs.Completed для параметра e не будет вызываться, и объект e, переданный в качестве параметра, может быть проверен сразу после возврата вызова метода для получения результата операции.

В настоящее время я не работаюне вижу, как цикл не решит проблему переполнения стека.Может быть, вы можете быть более конкретным в отношении кода, который вызывает проблему?


Редактировать 2: Я думаю о коде, как этот (только в отношении AcceptAsync, остальное было просто получитьрабочее приложение, чтобы опробовать его):

static void Main(string[] args) {
    Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, 4444));
    listenSocket.Listen(100);
    SocketAsyncEventArgs e = new SocketAsyncEventArgs();
    e.Completed += AcceptCallback;
    if (!listenSocket.AcceptAsync(e)) {
        AcceptCallback(listenSocket, e);
    }
    Console.ReadKey(true);
}

private static void AcceptCallback(object sender, SocketAsyncEventArgs e) {
    Socket listenSocket = (Socket)sender;
    do {
        try {
            Socket newSocket = e.AcceptSocket;
            Debug.Assert(newSocket != null);
            // do your magic here with the new socket
            newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!"));
            newSocket.Disconnect(false);
            newSocket.Close();
        } catch {
            // handle any exceptions here;
        } finally {
            e.AcceptSocket = null; // to enable reuse
        }
    } while (!listenSocket.AcceptAsync(e));
}
3 голосов
/ 17 октября 2010

Я решил эту проблему, просто изменив расположение цикла. Вместо того чтобы рекурсивно вызывать обработчик принятия изнутри самого себя, оборачивая код в цикл do-while с условием «! Socket.AcceptAsync (args)», предотвращается переполнение стека.

Причиной этого является то, что вы используете поток обратного вызова для обработки соединений, которые доступны немедленно, прежде чем пытаться асинхронно ожидать появления других соединений. Это эффективно повторно использует объединенную нить.

Я ценю ответы, но по какой-то причине никто из них не щелкнул со мной и не решил проблему. Тем не менее, кажется, что-то там заставило меня задуматься над этой идеей. Он избегает ручной работы с классом ThreadPool и не использует рекурсию.

Конечно, если у кого-то есть лучшее решение или даже альтернатива, я буду рад его услышать.

1 голос
/ 17 октября 2010

Я не посмотрел внимательно, но пахнет так, как будто это может быть полезно (см. Раздел «погружение в стек»):

http://blogs.msdn.com/b/mjm/archive/2005/05/04/414793.aspx

0 голосов
/ 30 июня 2011
newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

Проблема с этим фрагментом выше в том, что это заблокирует вашу следующую операцию принятия.

Лучший способ такой:

while (true)
{
   if (e.SocketError == SocketError.Success)
   {
      //ReadEventArg object user token
      SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
      Socket socket = ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;

      if (!socket.ReceiveAsync(readEventArgs))
         ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessReceiveEx), readEventArgs); .
    }
    else
    {
       HadleBadAccept(e);
    }

    e.AcceptSocket = null;

    m_maxNumberAcceptedClients.WaitOne();
    if (listenSocket.AcceptAsync(e))
       break;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...