Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/68: Рейтинг темы: голосов - 68, средняя оценка - 5.00
11 / 1 / 0
Регистрация: 08.07.2013
Сообщений: 2

C# SerialPort Неполный прием сообщений

08.07.2013, 21:43. Показов 13732. Ответов 7
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день. Долго искал решение своей проблемы, но поиски оказались тщетны.
Задача: организовать обмен сообщениями между железом и компом, посредством com порта на С# при использовании определенного протокола.
Железо основано на PIC24, ПО работает как часы.
Проблема: примерно в 5% случаях моя программа считывает не всё пришедшее сообщение, а только часть.
Вот пример, лог программы Portmon
Code
1
2
3
4
5
6
7
8
9
1072    0.00000419    Com_1.exe    IOCTL_SERIAL_GET_COMMSTATUS    VCP0    SUCCESS        
1073    0.00001145    Com_1.exe    IRP_MJ_READ                    VCP0    SUCCESS    Length 10: FF FF 2E A1 B1 03 44 35 01 09     
1074    0.91130577    Com_1.exe    IOCTL_SERIAL_WAIT_ON_MASK      VCP0    SUCCESS                
1079    0.00000447    Com_1.exe    IOCTL_SERIAL_GET_COMMSTATUS    VCP0    SUCCESS        
1080    0.00001117    Com_1.exe    IRP_MJ_READ                    VCP0    SUCCESS    Length 3: FF FF 2E     
1081    0.01545727    Com_1.exe    IOCTL_SERIAL_WAIT_ON_MASK      VCP0    SUCCESS        
1082    16.02207878   Com_1.exe    IOCTL_SERIAL_WAIT_ON_MASK      VCP0    SUCCESS            
1084    0.00000419    Com_1.exe    IOCTL_SERIAL_GET_COMMSTATUS    VCP0    SUCCESS        
1085    0.00001090    Com_1.exe    IRP_MJ_READ                    VCP0    SUCCESS    Length 7: A1 B1 03 44 36 01 C3
Первое сообщение принято сразу полностью. И я в программе его нормально обработал. Второе принято двумя кусками, соответственно я этот пакет просто отбросил как не соответствующий протоколу.

Далее код. Привожу не весь чтобы не загромождать сообщение.

Открытие порта.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public SerialPort sp = new SerialPort();
public bool Open(string portName, int baudRate, int databits, Parity parity, StopBits stopBits)
        {
            if (!sp.IsOpen)
            {
                sp.PortName = portName;
                sp.BaudRate = baudRate;
                sp.DataBits = databits;
                sp.Parity = parity;
                sp.StopBits = stopBits;
                
                sp.DtrEnable = false;
                sp.RtsEnable = false;
                sp.ReadBufferSize = 4096;
                sp.WriteBufferSize = 4096;
                
                try
                {
                    sp.Open();
                }
                catch (Exception err)
                {
                    comptStatus = "Error opening " + portName + ": " + err.Message;
                    return false;
                }
                Status = portName + " opened successfully";
                sp.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived);
 
                return true;
            }
            else
            {
                Status = portName + " already opened";
                return false;
            }
        }
Функция вызываемая по событию прихода данных в порт

C#
1
2
3
4
5
6
void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // способ 1, прием всего пакета по sp.BytesToRead;
            byte[] bufer = recieve_data();
            // и дальше  обработка....
        }
Собственно функция приема сообщения из порта
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 байт друг за другом. и в последних двух считываниях и возникает та же проблема не полного прочтения пакета.
Следующей попыткой был "циклический буфер". не знаю на сколько я корректно его реализовал:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 из потока, главная функция потока

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        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% случая оно читается не все сразу.

Подскажите пожалуйста как решить данную проблему.

PS. в С++ такая же в точности проблема решается отловом ошибок (хотя самих ошибок в логах Portmon нет, ни одной, все глаза сломал. не было) при помощи фишек WinAPI. при каждом чтении из порта идет проверка ошибок аля:
C++
1
2
3
4
5
6
7
8
DWORD dwError = GetLastError();
// и 20-30 If-ов типа таких 
if (dwError & CE_FRAME)      {
        PurgeComm(COMPort, PURGE_RXABORT| PURGE_RXCLEAR);
     }
if (dwError & CE_IOE)       {
        PurgeComm(COMPort, PURGE_RXABORT| PURGE_RXCLEAR);
     }
Но мне не нужно пихать WinAPI в С#, считаю это странным.
1
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
08.07.2013, 21:43
Ответы с готовыми решениями:

Асинхронные сокеты: Как организовать разделение на прием сообщений и прием файлов
Изучив синхронные сокеты, перешел к изучению асинхронных. Столкнулся вот с чем, как, используя асинхронные сокеты, сделать на стороне...

Прием данных через SerialPort c#
Мне на com port приходит строчка данных неопределенного размера каждые x микроскек. Мне надо считать эту строчку и записать в массив....

