v9.8.2018.0605   由DataReader直接映射实体列表,以支持netstandard的MySql和SQLite,且提升性能
大石头 编写于 2018-06-05 00:45:23
X
using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using NewLife.Data;

namespace NewLife.Net.Application
{
    /// <summary>串口服务器。把收发数据映射到本地的指定串口</summary>
    /// <remarks>
    /// 一定要注意:串口编程最大的问题在于硬件设备可能极为不稳定,响应速度不可估计,所以需要尽可能的等待。
    /// 
    /// 串口读取的学问非常的大,影响响应比较慢,要等一会才能得到数据,并且可能只是得到一部分,而无限制的等待,又会让整个读取变得相当漫长。
    /// 一般来说,串口传输的数据,是指定长度有头有尾的帧数据。因此,只需要设定好等待时间和帧大小,就能很好的读取数据。
    /// </remarks>
    public class SerialServer : NetServer<SerialServer.Session>
    {
        #region 属性
        private String _PortName = "COM1";
        /// <summary>串口名。默认COM1</summary>
        public String PortName { get { return _PortName; } set { _PortName = value; } }

        private Boolean _AutoClose = true;
        /// <summary>每次收发完数据之后自动关闭</summary>
        public Boolean AutoClose { get { return _AutoClose; } set { _AutoClose = value; } }

        private SerialPort _Serial;
        /// <summary>串口对象</summary>
        public SerialPort Serial { get { return _Serial; } set { _Serial = value; } }

        private Int32 _Timeout = 1000;
        /// <summary>超时时间</summary>
        public Int32 Timeout { get { return _Timeout; } set { _Timeout = value; } }

        private Int32 _ReceivedBytesThreshold = 1;
        /// <summary>每一帧长度</summary>
        public Int32 ReceivedBytesThreshold { get { return _ReceivedBytesThreshold; } set { _ReceivedBytesThreshold = value; } }
        #endregion

        #region 构造
        /// <summary>实例化一个串口服务器</summary>
        public SerialServer()
        {
            Port = 24;
            Name = "串口服务";
        }

        /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
        /// <param name="disposing">从Dispose调用(释放所有资源)还是析构函数调用(释放非托管资源)</param>
        protected override void OnDispose(Boolean disposing)
        {
            base.OnDispose(disposing);

            var sp = Serial;
            Serial = null;
            if (sp != null) sp.Dispose();
        }
        #endregion

        #region 事件
        /// <summary>创建窗口对象时触发</summary>
        public event EventHandler OnCreate;

        void RaiseCreate()
        {
            if (OnCreate != null) OnCreate(this, EventArgs.Empty);
        }
        #endregion

        #region 业务
        #endregion

        #region 会话
        /// <summary>串口服务会话</summary>
        public class Session : NetSession<SerialServer>
        {
            static readonly Byte[] vspStart = new Byte[] { 0xFF, 0xFA, 0x2C };
            static readonly Byte[] vspEnd = new Byte[] { 0xFF, 0xF0 };

            /// <summary>收到客户端发来的数据</summary>
            /// <param name="e"></param>
            protected override void OnReceive(ReceivedEventArgs e)
            {
                var ms = e.Stream;
                if (ms == null || ms.Length <= 0) return;

                var sp = Host.Serial;
                if (sp == null)
                {
                    sp = new SerialPort(Host.PortName);
                    Host.Serial = sp;

                    //sp.BaudRate = 9600;
                    //sp.DataBits = 8;
                    //sp.Parity = Parity.None;
                    //sp.StopBits = StopBits.One;
                    if (Host.Timeout > 0) sp.ReadTimeout = sp.WriteTimeout = Host.Timeout;
                    //sp.ReadBufferSize = sp.WriteBufferSize = 1024;
                    //sp.Handshake = Handshake.None;

                    //sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
                    if (Host.ReceivedBytesThreshold > 0) sp.ReceivedBytesThreshold = Host.ReceivedBytesThreshold;

                    // 如果不设置,有时候可能读不到数据
                    sp.RtsEnable = true;
                    sp.DtrEnable = true;

                    Host.RaiseCreate();
                }

                //var ms = e.GetStream();

                #region 特殊处理VSP格式
                // 特殊处理VSP格式。有可能是先开始FFF0才到FFFA
                while (ms.Position < ms.Length && (ms.StartsWith(vspStart) || ms.StartsWith(vspEnd) && ms.StartsWith(vspStart)))
                {
                    var data = ms.ReadTo(vspEnd);
                    if (data != null)
                    {
                        WriteLog("VSP: {0}", data.ToHex());
                        switch (data[0])
                        {
                            case 0x01:
                                Array.Reverse(data, 1, 4);
                                var rate = BitConverter.ToInt32(data, 1);
                                if (rate > 0) sp.BaudRate = rate;
                                break;
                            case 0x02:
                                if (data[1] > 0) sp.DataBits = data[1];
                                break;
                            case 0x03:
                                if (data[1] > 0) sp.Parity = (Parity)data[1];
                                break;
                            case 0x04:
                                if (data[1] > 0) sp.StopBits = (StopBits)data[1];
                                break;
                            case 0x05:
                                if (data[1] > 0) sp.Handshake = (Handshake)data[1];
                                break;
                            default:
                                break;
                        }
                    }
                }
                #endregion

                if (ms.Position >= ms.Length) return;

                // 必须加锁,防止多线程冲突
                lock (sp)
                {
                    if (!sp.IsOpen) sp.Open();

                    // 写数据
                    var data2 = ms.ReadBytes();
                    WriteLog("Net=>SerialPort: {0}", data2.ToHex());
                    sp.Write(data2, 0, data2.Length);

                    // 读数据
                    ReadAndSend(sp, Session);

                    if (Host.AutoClose) sp.Close();
                }

                //base.OnReceive(e);
            }

            void ReadAndSend(SerialPort sp, ISocketSession session)
            {
                // 读取数据
                var data = Read(sp, Host.ReceivedBytesThreshold);
                if (data != null && data.Count > 0)
                {
                    WriteLog("SerialPort=>Net: {0}", data.ToHex());
                    session.Send(data);
                }
            }
        }
        #endregion

        #region 辅助
        /// <summary>从串口中读取指定长度的数据,一般是一帧</summary>
        /// <param name="sp"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static Packet Read(SerialPort sp, Int32 length = -1)
        {
            // 读取数据
            var ms = new MemoryStream();
            if (sp.ReadTimeout < 1) sp.ReadTimeout = 1000;

            while (sp.IsOpen)
            {
                // 等待1秒,直到有数据为止
                var timeout = sp.ReadTimeout;
                if (timeout < 1) timeout = 500;
                var end = DateTime.Now.AddMilliseconds(timeout);
                while (sp.BytesToRead <= 0 && sp.IsOpen && end > DateTime.Now) Thread.SpinWait(1);

                if (!sp.IsOpen || sp.BytesToRead <= 0) break;

                try
                {
                    var data = new Byte[sp.BytesToRead];
                    var count = sp.Read(data, 0, data.Length);
                    if (count > 0) ms.Write(data, 0, count);
                }
                catch { break; }

                // 如果已经足够一帧,结束
                if (length > 0 && ms.Length >= length) break;
            }

            ms.Position = 0;
            return ms.ReadBytes();
        }
        #endregion
    }
}