C#上位机开发(二)—— 使用SerialPort实现串口粘包拆包

通过把SerialPort进行封装,以多线程和缓存的方式处理串口的发送和接收动作。

一、现象

    不管如何设置ReceivedBytesThreshold的值,DataReceived接收到的数据都是比较混乱,不是一个完整的应答数据。

二、原因

1、上位机下发的命令比较密集,以200ms周期发送实时状态轮询命令。

2、在状态实时轮询命令中间有操作命令插入。

2、不同的命令,接收的应答格式也不同。

三、分析

    不同的命令有不同的应答数据,但是不同的应答数据中都具有唯一的结束符,可以根据结束符来作为多个应答数据的分割标志。因此可以把应答数据进行缓存,然后另起一个线程对缓存的应答数据进行分析处理。

因此系统具有:

1、命令队列用来插入操作命令,空闲时处理状态实时轮询命令。

2、命令发送线程,以200ms周期性的发送队列中的命令。

3、应答集合,用来缓存DataReceived接收数据。

4、应答处理线程,对应答集合中的数据进行集中处理。

using System;
using System.Collections.Generic;
using System.Threading;
using System.IO.Ports;
using System.Text.RegularExpressions;

namespace TestDemoC {

    class Program {
        static void Main(string[] args) {

            Open232();

            while (true) {

                var r = send(("AA 00 02 25 26 03 BB").ToBytes(), 1000);
                if (r != 0) { Log("Recv " + "err r." + r); continue; }

                Log("Recv " + rs.Result.Remove().ToHexStr());
            }
            Console.ReadLine();
        }

        private static void Log(string msg) {

            Console.Write($"{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff} [Info] { msg}{System.Environment.NewLine}");
        }

        #region RS232

        private static RS232 rs = new RS232();
        private static long send_expire = 0;

        private static int Open232() {

            var r = rs.Open("com1", 9600);
            if (r != 0) { return -1; }

            rs.MaxLength = 1024;

            return 0;
        }

        private static int send(byte[] buf, int ti = 3000) {

            var r = rs.Send(buf);

            send_expire = DateTime.Now.Ticks + ti * 10000;
            var findhead = -1;
            var len = 5; //Recv 返回指令最短长度(注:考虑无数据的情况)
            RETRY:
            for (; DateTime.Now.Ticks < send_expire; Thread.Sleep(20)) if (rs.Length >= len) break;
            if (rs.Length < len) {
                rs.Clear(rs.Length);
                return 404;
            }

            //起始符
            if (rs.Result[0] != 0x5A || rs.Result[1] != 0x3C) {
                findhead = rs.Result.Find(new byte[] { buf[0], buf[1] });
                rs.Clear(findhead == -1 ? rs.Length : findhead);
                goto RETRY;
            }

            if (rs.Length <= 0) return 404;

            len = len + rs.Result[2];

            for (; DateTime.Now.Ticks < send_expire; Thread.Sleep(20)) if (rs.Length >= len) break;

            //长度err
            if (rs.Length < len) {
                return 2;
            }

            //结束符
            if (rs.Result[len - 1] != 0xDB) {
                return 3;
            }

            return 0;
        }

        #endregion
    }

    public static class Expand {

        public static bool IsEmpty(this string s) { return string.IsNullOrWhiteSpace(s); }

        public static byte[] Remove(this byte[] b, byte cut = 0x00) {
            var list = new List<byte>();
            list.AddRange(b);
            for (var i = list.Count - 1; i >= 0; i--) {
                if (list[i] == cut) { list.RemoveAt(i); } else { break; }
            }
            var lastbyte = new byte[list.Count];
            for (var i = 0; i < list.Count; i++) {
                lastbyte[i] = list[i];
            }
            return lastbyte;
        }

        public static string ToHexStr(this byte[] b) { return BitConverter.ToString(b, 0, b.Length).Replace("-", " "); }

