• 对CustomSerialPort类库的改进


    CustomSerialPort 项目地址:flyfire.CustomSerialPort。Github主页上对其介绍为:一个增强的自定义串口类,实现协议无关的数据帧完整接收功能,支持跨平台使用。

    经过查看其源码,发现其核心思想是在SerialPortStream类库的基础上,将128ms(默认)时间内接收的串口数据当作一个完整的数据包,通过事件机制将数据分发出去。

    该类库是对SerialPortStream类库的再封装,实现了跨平台的串口开发,因此具有一定普适性,但是其源码存在一定问题,下面进行概述。

    Gitee项目地址:SerialPortStreamHelper

    1、CustomSerialPort存在的问题

    (1) CustomSerialPort只在串口接收最新的数据128ms之后才将之前累积的缓存发送出去,因此如果在128ms之内有大于一个包数据到来,那么就会产生粘包问题。

    (2) CustomSerialPort在处理数据的时候不是线程安全的,存在接收数据线程和数据处理线程共用数据的情况。

    (3) CustomSerialPort会在一个数据处理周期(128ms)内创建一个线程,导致线程被频繁创建,造成资源浪费。

    2、可能的解决方法

    (1) CustomSerialPort假定每一个数据接收周期(默认128ms)之内收到的数据都是完整数据包(可能有多个),因此不必处理粘包问题。

    (2) 如果在一个接收数据时间周期之内发送多个命令,可能在这个周期内会收到多个完整包,因此需要对收到的多个包进行处理,以保证数据的完整性。

    (3) 将数据处理封装在一个基于信号量的线程处理代码逻辑中,可以解决串口数据线程安全的问题。同时,这样处理也能避免频繁创建线程,节约系统资源。

    3、对CustomSerialPort源码的重构:SerialPortStreamHelper

    (1) 创建对象时只传入是否启用超时机制以及超时参数。

    (2) SerialPortStream的参数只暴露读取属性。

    (3) 封装Open、Close、Dispose、Write等方法,将串口参数在Open方法中体现。

    (4) 创建串口数据帮助类SerialPortDataHelper及数据处理接口ISerialPortDataObserver。在SerialPortDataHelper中创建线程,基于信号量处理完整的串口数据包。

    4、具体实现

    ISerialPortDataObserver.cs

    namespace Xhubobo.IO.Ports
    {
        interface ISerialPortDataObserver
        {
            void ReceiveData(byte[] data);
        }
    }

    SerialPortDataHelper.cs

    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    namespace Xhubobo.IO.Ports
    {
        internal class SerialPortDataHelper
        {
            #region 线程
    
            private Thread _threadWorker;
            private bool _threadWorking;
            private readonly object _threadWorkingLockHelper = new object();
    
            private bool IsThreadWorking
            {
                get
                {
                    bool ret;
                    lock (_threadWorkingLockHelper)
                    {
                        ret = _threadWorking;
                    }
    
                    return ret;
                }
                set
                {
                    lock (_threadWorkingLockHelper)
                    {
                        _threadWorking = value;
                    }
                }
            }
    
            #endregion
    
            #region 队列
    
            private readonly Queue<byte[]> _messageQueue;
            private readonly Semaphore _messageSemaphore;
    
            #endregion
    
            private readonly byte[] _buffer;
            private int _offset;
            private int _lastMessageTick;
            private readonly int _timeout;
    
            private readonly ISerialPortDataObserver _dataObserver;
    
            public SerialPortDataHelper(
                ISerialPortDataObserver dataObserver,
                int timeout = 128, int bufferSize = 4096)
            {
                _dataObserver = dataObserver;
                _timeout = timeout;
                _buffer = new byte[bufferSize];
    
                _messageQueue = new Queue<byte[]>();
                _messageSemaphore = new Semaphore(0, byte.MaxValue);
            }
    
            public void Start()
            {
                IsThreadWorking = true;
                _threadWorker = new Thread(DoWork)
                {
                    IsBackground = true
                };
                _threadWorker.Start();
            }
    
            public void Stop()
            {
                IsThreadWorking = false;
                AddMessage(null);
                if (_threadWorker != null)
                {
                    _threadWorker.Join();
                    _threadWorker = null;
                }
    
                ClearMessage();
            }
    
            #region 队列操作
    
            public void AddMessage(byte[] message)
            {
                if (IsThreadWorking)
                {
                    lock (_messageQueue)
                    {
                        _messageQueue.Enqueue(message);
                    }
    
                    _messageSemaphore.Release();
                }
            }
    
            private byte[] PickMessage()
            {
                byte[] message = null;
                lock (_messageQueue)
                {
                    if (_messageQueue.Count > 0)
                    {
                        message = _messageQueue.Peek();
                        _messageQueue.Dequeue();
                    }
                }
    
                return message;
            }
    
            private void ClearMessage()
            {
                lock (_messageQueue)
                {
                    _messageQueue.Clear();
                }
            }
    
            #endregion
    
            /// <summary>
            /// 线程执行方法
            /// </summary>
            private void DoWork()
            {
                while (IsThreadWorking)
                {
                    if (_messageSemaphore.WaitOne(1))
                    {
                        var message = PickMessage();
                        HandleMessage(message);
                    }
    
                    DispatchData();
                }
            }
    
            #region HandleMessage
    
            private void HandleMessage(byte[] message)
            {
                _lastMessageTick = Environment.TickCount;
    
                if (_offset + message.Length > _buffer.Length)
                {
                    ResetBuffer();
                    return;
                }
    
                Buffer.BlockCopy(message, 0, _buffer, _offset, message.Length);
                _offset += message.Length;
            }
    
            private void DispatchData()
            {
                if (_offset > 0 && Environment.TickCount - _lastMessageTick > _timeout)
                {
                    var buffer = new byte[_offset];
                    Buffer.BlockCopy(_buffer, 0, buffer, 0, _offset);
                    _dataObserver?.ReceiveData(buffer);
                    ResetBuffer();
                }
            }
    
            private void ResetBuffer()
            {
                _offset = 0;
            }
    
            #endregion
        }
    }

    SerialPortStreamHelper.cs

    using RJCP.IO.Ports;
    using System;
    using System.Linq;
    
    namespace Xhubobo.IO.Ports
    {
        public class SerialPortStreamHelper : ISerialPortDataObserver
        {
            public event Action<string, byte[]> DataReceived = (portName, data) => { };
            public string LastErrorMessage { get; private set; }
    
            #region SerialPortStream Attributes
    
            public string PortName => _serialPortStream.PortName;
            public int BaudRate => _serialPortStream.BaudRate;
            public Parity Parity => _serialPortStream.Parity;
            public int DataBits => _serialPortStream.DataBits;
            public StopBits StopBits => _serialPortStream.StopBits;
    
            public bool IsOpen => _serialPortStream.IsOpen;
    
            public bool DtrEnable
            {
                set { _serialPortStream.DtrEnable = value; }
                get { return _serialPortStream.DtrEnable; }
            }
    
            public bool RtsEnable
            {
                set { _serialPortStream.RtsEnable = value; }
                get { return _serialPortStream.RtsEnable; }
            }
    
            #endregion
    
            /// <summary>
            /// 是否使用接收超时机制
            /// 默认为true
            /// 接收到数据后计时,计时期间收到数据,累加数据,重新开始计时。超时后返回接收到的数据。
            /// </summary>
            private readonly bool _enableTimeout;
    
            private readonly SerialPortStream _serialPortStream;
            private readonly SerialPortDataHelper _dataHelper;
    
            public SerialPortStreamHelper(bool enableTimeout = true, int timeout = 128, int bufferSize = 4096)
            {
                _enableTimeout = enableTimeout;
                _serialPortStream = new SerialPortStream()
                {
                    DtrEnable = true,
                    RtsEnable = true
                };
                _serialPortStream.DataReceived += OnDataReceived;
                _dataHelper = new SerialPortDataHelper(this, timeout, bufferSize);
            }
    
            public static string[] GetPortNames() => SerialPortStream.GetPortNames();
    
            public static string BytesToHexStr(byte[] bytes)
            {
                return string.Join(" ", bytes.Select(t => t.ToString("X2")));
            }
    
            public bool Open(string portName, int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8,
                StopBits stopBits = StopBits.One)
            {
                _serialPortStream.PortName = portName;
                _serialPortStream.BaudRate = baudRate;
                _serialPortStream.Parity = parity;
                _serialPortStream.DataBits = dataBits;
                _serialPortStream.StopBits = stopBits;
    
                try
                {
                    _serialPortStream.Open();
                    _dataHelper.Start();
                    return true;
                }
                catch (Exception e)
                {
                    LastErrorMessage = e.Message;
                    return false;
                }
            }
    
            public void Close()
            {
                if (_serialPortStream.IsOpen)
                {
                    _dataHelper.Stop();
                    _serialPortStream.Close();
                }
            }
    
            public void Dispose()
            {
                _dataHelper.Stop();
                _serialPortStream.Dispose();
            }
    
            public void ReceiveData(byte[] data)
            {
                DataReceived?.Invoke(PortName, data);
            }
    
            private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                if (_enableTimeout)
                {
                    while (_serialPortStream.BytesToRead > 0)
                    {
                        var bytesToRead = _serialPortStream.BytesToRead;
                        var buffer = new byte[bytesToRead];
    
                        var bytesRead = _serialPortStream.Read(buffer, 0, bytesToRead); //此处可能存在数组越界问题
                        if (bytesRead != bytesToRead)
                        {
                            throw new Exception("Serial port receives exception!");
                        }
    
                        _dataHelper.AddMessage(buffer);
                    }
                }
                else
                {
                    var bytesToRead = _serialPortStream.BytesToRead;
                    if (bytesToRead == 0)
                    {
                        return;
                    }
    
                    var buffer = new byte[bytesToRead];
                    var offset = 0;
                    while (offset < bytesToRead)
                    {
                        //读取数据到缓冲区
                        offset += _serialPortStream.Read(buffer, offset, bytesToRead - offset);
                    }
    
                    DataReceived?.Invoke(PortName, buffer);
                }
            }
    
            #region Write
    
            public void Write(byte[] buffer)
            {
                if (IsOpen)
                {
                    _serialPortStream.Write(buffer, 0, buffer.Length);
                }
            }
    
            public void Write(byte[] buffer, int offset, int count)
            {
                if (IsOpen)
                {
                    _serialPortStream.Write(buffer, offset, count);
                }
            }
    
            public void Write(string text)
            {
                if (IsOpen)
                {
                    _serialPortStream.Write(text);
                }
            }
    
            public void WriteLine(string text)
            {
                if (IsOpen)
                {
                    _serialPortStream.WriteLine(text);
                }
            }
    
            #endregion
        }
    }
  • 相关阅读:
    GridControl 选择列、复选框全选(上)
    iOS开发隐藏键盘方法总结
    Leetcode:remove_duplicates_from_sorted_list
    新版的Spring4X怎样下载
    leetcode 242: Valid Anagram
    最简单的基于FFMPEG的Helloworld程序
    电子商务系统的设计与实现(十):DWZ框架与第三方分页组件整合
    电子商务系统的设计与实现(十):DWZ框架与第三方分页组件整合
    电子商务系统的设计与实现(九):后端管理系统功能细化
    电子商务系统的设计与实现(九):后端管理系统功能细化
  • 原文地址:https://www.cnblogs.com/xhubobo/p/14592596.html
Copyright © 2020-2023  润新知