Повторное использование асинхронного сокета: последующие попытки подключения не удаются - PullRequest
7 голосов
/ 23 апреля 2011

Я пытаюсь повторно использовать сокет в асинхронном HTTP-клиенте, но не могу подключиться к хосту во второй раз.Я в основном трактую свой асинхронный HTTP-клиент как конечный автомат со следующими состояниями:

  • Доступен: сокет доступен для использования
  • Соединение: сокет подключается к конечной точке
  • Отправка: сокет отправляет данные в конечную точку
  • Получение: сокет получает данные от конечной точки
  • Сбой: произошел сбой сокета
  • ОчиститьВверх: очистка состояния сокета

В состоянии соединения я вызываю BeginConnect:

private void BeginConnect()
{
    lock (_sync) // re-entrant lock
    {
        IPAddress[] addersses = Dns.GetHostEntry(_asyncTask.Host).AddressList;

        // Connect to any available address
        IAsyncResult result = _reusableSocket.BeginConnect(addersses, _asyncTask.Port, new AsyncCallback(ConnectCallback), null);
    }
}

Метод обратного вызова изменяет состояние на Sending после успешного подключениябыло установлено:

private void ConnectCallback(IAsyncResult result)
{
    lock (_sync) // re-entrant lock
    {
        try
        {
            _reusableSocket.EndConnect(result);

            ChangeState(EClientState.Sending);
        }
        catch (SocketException e)
        {
            Console.WriteLine("Can't connect to: " + _asyncTask.Host);
            Console.WriteLine("SocketException: {0} Error Code: {1}", e.Message, e.NativeErrorCode);
            ThreadPool.QueueUserWorkItem(o =>
            {
                // An attempt was made to get the page so perform a callback
                ChangeState(EClientState.Failed);
            });
        }
    }
}

При очистке я Shutdown сокет и Disconnect с флагом повторного использования:

private void CleanUp()
{
    lock (_sync) // re-entrant lock
    {
        // Perform cleanup 
        if (_reusableSocket.Connected)
        {
            _reusableSocket.Shutdown(SocketShutdown.Both);
            _reusableSocket.Disconnect(true);
        }
        ChangeState(EClientState.Available);
    }
}

Последующие вызовы BeginConnect приводят к тайм-ауту иисключение:

SocketException: попытка подключения не удалась, потому что подключенная сторона не ответила должным образом через определенный промежуток времени, или не удалось установить соединение, поскольку подключенный хост имеет faiпривело к ответу XX.XXX.XX.XX: 80

Код ошибки: 10060

Вот трассировка состояния:

Initializing...
Change State: Connecting
Change State: Sending
Change State: Receiving
Change State: CleanUp
Callback:     Received data from client 0 // <--- Received the first data 
Change State: Available
Change State: Connecting // <--- Timeout when I try to reuse the socket to connect to a different endpoint

Что у меня естьсделать, чтобы иметь возможность повторно использовать сокет для подключения к другому хосту?

Примечание: я не пытался повторно подключиться к тому же хосту, но Я предполагаю происходит то же самое (т.е. не удается подключиться).

Обновление
Я обнаружил следующее примечание в документации BeginConnect :

Если этот сокет ранее был отключен, то BeginConnect должен вызываться в потоке, который не завершится, пока операция не будет завершена.Это ограничение основного поставщика.Также конечная точка, которая используется, должна отличаться.

Я начинаю задумываться, не связана ли моя проблема с этим ... Я подключаюсь к другой EndPoint, но что они означают, что поток, из которого мы вызываем BeginConnect, не долженвыйти до завершения операции?

Обновление 2.0:
Я задал связанный вопрос и попытался использовать вызовы "Семейство асинхронных" вместо вызовов "Начать семейство", но яполучите ту же проблему !!!

Ответы [ 2 ]

2 голосов
/ 23 апреля 2011

Я прокомментировал этот вопрос: какая выгода от повторного использования сокетов в C # о повторном использовании сокетов с использованием Disconnect(true)/DisconnectEx(), и это может вам помочь.

Лично я считаю, что это слишком оптимизацияв коде клиента.

Повторно обновите 1 до вашего вопроса;нет, в этом случае вы получите исключение AbortedOperation (см. здесь: VB.NET 3.5 SocketException при развертывании, но не на компьютере разработчика ), и документы будут неправильными, если вы работаете в Vista илипозже, поскольку он не обеспечивает выполнение «поток должен существовать до тех пор, пока не завершится перекрывающийся ввод-вывод», которое применяют предыдущие операционные системы.

Как я уже сказал в ответе на связанный вопрос;использование этой функции для установления исходящего соединения не имеет особого смысла.Вполне вероятно, что он изначально был добавлен в Winsock API для поддержки повторного использования сокетов для AcceptEx() на входящих соединениях, где на очень загруженном веб-сервере, который использовал TransmitFile() для отправки файлов клиентам (именно здесь кажется отключение для повторного использования)чтобы возникли).Документы утверждают, что он не очень хорошо работает с TIME_WAIT и поэтому использовать его для соединений, где вы инициируете активное закрытие (и, следовательно, помещаете сокет в TIME_WAIT, см. здесь ), на самом делеимеет смысл.

Можете ли вы объяснить, почему вы считаете, что эта микрооптимизация действительно необходима в вашем случае?

0 голосов
/ 27 апреля 2011
...