        public static byte[] ToBytes(this string s) {
            s = Replace(s, @"[^\dA-Fa-f]+", "");
            if (s.Length % 2 > 0) s = "0" + s;
            var max = s.Length / 2;
            var buf = new byte[max];
            for (var i = 0; i < max; i++) buf[i] = Convert.ToByte(s.Substring(i * 2, 2), 16);
            return buf;
        }

        public static string Replace(this string s, string p, string r) { return Regex.Replace(s, p, r); }

        public static string ToStr(this object o) { return string.Concat(o); }

    }

    public class RS232 : IDisposable {

        #region base

        private SerialPort p;

        public bool SendBeforeClear { get; set; } = true;

        public byte[] ResultHeader { get; set; }

        public string msg { get; private set; }

        /// <summary>
        /// 接收缓冲区
        /// </summary>
        public byte[] Result { get; private set; }
        /// <summary>
        /// 接收缓冲区字节Max
        /// </summary>
        public int MaxLength {
            get { return max; }
            set {
                Result = new byte[value];
                max = value;
            }
        }

        public int Length => i;

        private int i = 0;
        private int max = 255;
        private bool islife = false;

        #endregion

        #region RS232

        public RS232() { }

        public RS232(string port, int baud)
            : this() {
            if (port.ToUpper().StartsWith("COM")) Open(port, baud);
        }

        #endregion

        #region Open

        /// <summary>
        /// Open
        /// </summary>
        /// <param name="port">Port Name</param>
        /// <param name="baud">Baud Rate</param>
        /// <param name="dbit">Data Bits(def 8)</param>
        /// <param name="sbit">Stop Bits 0 None, 1 One(def), 2 Two, 3 OnePointFive</param>
        /// <param name="parity">-1 None(def), 0 Space, 1 Odd, 2 Even, 3 Mark</param>
        /// <returns></returns>
        public int Open(string port, int baud, int dbit = 8, int sbit = 1, int parity = -1) {
            if (port.ToLower().StartsWith("com")) return open232(port, baud, dbit, sbit, parity);
            return 0;
        }

        private int open232(string port, int baud, int dbit = 8, int sbit = 1, int parity = -1) {
            int r = 0;
            msg = "";
            if (null != p) {
                if (p.IsOpen) p.Close();
                p.Dispose();
            }
            p = null;
            p = new SerialPort();
            p.PortName = port;
            p.BaudRate = baud;
            p.DataBits = 8;
            p.Handshake = Handshake.None;
            p.RtsEnable = true;启用请求发送信号
            p.DtrEnable = true;启用控制终端就续信号
            switch (sbit) {
                case 0: p.StopBits = StopBits.None; break;
                case 1: p.StopBits = StopBits.One; break;
                case 2: p.StopBits = StopBits.Two; break;
                default: p.StopBits = StopBits.OnePointFive; break;
            }
            switch (parity) {
                case -1: p.Parity = Parity.None; break;
                case 0: p.Parity = Parity.Space; break;
                case 1: p.Parity = Parity.Odd; break;
                case 2: p.Parity = Parity.Even; break;
                default: p.Parity = Parity.Mark; break;
            }
            try {
                p.Open();
            } catch (Exception e) {
                r = -500;
                msg = e.Message;
            }
            if (r < 0) {
                if (null != p) {
                    if (p.IsOpen) p.Close();
                    p.Dispose();
                }
                p = null;
                return r;
            }
            islife = true;
            p.DataReceived += receive;
            Result = new byte[max];
            i = 0;
            return r;
        }

        #endregion

        #region Send

        public int Send(byte[] buf) {
            if (p != null) return Send232(buf);
            throw new InvalidOperationException("can't send");
        }

        private int Send232(byte[] buf) {
            int r = 1;
            if (!islife) return 0;
            if (SendBeforeClear) {
                p.DiscardInBuffer(); p.DiscardInBuffer(); p.DiscardInBuffer();
                p.DiscardOutBuffer(); p.DiscardOutBuffer(); p.DiscardOutBuffer();
                i = 0;
                Result.Clear();
            }
            try {
                p.Write(buf, 0, buf.Length);
            } catch (Exception e) {
                r = 2;
                msg = e.Message;
            }
            return r;
        }

