Добрый день. Долго искал решение своей проблемы, но поиски оказались тщетны.
Задача: организовать обмен сообщениями между железом и компом, посредством com порта на С# при использовании определенного протокола.
Железо основано на PIC24, ПО работает как часы.
Проблема: примерно в 95% случаях моя программа считывает не всё пришедшее сообщение, а только часть.
Вот пример, лог программы Portmon
Первое сообщение принято сразу полностью. И я в программе его нормально обработал. Второе принято двумя кусками, соответственно я этот пакет просто отбросил как не соответствующий протоколу.
Далее код. Привожу не весь чтобы не загромождать сообщение.
Функция вызываемая по событию прихода данных в порт
void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// способ 1, прием всего пакета по sp.BytesToRead;byte[] bufer = recieve_data();
// и дальше обработка....
}
Собственно функция приема сообщения из порта
public byte[] recieve_data()
{
try
{
byte[] bytes = new byte[sp.BytesToRead];
sp.Read(bytes, 0, bytes.Length);
Status = "Read successful";
return bytes;
}
catch (Exception err)
{
comptStatus = "Error in read event: " + err.Message;
return null;
}
}
Собственно на этом и возникает описанная в начале поста проблема.
Первой попыткой решить было — принимать данные не по значению поля sp.BytesToRead а по своему протоколу.
алгоритм такой же. жду события прихода какой то информации в порт. Если это старт байт, дочитываю жестко шапку пакета, оттуда читаю сколько еще надо дочитать. код такой же что и выше , просто sp.Read() вызывается не 1 раз а 3.
В результате я лишь увеличил вероятность ошибочного приема до 8% т.к. вместо одинарного считывания на 1 пакет в 10 байт, я произвожу целых 3 по 1, 4, 5 байт друг за другом. и в последних двух считываниях и возникает та же проблема не полного прочтения пакета.
Следующей попыткой был "циклический буфер". не знаю на сколько я корректно его реализовал:
public byte[] roundRecieveData()
{
try
{
List<byte> bytelist = new List<byte>();
int readLen = 0;
int _len = 0;
do
{
_len = sp.BytesToRead;
if (_len < 10 )
{;}
byte[] _byte = new byte[_len];
readLen = sp.Read(_byte, 0, _len);
for (int i = 0; i < _len ; i++)
bytelist.Add(_byte[i]);
System.Threading.Thread.Sleep(1);
}while((sp.BytesToRead > 0));
byte[] _bytes = new byte[bytelist.Count];
for (int i = 0; i < bytelist.Count ; i++)
_bytes[i] = bytelist[i];
return _bytes;
}
catch (System.Exception ex)
{
comptStatus = "Error in read event: " + ex.Message;
return null;
}
}
По моей задумке,- пришел неполный пакет. sp.BytesToRead = 4, при первом проходе цикла я читаю 4 байта, и значение sp.BytesToRead меняется на 6 и по циклу дочитывается остальной кусок. На практике выполняется только 1 проход цикла. Do while на всякий случай менял местами — не помогло.
Однако если поставить точку останова на удивительной конструкции "if (_len < 10 ) {;}" и сразу же нажать продолжить. То бишь у нас не полный пакет. то дочитка пакета происходит так как я думал в теории.
последней попыткой стал отказ от события приема байт порта и слежение за sp.BytesToRead из потока, главная функция потока
private void readThreadFn()
{
Byte[] inputBuffer;
while (true)
{
try
{
int availibleBytes = sp.BytesToRead;
while (availibleBytes > 0)
{
inputBuffer = new Byte[availibleBytes];
int readedBytes = sp.Read(inputBuffer, 0, inputBuffer.Length);
//inputBuffer = roundRecieveData(); // и так пробовал - не помогло
OnReceiveByte(inputBuffer); // вывоз события обработки данных
}
System.Threading.Thread.Sleep(1);
}
catch (System.Threading.ThreadAbortException)
{ break; }
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
Увы проблема осталась. Так же как и в простом считывании сообщения из порта в 5% случая оно читается не все сразу.
Здравствуйте, ethen, Вы писали:
E>принято двумя кусками
это нормально. при работе с потоками данных будьте готовы, что данные будут приходить частями или по полтора-два-десять склеенных "сообщений".
переписывайте алгоритм. должно быть что-то вроде:
1. появились данные на порту — читаем в буфер, пока не выполнится условие-признак окончания сообщения.
2. условием-признаком может быть — фиксированная длина сообщения, спец.байт (символ переноса строки, например) или количество байт должно идти первым делом (первые четыре байта — длина оставшейся части сообщения).
3. откусываем полные сообщения, передаём на обработку (парсинг и т.д.). остаток запоминаем и к нему приплюсовываем последующие куски сообщений.
если команды разделяются переносом строки, то можно юзать ReadLine метод — работал вроде без сбоев.
Здравствуйте, Neco, Вы писали:
N>это нормально. при работе с потоками данных будьте готовы, что данные будут приходить частями или по полтора-два-десять склеенных "сообщений". N>переписывайте алгоритм. должно быть что-то вроде: N>...
Добавлю еще:
1) первый поток читает из порта порциями данные, кладет в буфер.
2) второй поток ищет в буфере целые сообщения, выбирает их и обрабатывает сам или куда-то передает.
Естественно, не забываем о синхронизации потоков.
3) дополнительно для повышения надежности (на случай если линия связи с помехами или программа может начать читать порт в произвольный момент, с середины передаваемого сообщения): отслеживаем остающиеся в буфере куски, чтобы они не накапливались; разбираем сообщение, используя конечный автомат (по возможности).
Спасибо большое за помощь.
Я если честно ожидал более простого решения где то я находил на просторах интернета пример на winapi, прием данных сделан просто через Read() и обработку возникающих ошибок порта.
Но раз его нет, буду разбираться с вашим примером.