Каков правильный способ реализации линейного сетевого рабочего класса?

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

Мой первый метод состоял в том, чтобы создать StreamReader с именем _reader, используя TcpClient.GetStream() в качестве основы, и просто делая _reader.ReadLine(). Преимущество этого заключается в том, что поток фонового прослушивателя автоматически останавливается до тех пор, пока не будет получена строка. Проблема в том, что когда я отключаю и останавливаю поток слушателя (или приложение просто завершает работу), я не уверен, насколько это хорошо для сборки мусора и т. д., что поток с ожидающим ReadLine() просто уничтожается. Может ли поток стать осиротевшим и каким-то образом остаться в фоновом режиме, воруя ресурсы?

Мой второй метод состоял в том, чтобы создать NetworkStream с именем _reader на основе того же TcpClient.GetStream() и создать цикл, который проверяет _reader.DataAvailable и continue цикл while, если он ложный, и в противном случае вталкивает байты в StringBuilder, который мгновенно проверяется на \r\n и извлекает все целые линии. Это дает тот же эффект, что и ReadLine при извлечении данных, но мне не нравится постоянный цикл _reader.DataAvailable. На моем процессоре Core i5 он не требует никакого процессора, но на моем гораздо более мощном ноутбуке i9 он постоянно крадет виртуальный процессор. Thread.Sleep(1) «решает» эту проблему, но кажется грязным решением, и многочисленные статьи в Интернете классифицируют это как запах кода.

Итак, что было бы правильным решением?


person carlsb3rg    schedule 27.04.2011    source источник


Ответы (3)


Если вы прерываете поток прослушивателя, то это плохо. Но если приложение выйдет (или если вы закроете базовый сокет), то все будет в порядке.

Перечислите большинство других, я не рекомендую опрос. Я бы предложил решение, использующее асинхронное чтение и запись, хотя его гораздо сложнее запрограммировать.

person Stephen Cleary    schedule 27.04.2011
comment
Я просмотрел асинхронные методы, но документация MSDN — и особенно код примера — полна недостатков и ошибок. Я до сих пор не нашел хорошего руководства по их использованию. - person carlsb3rg; 27.04.2011
comment
Истинный. Наиболее полезные ресурсы предназначены для неуправляемых приложений TCP/IP. У меня есть несколько рекомендаций в разделе 2 моих часто задаваемых вопросов, но это не учебный материал. - person Stephen Cleary; 27.04.2011
comment
Я никогда не прерываю ветку. Это фоновый поток, который автоматически умирает при закрытии приложения. Цикл прослушивания while (tcpClient.Connected) {...}. Если я использую .ReadLine(), то невозможно заставить выполнение программы перейти к следующей проверке while и изящно завершить цикл. Вот почему я хочу использовать метод NetworkStream, так как я могу непрерывно зацикливаться, пока данные недоступны, и корректно завершать работу при необходимости. Хотя мне понравился ваш FAQ. Вы должны перейти к битам, которые предназначены для будущей записи FAQ. - person carlsb3rg; 28.04.2011
comment
Я только что понял, что мне не следует использовать TcpClient.Connected для моего условия while, так как оно никогда не изменится, если цикл на самом деле не считывает данные. Мне даже не следует использовать DataAvailable согласно этот пост :) - person carlsb3rg; 28.04.2011
comment
Завершение этого часто задаваемых вопросов является в моем списке дел. Наряду со многими другими вещами. ;) - person Stephen Cleary; 28.04.2011
comment
Я нашел другую IRC-библиотеку и просмотрел исходный код, и вместо этого они используют Socket, ReadAsync и WriteAsync. Может быть, они лучше подходят для длительной двунаправленной связи TCP? - person carlsb3rg; 28.04.2011
comment
*Async — это более новые асинхронные API, добавленные, чтобы избежать накладных расходов на выделение, неявных с Begin*/End*. Я написал несколько оболочек сокетов как часть Nito.Async, но они используют Begin*/End* по двум причинам: 1) разница в производительности незаметна, за исключением небольшого процента приложений, и 2) вам действительно нужно знать конкретные сценарии использования приложений, чтобы действительно получить прирост производительности; библиотеке общего назначения, подобной моей, было бы трудно найти баланс между удобством использования и производительностью, если бы она была построена на *Async. - person Stephen Cleary; 28.04.2011

Вот кое-что, что поможет вам двигаться вперед.

Обратите внимание, что не очень эффективно использовать string при построении сообщений. Но это делает все намного более читабельным.

public abstract class LineBasedChannel
{   
    Socket _socket;
    string _inbuffer = string.Empty;
    Encoding _encoding;
    byte[] _buffer = new byte[8192];

    public LineBasedChannel(Socket socket)
    {
        _socket = socket;
        _encoding = Encoding.ASCII;
        _sb = new StringBuilder();
    }

    public void Start()
    {
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);
    }

    public void OnRead(IAsyncResult res)
    {
        var bytesRead = _socket.EndReceive(res);
        if (bytesRead == 0)
        {
            HandleDisconnect();
            return;
        }

        _inbuffer += _encoding.GetString(_buffer, 0, bytesRead);
        _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None,OnRead, null);

        while (true)
        {
            int pos = _inbuffer.IndexOf("\r\n");
            if (pos == -1)
                break;

            OnReceivedLine(_inbuffer.SubString(0, pos+2);
            _inbuffer = _inbuffer.Remove(0,pos+1);
        }
    }

    protected abstract void OnReceivedLine(string line);
}

public class IrcTcpChannel : LineBasedChannel
{
    protected override void OnReceivedLine(string line)
    {
        var cmd = new IrcCommand();
        cmd.Channel = line.SubString(x,y);
        cmd.Message = line.SubString(z,a);
        CommandReceived(this, new IrcCommandEventArgs(cmd));
    }

    public event EventHandler<IrcCommandEventArgs> CommandReceived = delegate {};
}

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

person jgauffin    schedule 29.04.2011

Старый вопрос, проблема должна быть решена давно. В любом случае, это очень похоже на еще более старый вопрос c# - Каков хороший метод обработки сетевых потоков ввода-вывода на основе строк? - Переполнение стека

Если это так, ответ должен предоставить часть решения. По сути, это класс, который предоставляет метод Process(byte[]), возвращающий IEnumerable<string>, который сохраняет состояние частичного содержимого, еще не формирующего полную строку.

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

person Stéphane Gourichon    schedule 19.08.2015