        #endregion

        #region receive

        private static object lck_result = new object();

        private void receive(object sender, SerialDataReceivedEventArgs e) {
            Monitor.Enter(lck_result);
            if (!islife) return;
            int len = 0;
            try {
                len = p.BytesToRead;
            } catch (Exception ex) {
                //Log("[BytesToRead] " + ex.Message);
                islife = false;
            }
            if (!islife) return;
            var buf = new byte[len];
            try {
                p.Read(buf, 0, len);
            } catch (Exception ex) {
                //Log("[Read] " + ex.Message);
            }
            if (max >= (i + len)) {
                Result.SetData(buf, i);
                i += len;
            } else {
                Result.SetData(buf, i, max - i);
                i = max;
            }

            Monitor.Exit(lck_result);
        }

        #endregion

        #region Dispose

        public int Close() {
            int r = 0;
            msg = "";
            try {
                if (p?.IsOpen == true) p.Close();

            } catch (Exception e) {
                r = -500;
                msg = e.Message;
            }
            islife = false;
            return r;
        }

        public void Dispose() {
            if (null != p) p.DataReceived -= receive;
            Close();
            try {
                p?.Dispose();
            } catch { }
            p = null;
        }

        #endregion

        #region LRC、CRC16

        public void LRC(ref byte[] buf, int pos = 0, int len = -1, int vfypos = -1) {
            int n = 0;
            if (len < 0) len = buf.Length;
            for (int i = pos; i < len; i++) n ^= buf[i];
            if (vfypos < 0) buf[buf.Length + vfypos] = (byte)n;
            else buf[vfypos] = (byte)n;
        }

        public int CRC16_POLYNOMIAL = 0x8408;
        public int CRC16_DEFAULT = 0x0000;
        public int? CRC16_CHECK = null;
        public int CRC16_FOOTER { get { return CRC16_CHECK ?? 0; } set { CRC16_CHECK = value; } }

        public bool CRC16(ref byte[] buf, int vfypos, int pos = 0, int len = -1, bool vfyreve = false) {
            if (buf.Length < pos) return false;
            if (len > 0 && buf.Length < (pos + len)) return false;
            int crc = crc16(buf, pos, len);

            if (vfyreve) {
                buf[vfypos + 1] = (byte)(crc & 0xFF);
                buf[vfypos] = (byte)(crc >> 8);
            } else {
                buf[vfypos] = (byte)(crc & 0xFF);
                buf[vfypos + 1] = (byte)(crc >> 8);
            }

            return true;
        }
        public int CRC16(byte[] buf, int pos = 0, int count = -1) {
            if (buf.Length < pos) return 0;
            if (count > 0 && buf.Length < (pos + count)) return 0;
            return crc16(buf, pos, count);
        }
        private int crc16(byte[] buf, int pos, int len) {
            int crc = CRC16_DEFAULT; // PRESET_VALUE
            int i = 0, j = 0;
            if (len <= 0) len = buf.Length - pos;
            byte b = 0;
            for (i = pos; i < len + pos; i++) {
                crc ^= buf[i];
                for (j = 0; j < 8; j++) {
                    b = (byte)(crc & 0x01);
                    crc >>= 1;
                    if (b == 0x01) crc ^= CRC16_POLYNOMIAL;
                }
            }
            crc = ~crc;
            if (CRC16_CHECK.HasValue) crc ^= CRC16_CHECK.Value;
            return crc;
        }