Прием и обработка данных из SerialPort
Здравствуйте! Подскажите как можно реализовать вот такую задачу: С МК по SerialPort приходят данные в виде адрес/значение(Привер:...

7
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
10.07.2013, 16:43
Здравствуйте.
Выбросьте свойство sp.BytesToRead — оно показывает количество уже пришедших на порт байтов. СОМ-порт медленный, а комп у вас — быстрый, через это данные будут считываться быстрее, чем они успевают приходить, в результате значение этого свойства будет очень часто равно нулю или размеру, меньшему, чем размер сообщения.

Правильный подход вы выбрали в первой попытке: считывайте шапку для того, чтобы узнать длину сообщения, а потом считывайте нужное количество байтов примерно таким образом:
C#
1
2
3
4
5
6
7
int ReadMessage(byte[] buffer)
{
   int length = ReadPacketLength(); // Считывание длины сообщения согласно протокола
   int offset = 0;
   while (offset < length) offset += sp.Read(buffer, offset, length - offset);
   return offset;
}
2
11 / 1 / 0
Регистрация: 08.07.2013
Сообщений: 2
11.07.2013, 21:56  [ТС]
Спасибо большое, принимает теперь всегда.
Но, если сообщение пришло не полностью, цикл крутится до тех пор пока не придет следующее, не будет ли это программу вешать в дальнейшем?
0
 Аватар для Spectral-Owl
608 / 583 / 157
Регистрация: 29.06.2010
Сообщений: 1,620
12.07.2013, 14:04
как то мне ближе побайтовое чтение данных с порта, вынесенное в отдельный поток:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//---метод чтения данных с ком-порта---
        protected void Read()
        {
            //пока флаг чтения истинный
            while (boolRead)
            {
                try
                {
                    //побайтовое чтение того, что порт посылает
                    sBuffer.Add((byte)spPort.BaseStream.ReadByte());
 
                    //если байт не осталось..
                    if (spPort.BytesToRead == 0)
                        //и если буффер не пуст..
                        if (sBuffer.Count != 0)
                            //вызов метода получения каких-то данных
                            Readed();
                }
                //если кончилось время ожидания..
                catch (TimeoutException)
                {
                    //вызов соответствующего метода
                    Timed();
                }
                catch
                { }
            }
        }
метод потоковый, работает постоянно. если данные на порт не идут программа ждёт их в точке (byte)spPort.BaseStream.ReadByte(), что не даёт методу "завесить" приложение. как только данные появляются на порту всё шустренько обрабатывается. после того как порт сказал что байт нет - вызов метода Readed();, в котором проверка CRC и длинны пакета. если совпало - вызвать событие принятия данных, обнулить буффер. если не совпало - ждём дальше, пока не словим корректный пакет, или пока не сгенерируется исключение по тайм-ауту.
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
13.07.2013, 00:08
Цитата Сообщение от ethenhunt Посмотреть сообщение
Но, если сообщение пришло не полностью, цикл крутится до тех пор пока не придет следующее, не будет ли это программу вешать в дальнейшем?
Цикл крутиться до тех пор, пока не считает количество байт, полученное из метода ReadPacketLength. В вашем случае это длина сообщения по протоколу. Если отошел шнур от порта или девайс "переклинило" и он отправил меньше байт, чем указал в шапке, то да, поток будет висеть до тех пор, пока либо девайс не отправит остаток сообщения, либо пока не отправит новое сообщение. Во втором случае данные будут входить кривые, но это уже не зависит от выбранного алгоритма считывания.
Чтобы это избежать, у вас в свойствах порта должен быть установлен таймаут (практически все протоколы, основанные на работе через COM-порт, регламентируют таймаут на чтение контроллером ответа — посмотрите доки, он там должен быть). Таймаут устанавливается через свойство ReadTimeout на чтение и WriteTimeout на отправку.
Обычно метод, считывающий целиком сообщение, оборачивается в try/catch TimeoutException. В обработчике исключения очищается буфер чтения и процесс считывания начинается заново.

Цитата Сообщение от Spectral-Owl Посмотреть сообщение
//если байт не осталось..
if (spPort.BytesToRead == 0)
Проблема с использованием этого свойства описана выше: если данные считываются быстрее, чем успевают поступать на порт, значение этого свойства будет равно нулю, хотя половина сообщения еще в пути.
Результат — не полностью считанное сообщение
2
 Аватар для Spectral-Owl
608 / 583 / 157
Регистрация: 29.06.2010
Сообщений: 1,620
15.07.2013, 09:52
Цитата Сообщение от kolorotur Посмотреть сообщение
если данные считываются быстрее, чем успевают поступать на порт, значение этого свойства будет равно нулю
боюсь не согласиться: при моём коде значение этого свойства будет равно нулю в основном при прочтении последнего бита сообщения от порта, в остальное время метод или ждёт пока на порт придёт байт
C#
1
spPort.BaseStream.ReadByte();
, или обрабатывает исключение по тайм-ауту.

проверка
C#
1
if (spPort.BytesToRead == 0)
будет производится только после того как на порт пришли данные (а они приходят не по одному байту, а штук по 4-8 сразу (по крайней мере у меня так)), и считался один из пришедших, но не прочитанных байтов. После прочтения число BytesToRead соответственно уменьшается, и так пока не прочитаются все байты в куске сообщения.

После прочтения последнего вызов метода
C#
1
Readed();
, внутри которого проверка CRC и предпологаемой длинны пакета, и если что-то не совпадает то просто делается вид что не произошло ничего, поток опять останавливается в месте
C#
1
spPort.BaseStream.ReadByte()
и следующий раз проверяет свойство BytesToRead только после прочтения очередного байта (а он один не приходит).

в общем как то так)
0
Эксперт .NET
 Аватар для kolorotur
