免费国产网站_秋霞午夜一区二区三区视频_99热在线看_日韩精品久久一区二区_午夜看一级毛片_天天鲁在视频在线观看

  • 您的位置:首頁 > 新聞動態 > 技術文章

    C# 串口二進制協議數據解析范例

    2018/12/26??????點擊:
    我們先說一下通訊協議。通訊協議就是通訊雙方共同遵循的一套規則,定義協議的原則是盡可能的簡單以提高傳輸率,盡可能的具有安全性保證數據傳輸完整正確?;谶@2點規則,我們一個通訊協議應該是這樣的:頭+數據長度+數據正文+校驗
        例如:AA 44 05 01 02 03 04 05 EA
        這里我假設的一條數據,協議如下:
        數據頭:     AA 44
        數據長度: 05
        數據正文: 01 02 03 04 05
        校驗:       EA
        一般數據的校驗,都會采用常用的方式,CRC16,CRC32,Xor。
        有的數據安全要求高的,不允許丟包的,可能還要加入重發機制或是加入數據恢復算法,在校驗后根據前面數據添加恢復字節流以恢復數據。我這里采用的是簡單的異或校驗,包含數據頭的所有字節,依次異或得到的。
        協議很簡單,我也認為分析協議是很簡單的事情,下面我們就如何分析協議來實際的結合c#看一下。
        在我們實際開始編碼之前,還有一個規則需要了解,我們有了通訊協議,如何結合串口的協議來分析,需要關心什么呢?哦。一般就是4個問題:緩存收到的所有數據,找到一條完整數據,分析數據,界面通知。
        如果分的更詳細一點,緩存收到的所有數據,我們想到高效的辦法就是順序表,也就是數組,但數組的操作比較復雜,當你使用完一條數據后,用過的需要移除;新數據如果過多的時候,緩存過大需要清理;數據搬移等等,很有可能一個不小心就會丟數據導致軟件出些莫名其妙的小問題。個人建議,使用List,內部是數組方式實現,每次數據不足夠的時候會擴容1倍,數據的增刪改都已經做的很完善了。不會出現什么小問題。
        找到一條完整數據,如何找到完整數據呢?就我們例子的這個協議,首先在緩存的數據中找AA 44,當我們找到后,探測后面的字節,發現是05,然后看緩存剩下的數據是否足夠,不足夠就不用判斷,減少時間消耗,如果剩余數據>=6個(包含1個字節的校驗),我們就算一個校驗,看和*后的校驗是否一致。
        分析數據:鑒于網絡的開放性,我無法確定讀者對c#的了解程度,介紹一下,常用的方式就是BitConvert.ToInt32這一系列的方法,把連續的字節(和變量長度一樣)讀取并轉換為對應的變量。c++下使用memcpy,或直接類型轉換后進行值拷貝,vb6下使用CopyMemory這個api。
        校驗:前面說過了。完整性判斷的時候需要和校驗對比,大多系統都不太嚴格,不支持重發,所以數據錯誤就直接丟棄。導致數據錯誤的原因很多,比如電磁干擾導致數據不完整或錯誤、硬件驅動效率不夠導致數據丟失、我們的軟件緩存出錯等。這些軟件因素數據系統錯誤,需要修改,但是電磁干擾么,有這個可能的。雖然很少。
       下面是支持協議分析(協議不能配置,可配置的協議不是我們討論的范疇??梢钥纯从蠨FA(確定性有限狀態機))
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.IO.Ports;
    using System.Text.RegularExpressions;
    namespace SerialportSample
    {
        public partial class SerialportSampleForm : Form
        {
            private SerialPort comm = new SerialPort();
            private StringBuilder builder = new StringBuilder();//避免在事件處理方法中反復的創建,定義到外面。
            private long received_count = 0;//接收計數
            private long send_count = 0;//發送計數
            private bool Listening = false;//是否沒有執行完invoke相關操作
            private bool Closing = false;//是否正在關閉串口,執行Application.DoEvents,并阻止再次invoke
            private Listbuffer = new List(4096);//默認分配1頁內存,并始終限制不允許超過
            private byte[] binary_data_1 = new byte[9];//AA 44 05 01 02 03 04 05 EA
            public SerialportSampleForm()
            {
                InitializeComponent();
            }
            //窗體初始化
            private void Form1_Load(object sender, EventArgs e)
            {
                //初始化下拉串口名稱列表框
                string[] ports = SerialPort.GetPortNames();
                Array.Sort(ports);
                comboPortName.Items.AddRange(ports);
                comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;
                comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("19200");
                //初始化SerialPort對象
                comm.NewLine = "/r/n";
                comm.RtsEnable = true;//根據實際情況吧。
                //添加事件注冊
                comm.DataReceived += comm_DataReceived;
            }
            void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                if (Closing) return;//如果正在關閉,忽略操作,直接返回,盡快的完成串口監聽線程的一次循環
                try
                {
                    Listening = true;//設置標記,說明我已經開始處理數據,一會兒要使用系統UI的。
                    int n = comm.BytesToRead;//先記錄下來,避免某種原因,人為的原因,操作幾次之間時間長,緩存不一致
                    byte[] buf = new byte[n];//聲明一個臨時數組存儲當前來的串口數據
                    received_count += n;//增加接收計數
                    comm.Read(buf, 0, n);//讀取緩沖數據
                    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
                    //<協議解析>
                    bool data_1_catched = false;//緩存記錄數據是否捕獲到
                    //1.緩存數據
                    buffer.AddRange(buf);
                    //2.完整性判斷
                    while (buffer.Count >= 4)//至少要包含頭(2字節)+長度(1字節)+校驗(1字節)
                    {
                        //請不要擔心使用>=,因為>=已經和>,<,=一樣,是獨立操作符,并不是解析成>和=2個符號
                        //2.1 查找數據頭
                        if (buffer[0] == 0xAA && buffer[1] == 0x44)
                        {
                            //2.2 探測緩存數據是否有一條數據的字節,如果不夠,就不用費勁的做其他驗證了
                            //前面已經限定了剩余長度>=4,那我們這里一定能訪問到buffer[2]這個長度
                            int len = buffer[2];//數據長度
                            //數據完整判斷第一步,長度是否足夠
                            //len是數據段長度,4個字節是while行注釋的3部分長度
                            if (buffer.Count < len + 4) break;//數據不夠的時候什么都不做
                            //這里確保數據長度足夠,數據頭標志找到,我們開始計算校驗
                            //2.3 校驗數據,確認數據正確
                            //異或校驗,逐個字節異或得到校驗碼
                            byte checksum = 0;
                            for (int i = 0; i < len + 3; i++)//len+3表示校驗之前的位置
                            {
                                checksum ^= buffer[i];
                            }
                            if (checksum != buffer[len + 3]) //如果數據校驗失敗,丟棄這一包數據
                            {
                                buffer.RemoveRange(0, len + 4);//從緩存中刪除錯誤數據
                                continue;//繼續下一次循環
                            }
                            //至此,已經被找到了一條完整數據。我們將數據直接分析,或是緩存起來一起分析
                            //我們這里采用的辦法是緩存一次,好處就是如果你某種原因,數據堆積在緩存buffer中
                            //已經很多了,那你需要循環的找到*后一組,只分析*新數據,過往數據你已經處理不及時
                            //了,就不要浪費更多時間了,這也是考慮到系統負載能夠降低。
                            buffer.CopyTo(0, binary_data_1, 0, len + 4);//復制一條完整數據到具體的數據緩存
                            data_1_catched = true;
                            buffer.RemoveRange(0, len + 4);//正確分析一條數據,從緩存中移除數據。
                        }
                        else
                        {
                            //這里是很重要的,如果數據開始不是頭,則刪除數據
                            buffer.RemoveAt(0);
                        }
                    }
                    //分析數據
                    if (data_1_catched)
                    {
                        //我們的數據都是定好格式的,所以當我們找到分析出的數據1,就知道固定位置一定是這些數據,我們只要顯示就可以了
                        string data = binary_data_1[3].ToString("X2") + " " + binary_data_1[4].ToString("X2") + " " +
                            binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +
                            binary_data_1[7].ToString("X2");
                        //更新界面
                        this.Invoke((EventHandler)(delegate { txData.Text = data; }));
                    }
                    //如果需要別的協議,只要擴展這個data_n_catched就可以了。往往我們協議多的情況下,還會包含數據編號,給來的數據進行
                    //編號,協議優化后就是: 頭+編號+長度+數據+校驗
                    //
                    /////////////////////////////////////////////////////////////////////////////////////////////////////////////
                    builder.Clear();//清除字符串構造器的內容
                    //因為要訪問ui資源,所以需要使用invoke方式同步ui。
                    this.Invoke((EventHandler)(delegate
                    {
                        //判斷是否是顯示為16禁止
                        if (checkBoxHexView.Checked)
                        {
                            //依次的拼接出16進制字符串
                            foreach (byte b in buf)
                            {
                                builder.Append(b.ToString("X2") + " ");
                            }
                        }
                        else
                        {
                            //直接按ASCII規則轉換成字符串
                            builder.Append(Encoding.ASCII.GetString(buf));
                        }
                        //追加的形式添加到文本框末端,并滾動到*后。
                        this.txGet.AppendText(builder.ToString());
                        //修改接收計數
                        labelGetCount.Text = "Get:" + received_count.ToString();
                    }));
                }
                finally
                {
                    Listening = false;//我用完了,ui可以關閉串口了。
                }
            }
            private void buttonOpenClose_Click(object sender, EventArgs e)
            {
                //根據當前串口對象,來判斷操作
                if (comm.IsOpen)
                {
                    Closing = true;
                    while (Listening) Application.DoEvents();
                    //打開時點擊,則關閉串口
                    comm.Close();
                }
                else
                {
                    //關閉時點擊,則設置好端口,波特率后打開
                    comm.PortName = comboPortName.Text;
                    comm.BaudRate = int.Parse(comboBaudrate.Text);
                    try
                    {
                        comm.Open();
                    }
                    catch(Exception ex)
                    {
                        //捕獲到異常信息,創建一個新的comm對象,之前的不能用了。
                        comm = new SerialPort();
                        //現實異常信息給客戶。
                        MessageBox.Show(ex.Message);
                    }
                }
                //設置按鈕的狀態
                buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open";
                buttonSend.Enabled = comm.IsOpen;
            }
            //動態的修改獲取文本框是否支持自動換行。
            private void checkBoxNewlineGet_CheckedChanged(object sender, EventArgs e)
            {
                txGet.WordWrap = checkBoxNewlineGet.Checked;
            }
            private void buttonSend_Click(object sender, EventArgs e)
            {
                //定義一個變量,記錄發送了幾個字節
                int n = 0;
                //16進制發送
                if (checkBoxHexSend.Checked)
                {
                    //我們不管規則了。如果寫錯了一些,我們允許的,只用正則得到有效的十六進制數
                    MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");
                    Listbuf = new List();//填充到這個臨時列表中
                    //依次添加到列表中
                    foreach (Match m in mc)
                    {
                        buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));
                    }
                    //轉換列表為數組后發送
                    comm.Write(buf.ToArray(), 0, buf.Count);
                    //記錄發送的字節數
                    n = buf.Count;
                }
                else//ascii編碼直接發送
                {
                    //包含換行符
                    if (checkBoxNewlineSend.Checked)
                    {
                        comm.WriteLine(txSend.Text);
                        n = txSend.Text.Length + 2;
                    }
                    else//不包含換行符
                    {
                        comm.Write(txSend.Text);
                        n = txSend.Text.Length;
                    }
                }
                send_count += n;//累加發送字節數
                labelSendCount.Text = "Send:" + send_count.ToString();//更新界面
            }
            private void buttonReset_Click(object sender, EventArgs e)
            {
                //復位接受和發送的字節數計數器并更新界面。
                send_count = received_count = 0;
                labelGetCount.Text = "Get:0";
                labelSendCount.Text = "Send:0";
            }
        }
    }

    至此,只要按這個協議格式發送數據到軟件打開的串口,就能在數據的data標簽顯示出你的數據內容,我們現在是直接顯示為:

    01 02 03 04 05

    也就是實際的數據段內容。

    我們在回顧一下,一般二進制格式數據就是這樣分析,分析數據長度是否足夠,找到數據頭,數據長度,校驗,然后分析。
    分析方式很多。結合各自實際情況操作,可以使用序列化方式,但是wince不支持,也可以用BitConvert方式將連續的字節讀取為某個類型的變量。
    DataReceived事件中,*高效的做法是指緩存數據,然后異步的去分析數據。但是,這樣較復雜,在效率要求不是很高的情況下(大多數情況),可以在DataReceived事件中緩存數據后,立刻進行數據完整性檢查,有效性檢查,分析數據,以及更新界面。但這么做有一個隱患,底層串口操作的效率依賴于數據分析和界面更新,任何一個環節頻繁耗時過長,都會造成數據的堆積。本文只假設都不拖時間的情況。各位小朋友, 希望本文對您有幫助, 感謝關注WONGLOVE!

    主站蜘蛛池模板: 奇米影视狠888_亚洲国产日韩欧美高清片_国内精品久久久久久中文字幕_亚洲成av人片在线观看无_99精品视频网站_国产制服丝袜亚洲日本在线 | 欧美性a视频_久久黄色a级片_无码人妻aⅴ一区二区三区麻豆_亚洲综合中文字幕在线_免费国产wwwwwww网站_欧美男人操女人视频 | 日本高清二区三区_免费毛片播放_激动网色视频_性福视频在线观看_外卖gayxxxxgay2_亚洲成人免费在线 | 亚洲日韩第一页_美女视频黄免费_av亚洲产国偷v产偷v自拍软件_国产精品一区久久人人爽_亚洲精品日韩精品_日日夜夜精品免费看 | 黄色一级播放_久久91精品国产91久久小草_美女黄网站免费福利视频_欧美亚洲综合网_久久人与动人物a级毛片_欧美视频网站在线观看 | 天天操夜夜操国产精品_国产特级毛片AAAAAA高清_久久久久久久久久一本门道91_欧美片第一页_免费99精品国产人妻自在线_亚洲国产日韩一区三区 | 中文字幕在线观看免费视频_天天躁日日躁狠狠躁一区_女人与公拘交酡全过女免费_日本女人b_中文无码人妻影音先锋_狠狠色噜噜狠狠狠狠黑人 | 91射网站_91性生活视频_6699www免费人成在线观看_国产免费一区二区三区不卡_成人性生交免费看国产_男女www视频 | 922tv视频在线观看_99久久婷婷国产综合精品_欧美大片a级毛片_免费毛片黄色视频_久久99精品热在线观看_国产精品福利91 | 男人女人努力生猴子_精品国产乱码一区二区三区四区_中文字幕在线一_国产精品免费观看久久_国产黄色大片网站_在线免费av片 | 波多野结喷水最猛一部352_片在线免费观看_欧美韩一区二区_国产乱子伦一区二区三区视频播放_免费视频精品_日韩在线观看av | 性视频亚洲_超碰在线中文字幕_久久影院亚洲_亚洲com_精品久久网站_亚洲娇小与黑人巨大交 | 久青草资源视频在线无码_麻豆精品一区_就爱av_性欧美1819sex性高清_国语对白做受69按摩_外出3在线观看 在线四区_自拍偷拍臀av_日韩高清免费看_欧美精品VIDEOFREE1080P_97久久综合一区二区三区_亚洲日韩欧美一区二区三区在线 | 亚洲资源视频_国产情侣真实露脸在线_四色av网站入口_日本一区二区三区日本免费_国产91天堂素人系列在线播放_欧美gv在线 | 黄页网站视频免费大全_2021高清精品国产_久久国产精品99久久久大便_亚洲精品国产剧情久久9191_国产欧美一区二区精品性_激情片一区二区 | 欧美日韩视频在线第一区_中文字幕第2页不卡_久久9999免费视频_久久久久久一级_久久久综_亚洲永久字幕 | 亚洲AV福利天堂一区二区三_免费看中国毛片_久操视频网_手机永久无码国产AV毛片_国产欧美一区二区视频_制服丝袜中文字幕第一页 | 永夜星河在线观看_日韩色性视频_日本成人免费网站_日本在线看_国产a一级毛片爽爽影院无码_久久xxx | 免播放器在线观看av_精品久久999国产免费_国产棈品久久久久久久久久免费看_caoporn视频在线观看_麻豆一区二区在我观看_9九色桋品熟女内射 | 日韩五码在线_羞羞答答入口_97自拍偷拍_99久久精品免费看国产免费粉嫩_中文成人无码精品久久久动漫_精品国产福利一区二区 | 欧美xxx片_免费av网站在线播放_免费看一级黄色大片_成人黄页网站视频_奇米777四色影色在线看_色一情一区二区三区四区 | 欧美性猛交免费看_日韩免费小视频_最新国产亚洲亚洲精品a_三级影院在线观看_免费一级男女裸片_亚洲综合久久一区二区 | 久久久91精品_色久五月_亚洲日韩在线中文字幕综合_哺乳一区二区久久久免费_麻豆午夜_大战丰满人妻性色Av偷偷 | 中文不卡1区2区3区_在线免费观看不卡av_亚洲精品第一国产综合精品99_台湾佬久久_国产老熟女狂叫对白_METART极品人体 | 亚洲欧美在线视频_亚洲自拍首页_无码国产69精品久久久久孕妇_久久99久久99精品免视看动漫_麻豆午夜福利国产高潮偷啪_精品国产91久久久久久浪潮蜜月 四虎视频在线精品免费网址_青草青草久热国产精品_免费在线亚洲_www一片黄_最色www_国产女上位疯狂榨精合集 | 在线观看视频免费区_黑人又大又粗又长进去很舒服_免费无码久久成人网站_黄色成人在线网站_国产精品爆乳在线播放第一人称_尤物99国产成人精品视频 | 久草网视频_黄色视屏在线免费观看_亚洲综合av一区二区_国产拍揄自揄免费观看_天天av天天干_欧美片一区二区 | 久久福利_麻豆免费网站_国产亚洲av片天天在线观看_免费成人国产_免费在线日韩_日本www在线视频 | 久久久久国产精品老师性教育影院_狠狠的日_超碰日韩_公侵犯一区二区三区四区中文字幕_天天爽夜夜骑_q2002日韩午夜伦高清 | 免费看一级一片_黄色二级视频_日本高清无吗v一区_夜夜爽妓女8888888视频_激情亚洲一区二区三区四区_欧洲尺码日本尺码特价 | 91热久久_国产黄色特级片_国产一级视屏_狠狠干b_亚洲高清线_亚洲一区二区女搞男 | 成人久久18免费网站_青青草97国产精品免费观看_最新国产一区最新在线_欧美激情A∨在线视频播放_在线视频免费观看国产_丰满少妇猛烈进入A片99A | 国产网站自拍_国产一区极品_欧美日韩一二三区_免费看片A片人人免费_亚洲韩国日本在线观看p_欧美色一级 日韩四区_亚洲成人色区_日韩欧美在线观看免费_日韩一级片av_量新国产精品亚洲_四虎视频网站 | 在线人成免费视频69国产_亚洲精品一区二区四区_亚洲欧美日韩久久_大地资源第一在线_亚洲乱妇_免费观看视频在线播放 | 久久精品免费播放_国产在线观看禁18_色婷婷色偷偷色天堂_日韩在观看线_日日日操操操_亚洲经典av 久久99视频免费观看_久久久久久久麻豆_国内外精品一区二区三区_成人免费毛片AAAAAA片_亚洲淫区_欧美成人天天综合在线 | 2020VA最新国产在线_免费精品国偷自产在线2020_国产18禁黄网站禁片免费观看_99久草_国产精品va无码免费麻豆_A片在线观看免费视频网站 | 国产精品毛片一区二区三区四区_中文字幕av免费专区久久_成人h视频_国产精品久久久一区二区_国产一区二区影院_舌头伸进去搅动好爽视频 | 亚欧免费无码AⅤ在线观看蜜桃_亚洲国产成人欧美在线观看_黄色大片在线播放_国产精品免费一区二区久久夜色_99久久精品国产一区二区三区_一级视频在线观看免费 | 欧美黑人成人www在线观看_91污版_日韩一区二区三区免费看_中国丰满少妇xxxxx高潮_美日韩中文字幕_免费无码成人片在线观看 | xxx69在线观看_好硬好湿好大再深一点动态图_性中国妓女毛茸茸视频_亚洲精品天堂无码中文字幕_69一区二三区好的精华液_97久久精品无码一区二区欧美人 | 色婷婷狠狠爱_日韩在线aⅴ免费视频_成年美女黄网站色视频免费_欧美日韩视频观看_色偷偷亚洲女人的天堂_极品大长腿啪啪高潮露脸 |