#c# #stream
#c# #поток
Вопрос:
Мне нужен экономичный по памяти и времени способ разделения входящего потока данных на основе разделителя. Поток является сетевым потоком, и поступающие «сообщения» разделяются на CRLF
. Ранее я делал это путем преобразования входящих данных в строку с использованием UTF8, затем проверял наличие CRLF, и если он существует, я разбивал на основе этого, однако это не очень хороший способ решить проблему, поскольку поступает все больше и больше сообщений. Кроме того, я мог бы получать блоки данных, содержащие 1 сообщение, и я мог бы получать блоки данных, содержащие 10 сообщений, и даже некоторые, которые содержат только части сообщений.
Итак, это то, что я придумал до сих пор. Используйте memorystream в качестве буфера, и когда поступают данные, считывайте данные в поток памяти. Если я нахожу разделитель (CRLF), я беру все данные в memorystream и вызываю MessageReceived для этого, затем я продолжаю. Есть мысли по этому поводу?
[Править]
Хорошо, я думаю, мне нужно лучше объяснить, что я хочу сделать. Используемый протокол — это IRC-протокол, который отправляет «сообщения» или, если хотите, «команды», разделенные CRLF
. Я использую класс сокетов в C # с BeginReceive и EndReceive, поэтому все выполняется асинхронно. Класс, который я пишу, называется MessageConnection. Он получает данные из tcp-сокета, и всякий раз, когда данный разделитель найден (в данном случае CRLF
), я хочу, чтобы он вызывал функцию с именем onMessage, которая принимает полученное сообщение в качестве параметра. Я решил точно такую же проблему, прежде чем использовать StringBuilder в качестве буфера и добавлять новую строку в StringBuilder всякий раз, когда получал данные, затем я бы разделил строку, возвращаемую StringBuilder, на основе разделителя, очистил StringBuilder и вставил последнюю часть операции разделения. После этого я перебираю разделенный массив (без последнего элемента) и вызываю onMessage. Этот сервер кажется неэффективным способом решения проблемы, потому что я делаю много преобразований в строки и из строк — что, как говорят, не очень хорошо, поэтому я подумал, что должен быть простой способ решить это без необходимости думать в строках, только в байтовых массивах, и преобразовывать в строку только тогда, когда у меня есть байтовый массив, который представляет фактическое «сообщение», и это то, с чем я хочу помочь.
Комментарии:
1. Я не предполагаю, что у вас есть контроль над системой обмена сообщениями? Я спрашиваю только потому, что это крайне ненадежное средство связи; почти каждый крупный сетевой протокол либо использует блоки фиксированного размера, либо включает размер сообщения в качестве поля внутри сообщения…
2. @Alxandr — ваши данные представлены в каком-либо определенном формате? это просто строки, передаваемые по проводу? или вы разбиваете каждое сообщение по тегу узла, как при использовании XML? Было бы здорово получить немного больше информации о типе потока данных, отправляемых по проводам.
3. Нет, формат простой. Это «сообщения» (могут быть любыми, но в данном случае это IRC-протокол), разделенные <CRLF> . Чего я не хочу, так это просто запускать событие (onMessage) с сообщением в качестве параметра (используя мои собственные MessageEventArgs) всякий раз, когда я получаю CRLF.
4. Удаленный предложенный ответ после того, как вопрос был переработан.
5. поскольку это «команды», есть ли способ нормализовать сообщения до меньшего набора (например, перечисления), чтобы вы передавали меньше данных? Или эти сообщения имеют свободную форму, поэтому требуется текст? Управляете ли вы отправителем и получателем сообщений?
Ответ №1:
Я думаю, у вас действительно правильная идея. Просто сделайте это с помощью массива байтов.
Вот как я бы это сделал, чисто непроверенный, и его можно было бы оптимизировать….
byte[] m_LongBuffer;
byte[] m_SmallBuffer;
void ReceiveCallback(IAsyncResult iar)
{
//m_SmallBuffer contains the data read from the stream
//Append it to m_LongBuffer
int bytesread = socket.EndReceive(iar);
m_LongBuffer = m_LongBuffer.Concat(m_SmallBuffer.Take(bytesread)).ToArray();
int startpoint = 0;
int splitpoint = 0;
int lastendpoint = 0;
bool twochar = false;
do
{
int i = 0;
for(i = 0;i < m_LongBuffer.Length; i)
{
if((m_LongBuffer[i] == 0x0A) || (m_LongBuffer[i] == 0x0D))
{
splitpoint = i;
if((m_LongBuffer[i 1] == 0x0A) || (m_LongBuffer[i 1] == 0x0D))
twochar=true;
else
twochar=false;
lastendpoint = splitpoint;
String message = ASCII.ASCIIEncoding.GetString(m_LongBuffer.Skip(startpoint).Take(splitpoint - startpoint).ToArray());
//Do something with the message
startpoint = splitpoint (twochar ? 2 : 1);
break;
}
}
if(i >= m_LongBuffer.Length)
splitpoint = -1;
} while (splitpoint != -1);
m_LongBuffer = m_LongBuffer.Skip(lastendpoint).ToArray();
}
Комментарии:
1. Ваш
i
входif(i >= m_LongBuffer.Length)
выходит за рамки2. Я не проверял, но, судя по всему, вам следует инициализировать свой
i
tostartpoint
в вашем цикле. Что-то вроде этого:for(i = startpoint;i < m_LongBuffer.Length; i)
Ответ №2:
Мне приходилось делать что-то подобное некоторое время назад. Я решил это, создав поток производитель / потребитель. Производитель (в вашем случае, то, что считывает сетевой поток) записывает байты в поток, а потребитель создает StreamReader
подключенный к потоку.
Конечно, для этого требуется другой поток для потребителя, но это предотвращает проблемы, которые могут возникнуть, если обратный вызов занимает слишком много времени и вы в конечном итоге пропускаете сообщения.
Я написал поток, который я назвал ProducerConsumerStream
в статье. Смотрите это на http://www.informit.com/guides/content.aspx?g=dotnetamp;seqNum=852 .
Предыдущее решение проблемы включало в себя разбор массива байтов самостоятельно. Это сработало, но было не таким гибким, как этот потоковый подход.