Подтвердить что ты не робот

Переполнение стека при использовании модели System.Net.Sockets.Socket.AcceptAsync

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

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

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

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

4b9b3361

Ответ 1

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

См. также AsyncCallBack завершена синхронно


Измените требование использования AcceptAsync:

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

Возвращает 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));
}

Ответ 2

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

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

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

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

Ответ 4

newSocket.Send(Encoding.ASCII.GetBytes("Hello socket!")); 
newSocket.Disconnect(false); 
newSocket.Close(); 

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

Лучший способ:

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