Client.cs 12.8 KB
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AGVLib
{
    /// <summary>
    /// AGV客户端
    /// </summary>
    public class Client
    {
        internal static LogBean Log;
        private bool _loop;
        private TcpClient _client; //客户端
        private List<Node> _nodes;
        private Thread tSend;          //发送
        private string serverIp;        //远程IP地址
        private int serverPort;    //端口
        /// <summary>
        /// 接收消息事件委托
        /// </summary>
        public delegate void ReceiveEventHandler(Node node);
        /// <summary>
        /// 服务器连接事件
        /// </summary>
        /// <param name="status"></param>
        public delegate void ConnectedEventHandler(bool status);
        /// <summary>
        /// 客户端接收事件
        /// </summary>
        public event ReceiveEventHandler ReceivedEvent;
        /// <summary>
        /// 服务器连接事件
        /// </summary>
        public event ConnectedEventHandler ConnectedEvent;

        /// <summary>
        /// 服务端信息
        /// </summary>
        public string ServerInfo
        {
            get
            {
                return string.Format("{0}:{1}", serverIp, serverPort.ToString());
            }
        }
        /// <summary>
        /// AGV客户端
        /// </summary>
        /// <param name="nodes">当前客户端所使用的节点</param>
        public Client(List<Node> nodes = null)
        {
            _loop = true;
            if (nodes == null)
                _nodes = new List<Node>();
            else
                _nodes = nodes;
            Log = TcpClient.Log;
        }
        ~Client()
        {
            _loop = false;
        }
        /// <summary>
        /// 是否连接服务器
        /// </summary>
        public bool Connected { private set; get; } = false;

        /// <summary>
        /// 发送命令的时间间隔(单位:秒)
        /// </summary>
        public int SendSleep { set; get; } = 1;
        /// <summary>
        /// 是否屏蔽:true:信号屏蔽,false:信号开启
        /// </summary>
        public bool Shielded { get; set; } = true;
        public bool IsAutoReconnect = false;
        /// <summary>
        /// 连接
        /// </summary>
        public void Connect(string serverIP, int port = 12000)
        {
            Task.Factory.StartNew(delegate
            {
                serverIp = serverIP;
                serverPort = port;
                _client = new TcpClient();
                bool rtn = _client.Connect(serverIp, serverPort, ReceiveMessage);
                Connected = rtn;
                ConnectedEvent?.Invoke(rtn);
                _loop = true;
                IsAutoReconnect = true;
                tSend = new Thread(KeepSendStatus);
                tSend.IsBackground = true; ;
                tSend.Start();
                Shielded = false;
            });

        }

        /// <summary>
        /// 关闭
        /// </summary>
        public void Close()
        {
            Task.Factory.StartNew(delegate
            {
                Shielded = true;
                _loop = false;
                SendStatus();
                if (_client != null)
                {
                    _client.Close();
                    _client = null;
                }
                Connected = false;
                ConnectedEvent?.Invoke(false);
            });
        }
        /// <summary>
        /// 设置节点状态
        /// </summary>
        /// <param name="node"></param>
        public void SetStatus(Node node)
        {
            Node find = _nodes.Find(s => s.id == node.id);
            if (find == null)  //没有找到
            {
                _nodes.Add(node);
            }
            else
            {
                find.SetState(node.status, node.level, node.shelf_id, node.remark);
            }
            Log.Info($"SetStatus To Server 【{ServerInfo}】【{node.name}】【{node.status}】【{node.shelf_id}】【{node.ToText()}】");
            //Log.Info(string.Format("SetStatus To Server [{0}]:[{1}]", ServerInfo, node.ToText()));
        }

        /// <summary>
        /// 收到数据事件
        /// </summary>
        /// <param name="msg"></param>
        void ReceiveMessage(string msg, byte[] data)
        {
            if(string.IsNullOrEmpty(msg)) return;
            //Log.Info($"收到服务端【{ServerInfo}】的消息:【{msg}】【{StringHelper.ToHexString(data)}】");
            StickyBagHandle(data);
        }

        #region 粘包处理
        byte[] surplusBuffer = null;//不完整的数据包,即用户自定义缓冲区
        /// <summary>
        /// 粘包处理
        /// </summary>
        /// <param name="bytes"></param>
        private void StickyBagHandle(byte[] bytes)
        {
            //bytes 为系统缓冲区数据
            //bytesRead为系统缓冲区长度
            int bytesRead = bytes.Length;
            if (bytesRead > 0)
            {
                if (surplusBuffer != null)
                {
                    byte[] curBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
                    //更新会话的最新字节
                    surplusBuffer = curBuffer;//同步
                }
                else
                {
                    if (Common.CheckHeadChar(bytes, out int startIdx))//检查是否存在指定包头识别字符
                    {
                        //添加会话ID的bytes
                        byte[] tmp = new byte[bytesRead - startIdx];
                        Buffer.BlockCopy(bytes, startIdx, tmp, 0, bytesRead - startIdx);
                        surplusBuffer = tmp;//同步
                    }
                    else
                        return;
                }

                //已经完成读取每个数据包长度
                int haveRead = 0;
                //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
                int totalLen = surplusBuffer.Length;
                while (haveRead <= totalLen)
                {
                    //如果在N次拆解后剩余的数据包连一个包头的长度都不够
                    //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
                    if (totalLen - haveRead < Common.headSize)
                    {
                        byte[] byteSub = new byte[totalLen - haveRead];

                        //把剩下不够一个完整的数据包存起来
                        Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
                        surplusBuffer = byteSub;

                        totalLen = 0;

                        break;

                    }

                    //如果够了一个完整包,则读取包头的数据
                    byte[] headByte = new byte[Common.headSize];
                    Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, Common.headSize);//从缓冲区里读取包头的字节
                                                                                            //判断包头起始符
                    int bodySize = BitConverter.ToUInt16(headByte, 2);//从包头里面分析出包体的长度
                                                                      //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N

                    //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
                    if (haveRead + Common.headSize + bodySize > totalLen)
                    {
                        byte[] byteSub = new byte[totalLen - haveRead];
                        Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
                        surplusBuffer = byteSub;
                        break;
                    }
                    else
                    {
                        //挨个分解每个包,解析成实际文字
                        //String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
                        byte[] resBytes = new byte[bodySize];
                        Buffer.BlockCopy(surplusBuffer, haveRead + Common.headSize, resBytes, 0, bodySize);
                        Log.Debug($"粘包处理结果: 【{StringHelper.ToHexString(resBytes)}】【{StringHelper.ToHexString(surplusBuffer)}】");
                        DecodeNode(resBytes);
                        //依次累加当前的数据包的长度
                        haveRead = haveRead + Common.headSize + bodySize;
                        if (Common.headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
                        {
                            surplusBuffer = null;//设置空 回到原始状态
                            totalLen = 0;//清0
                        }

                    }

                }

            }

        }
        #endregion
        Node Decode(byte[] buff)
        {
            try
            {
                string json = System.Text.Encoding.UTF8.GetString(buff);
                Log.Debug(string.Format("Decode【{0}】", json));
                Node node = JsonHelper.DeserializeJsonToObject<Node>(json);

                return node;
            }
            catch (Exception ex)
            {
                Log.Error("Decode Error", ex);
                return null;
            }
        }
        private void DecodeNode(byte[] resultBytes)
        {
            Node node = Decode(resultBytes);
            if (node == null)
            {
                Log.Error($"命令解析失败:{StringHelper.ToHexString(resultBytes)}");
            }
            else
            {
                Log.Info($"Received事件触发:【{node.name}】【{node.status.ToString()}】【{node.shelf_id}】【{node.ToText()}】");
                //发送收到
                ReceivedEvent?.Invoke(node);
            }
        }
        /// <summary>
        /// 连续发送状态线程
        /// </summary>
        private void KeepSendStatus()
        {
            while (_loop)
            {
                try
                {

                    //Connected = _client.IsConnected();
                    if (!Connected)
                    {
                        if (IsAutoReconnect)
                        {
                            bool rtn = _client.Connect(serverIp, serverPort, ReceiveMessage);
                            Connected = rtn;
                            ConnectedEvent?.Invoke(rtn);
                        }
                    }
                    else
                    {
                        SendStatus();
                    }
                }
                catch (Exception ex)
                {
                    Log.Error($"KeepSendStatus出错", ex);
                }
                Thread.Sleep(SendSleep * 1000);
            }
        }
        object locsend = new object();
        private void SendStatus()
        {
            if (Monitor.TryEnter(locsend))
            {
                try
                {
                    bool bln;
                    foreach (Node node in _nodes)
                    {
                        node.shielded = Shielded;
                        byte[] buff = Encode(node);
                        bln = Send(buff);
                        Connected = bln;
                        if (!bln) continue;
                        Thread.Sleep(300);
                    }
                }
                catch (Exception ex)
                {
                    Log.Error($"SendStatus 出错", ex);
                }
                finally
                {
                    Monitor.Exit(locsend);
                }
            }


        }

        /// <summary>
        /// 发送命令
        /// </summary>
        /// <param name="buff"></param>
        /// <returns></returns>
        private bool Send(byte[] buff)
        {
            return _client.Send(buff);
        }
        byte[] Encode(Node node)
        {
            try
            {
                string json = JsonHelper.SerializeObject(node);
                Log.Debug(string.Format("Encode[{0}]", json));
                byte[] body = Encoding.UTF8.GetBytes(json);
                byte[] bodySize = Common.IntToByteArray(body.Length);
                byte[] buff = Common.headPack.Concat(bodySize).Concat(body).ToArray();
                return buff;
            }
            catch (Exception ex)
            {
                Log.Error("Encode Error", ex);
                return null;
            }
        }
    }

}