        public bool CRC16AD(ref byte[] buf, int vfypos, int pos = 0, int len = -1, bool vfyreve = false) {
            if (buf.Length < pos) return false;
            if (len > 0 && buf.Length < (pos + len)) return false;
            int crc = crc16_andea(buf, pos, len);

            if (vfyreve) {
                buf[vfypos + 1] = (byte)(crc & 0xFF);
                buf[vfypos] = (byte)(crc >> 8);
            } else {
                buf[vfypos] = (byte)(crc & 0xFF);
                buf[vfypos + 1] = (byte)(crc >> 8);
            }

            return true;
        }
        private int crc16_andea(byte[] buf, int pos, int len) {
            int crc = CRC16_DEFAULT; // PRESET_VALUE
            int i = 0, j = 0;
            if (len <= 0) len = buf.Length - pos;
            byte b = 0;
            for (i = pos; i < len + pos; i++) {
                crc ^= buf[i];
                crc = crc & CRC16_DEFAULT;
                for (j = 0; j < 8; j++) {
                    b = (byte)(crc & 1);
                    crc >>= 1;
                    if (b == 0x01) {
                        crc ^= CRC16_POLYNOMIAL;
                        crc = crc & CRC16_DEFAULT;
                    }
                }
            }
            return crc;
        }
        public bool CRC16Verify(byte[] buf, byte[] vfy, int pos = 0, int len = -1) {
            if (buf.Length < pos) return false;
            if (len > 0 && buf.Length < (pos + len)) return false;
            if (vfy.Length < 2) return false;
            int crc = crc16(buf, pos, len);
            if (vfy[0] != (byte)(crc & 0xFF) || vfy[1] != (byte)(crc >> 8)) return false;
            return true;
        }

        #endregion

        public string[] Prots() {
            var arr = SerialPort.GetPortNames();//获取当前计算机的串行端口名称数组。
            Array.Sort(arr);
            return arr;
        }

        public void Clear(int len) {
            if (len <= 0) return;
            if (len > Length) len = Length;
            lock (lck_result) {
                Result = Result.Remove(0, len);
                i -= len;
            }
        }

    }

    public static class Rs232Exand {

        /// <summary>
        /// 组包
        /// </summary>
        /// <param name="me"></param>
        /// <param name="buf"></param>
        /// <param name="idx"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        public static byte[] SetData(this byte[] me, byte[] buf, int idx = 0, int len = -1) {
            if (len < 0) len = buf.Length;
            if ((idx + len) > me.Length) len -= ((idx + len) - me.Length);
            Array.Copy(buf, 0, me, idx, len);
            return me;
        }
        public static byte[] GetData(this byte[] me, int idx = 0, int len = -1) {
            if (len < 0) len = me.Length - idx;
            if ((idx + len) > me.Length) len -= ((idx + len) - me.Length);
            var buf = new byte[len];
            Array.Copy(me, idx, buf, 0, len);
            return buf;
        }

        public static byte[] Clear(this byte[] buf) {
            for (int i = 0; i < buf.Length; i++) buf[i] = 0;
            return buf;
        }

        public static byte[] Remove(this byte[] data, int start, int len) {
            byte[] result = new byte[data.Length];
            if (start == 0) Array.Copy(data, len, result, 0, data.Length - len);
            if (start > 0) {
                Array.Copy(data, 0, result, 0, start);
                Array.Copy(data, start + len, result, start, data.Length - (start + len));
            }
            return result;
        }

        public static int Find(this byte[] data, byte b, int start = 0, int len = -1) {
            int i = 0;
            int ok_start = -1;
            if (len > 0) len = Math.Min(data.Length, len);
            else len = data.Length;
            for (i = start; i < len; i++) {
                if (data[i] == b) {
                    ok_start = i;
                    break;
                }
            }
            return ok_start;
        }

        public static int Find(this byte[] data, byte[] buf, int start = 0, int len = -1) {
            int i = 0;
            int buf_idx = 0;
            int ok_start = -1;
            if (len > 0) len = Math.Min(data.Length, len);
            else len = data.Length;
            for (i = start; i < len; i++) {
                if (data[i] == buf[buf_idx]) {
                    if (ok_start == -1) ok_start = i;
                    buf_idx++;
                    if (buf_idx >= buf.Length) break;
                } else {
                    buf_idx = 0;
                    ok_start = -1;
                }
            }
            if (buf_idx < buf.Length) ok_start = -1;
            return ok_start;
        }

    }
}

 

  • 2
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值