Блокировка Telnet TCP-сервер C #

Я пишу TCP-сервер на C # и столкнулся со странной и потенциально опасной проблемой.

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

  1. Сокет C #, прослушивающий порт, используя метод AcceptAsync для приема входящих подключений.
  2. Отключение принятых соединений с использованием ThreadPool для завершения принятия.

Все работает достаточно хорошо, однако все тормозит, если кто-то подключится к порту.

Симптомы:

  • Если я подключусь к своему серверу через telnet и не отправлю никаких данных (т.е. не нажму никакие клавиши), сервер никогда не завершит прием соединения.

  • Мой обратный вызов SocketAsyncEventArgs.Completed никогда не выполняется для соединения telnet.

  • Хуже того, все дальнейшие соединения блокируются / ставятся в очередь и никогда не принимаются моим кодом. Они переведены в состояние CLOSE_WAIT:

    TCP 127.0.0.1:8221 chance:53960 CLOSE_WAIT

    TCP 127.0.0.1:8221 chance:53962 CLOSE_WAIT

    TCP 127.0.0.1:8221 chance:53964 CLOSE_WAIT

Любой совет будет принят во внимание.

StartAccept:

private void StartAccept(SocketAsyncEventArgs AcceptArgs)
{
    CurrentAcceptArgs = AcceptArgs;
    AcceptArgs.AcceptSocket = null;

    if (AcceptArgs.Buffer == null ||
        AcceptArgs.Buffer.Length < 1024)
    {
        AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
    }

    if (MainSocket != null)
    {
        lock (MainSocket)
        {
            // If this is false, we have an accept waiting right now, otherwise it will complete aynsc
            if (MainSocket.AcceptAsync(AcceptArgs) == false)
            {
                ThreadPool.QueueUserWorkItem(FinishAccept, AcceptArgs);
                StartAccept(GetConnection());
            }
        }
    }
}

Завершенный обратный вызов для приема соединений:

protected override void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
    PWClientRemote RemoteClient = e.UserToken as PWClientRemote;

    // Determine which type of operation just completed and call the associated handler.
    switch (e.LastOperation)
    {
        case SocketAsyncOperation.Accept:
            StartAccept(GetConnection());
            ThreadPool.QueueUserWorkItem(FinishAccept, e);
            break;
        default:
            base.OnIOCompleted(sender, e);
            break;
    }
}

Завершить Принять:

private void FinishAccept(object StateObject)
{
    SocketAsyncEventArgs args = (SocketAsyncEventArgs)StateObject;
    FinishAcceptInternal(args);
}

Вот wirehark от подключения telnet, но до отправки данных:

No.     Time        Source                Destination           Protocol Length Info
  1 0.000000    192.168.1.146         192.168.1.109         TCP      66     59766 > 8221 [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
  2 0.000076    192.168.1.109         192.168.1.146         TCP      66     8221 > 59766 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
  3 0.000389    192.168.1.146         192.168.1.109         TCP      60     59766 > 8221 [ACK] Seq=1 Ack=1 Win=65536 Len=0

Это должно быть полное рукопожатие для установления моего соединения, но событие Completed никогда не возникает.


person Stryck    schedule 16.01.2012    source источник
comment
Вы искали в Google CLOSE_WAIT? Если да, то вы знаете, почему вы его получили. А где код?   -  person jgauffin    schedule 17.01.2012
comment
Можете ли вы указать тайм-аут для попытки подключения?   -  person Fantius    schedule 17.01.2012
comment
Да, я понимаю, что мои соединения застряли в плохом состоянии TCP. Ищу решения. В настоящее время злоумышленник может повесить мой сервер, просто подключившись к нему через telnet.   -  person Stryck    schedule 17.01.2012
comment
Если я подключусь к своему серверу через telnet и не отправлю никаких данных (т.е. не нажму никакие клавиши), сервер никогда не завершит принятие соединения, приостановите ваш процесс, когда это произойдет. На какой линии висит?   -  person wal    schedule 17.01.2012
comment
Технически их нет в моем коде. Я предполагаю, что он застрял где-то в структуре, хотя приостановка всех потоков не дает мне больше понимания. Пауза в Application.Run (). AcceptAsync вызывается и должен возвращать асинхронно при входящем соединении и вызывать OnIOCompleted с SocketAsyncOperation.Accept. В данном случае этого не происходит. Он действует так, как будто он начинает принимать соединение, но не уходит достаточно далеко, чтобы перезвонить в мой код.   -  person Stryck    schedule 17.01.2012
comment
Вы блокируете MainSocket из разных потоков, и код внутри блокировки делает разные вещи в зависимости от того, ожидают ли соединения ... здесь что-то пахнет вашим основным кодом прослушивания сокета.   -  person Andrew Barber    schedule 17.01.2012


Ответы (2)


Отвечая на свой вопрос, я нашел первопричину:

Ошибка в этой строке:

if (AcceptArgs.Buffer == null ||
    AcceptArgs.Buffer.Length < 1024)
{
    AcceptArgs.SetBuffer(new byte[1024], 0, 1024);
}

Это связано с тем, что если вы установите буфер, AcceptAsync будет блокироваться до тех пор, пока не получит некоторые данные.

Из MSDN:

Минимальный требуемый размер буфера составляет 288 байт. Если указан больший размер буфера, то Socket будет ожидать некоторых дополнительных данных, отличных от данных адреса, полученных вызовом Winsock AcceptEx, и будет ждать, пока эти дополнительные данные не будут получены.

Мой исправленный код:

// We set a null buffer here.
// If we set a valid buffer, the accept will expect data
// and will hang unless it gets it.
AcceptArgs.SetBuffer(null, 0, 0);

Я не уверен, что это точное исправление, установка буфера размером 288 байт или меньше, похоже, не решила проблему. Только установка буфера равным нулю приводила к возникновению события Completed при подключении без отправки данных.

person Stryck    schedule 17.01.2012

Похоже, GetConnection где-то заблокирован. Обычно, если сервер не находится под большой нагрузкой, асинхронные операции могут выполняться синхронно. Также тот факт, что AcceptAsync вернул false или был вызван метод обратного вызова, означает, что асинхронная операция завершена, и ваш код должен проанализировать результаты.

Ниже представлен простой скелет асинхронного TCP-сервера, который принимает соединения асинхронно.

void StartServer()
{
    Socket serverSocket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    serverSocket.Bind(new IPEndPoint(addr, port));
    s.Listen(5000);

    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptCompleted);
    args.UserToken = serverSocket;

    if ( !serverSocket.AcceptAsync(args) )
        AcceptCompleted(this, args);
}

void AcceptCompleted(object obj, SocketAsyncEventArgs args)
{
    Socket client = args.AcceptSocket;
    if (args.SocketError != SocketError.Success)
        return;

    StartClientOperations(args.AcceptSocket);

    args.AcceptSocket = null;
    Socket s = (Socket)args.UserToken;
    if (!s.AcceptAsync(args))
        AcceptCompleted(this, args);
}

void StartClientOperations(Socket newClient) 
{
    //start other asynchronous operations here with the client socket
}
person Vadym Stetsiak    schedule 17.01.2012
comment
GetConnection () происходит до установления TCP-соединения. Это также просто ссылка на простую очередь пула соединений. - person Stryck; 17.01.2012
comment
В вашем коде GetConnection был вызван после того, как было принято фактическое TCP-соединение. Не могли бы вы подробнее рассказать о простой очереди пула соединений и о ее роли в вашем приложении? - person Vadym Stetsiak; 17.01.2012