17823 / 12973 / 3382
Регистрация: 17.09.2011
Сообщений: 21,261
15.07.2013, 11:46
Цитата Сообщение от Spectral-Owl Посмотреть сообщение
при моём коде значение этого свойства будет равно нулю в основном при прочтении последнего бита сообщения от порта, в остальное время метод или ждёт пока на порт придёт байт [...]
проверка будет производится только после того как на порт пришли данные (а они приходят не по одному байту, а штук по 4-8 сразу
Тут дело не столько в коде, сколько в настройках скорости порта (Baud Rate) и скорости, с которой ваш код считывает эти байты.
Если скорость порта маленькая (9600, например), а код считывания быстрый, то проблема будет очень явно выражена.
То есть суть в том, что метод Readed будет вызван, когда считалась еще только часть сообщения.
Если в методе Readed это учитывается, то все норм, но внимание на этом заострить все-таки нужно.

Ну вот простой пример:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static SerialPort sp;
static List<byte> sBuffer = new List<byte>();
static void Main(string[] args)
{
    sp = new SerialPort
    {
        PortName = "COM1",
        BaudRate = 9600,
        DataBits = 8,
        Parity = Parity.Odd,
        StopBits = StopBits.One,
        ReceivedBytesThreshold = 1
    };
    sp.Open();
    Read();
}
 
static void Read()
{
    bool boolRead = true;
    while (boolRead)
    {
        try
        {
            sBuffer.Add((byte)sp.BaseStream.ReadByte());
            if (sp.BytesToRead == 0)
                if (sBuffer.Count != 0)
                    Readed();
        }
        catch (TimeoutException)
        {
            Console.WriteLine("Timeout");
        }
    }
}
 
private static void Readed()
{
    Console.WriteLine(BitConverter.ToString(sBuffer.ToArray()));
    sBuffer.Clear();
}
Засылаю на него сообщение из 20 байт, каждая строчка — это вызов Readed:
Первая отправка:
01-02-03-04-05-06-07-08-09-10-11-12-13-14-15-16
17-18-19-20
Вторая:
01-02-03-04-05-06-07-08
09-10-11-12-13-14-15-16
17-18-19-20
И так далее.
0
 Аватар для MasMaX
8 / 8 / 2
Регистрация: 07.02.2012
Сообщений: 71
06.09.2013, 14:31
Помогите решить аналогичную проблему.

В линию постоянно пишется IOCTL_SERIAL_WAIT_ON_MASK и IOCTL_SERIAL_GET_COMMSTATUS разрывая тем самым пакеты.

Дело в том, что старая программа, написанная в C++ Builder 6 таких служебных сообщений не делала. А тут понадобилось переписать код на c# и понеслось. Все пакеты рвутся. Читать по-байтно пробовал, это работает. Но из-за этих служебных пакетов очень засоряется и тормозится линия. Да и принимать пакет проще разом, чем кусами. Т.е. мне кажется тут дело 100% в настройках ком-порта в программе.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
06.09.2013, 14:31
Помогаю со студенческими работами здесь

Приём данных через компонент serialport
Доброго времени суток, подскажите. каким образом осуществляется чтение данных с ком порта посредством данного компонента? У serialport есть...

Организовать приём байтов из буфера последовательного порта SerialPort
Всем здравствуйте. Подскажите, пожалуйста, кто работал с последовательным портом, как правильно организовать приём байтов из буфера...

Отправка и прием сообщений
Здравствуйте! Мне нужно написать приложение, которое позволяло бы общаться его пользователям. При запуске должно проверяться установлено...

Прием почтовых сообщений
Нашел такой код сделал тестовый почтовый ящик и решил попробовать: procedure TForm1.Button2Click(Sender: TObject); begin ...

Прием сообщений IdIRC
Здравствуйте. Как сделать прием сообщений? IdIRC. Коннект идет по кнопке (idIRC1.Nick:=':Nick'; idIRC1.Password:=rdtd; ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru