混点分【TCP 通讯,包括:分包发送,帧头,CRC16校验,AES 加解密】

 

最近业务需要,需要重写一些C# 的 TCP通讯相关的东西,为了混点分下载资料,发上来供大家参考,有问题请留言指教,理性探讨,相互学习。

情景如下:公司业务需要在云服务器上实时采集很多智能传感器/PLC等之类的终端设备数据,有实时性/安全性/容错性的实际要求,采用最常见的CS架构。基本功能是很好实现的。但是中间很多坑在这里和大家分享。

先说点教训与心得:

1.这种多接入对象一定要采用异步监听。

2.TCP链路最好采用短链接进行单次业务流程,即上下行1次数据,长连接不好维护。

3.实际部署在云端后,网络延迟等因素,网络流字节到达时间有延迟,所以不要想着 Sleep 一下,再去读。老老实实设计通讯规约,设计好帧头,起始符,校验方式,容错机制。

4.要做到较高实时性,一定不要故弄玄虚,规约设计的内容存在即有用,非传输必须的功能一定不要在传输过程中处理。

5.容错机制设计时,不要想到云服务器应该不会怎样,而要考虑成出现这种异常要怎样。

6.接收与解析异步处理。

 

然后是代码,需要使用的同学详看,下面已经过初步测试

示例链接  示例点击下载

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace CS_TEST
{
   
     * 该部分包括:
     * 1.站点到服务器
     * 2.客户端到服务器
     
         */
    public class FCS_Protocols
    {
        public class FCSNetSteam
        {
            public FCSNetSteam(NetworkStream ns)
            {
                State = RCErrors.NORMAL;
                nstem = ns;
            }
            public int State;
            public NetworkStream nstem;
        }

        /// <summary>
        /// 收发消息
        /// </summary>
        public struct RCErrors
        {
            public const int NORMAL = 0;//正常
            public const int NS_WRITE_LOCKED = 1;//当前流锁定中,禁止写操作
            public const int PACKAGE_LEN_ERR = 2;//接收到的包长度不正确。
            public const int PACKAGE_INDEX_ERR = 3;//当前分包序列号错误
            public const int PACKAGE_READ_ERR = 4;//读取分包内容失败
            public const int NS_NORESPONS = 5;//网络流读取无响应,包括 读取无字节 和 未读取到标识符两种情况。
            public const int CRC16_ERR = 6;//CRC校验失败
        }

        //======================字段==========================
        /// <summary>
        /// AESKey
        /// </summary>
        public static byte[] AesKeys = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
        /// <summary>
        /// 流起始标识
        /// </summary>
        private byte[] HeadBegin = Encoding.UTF8.GetBytes("FCCP");
        /// <summary>
        /// 发送报文字节计数,不包含Header 20字节
        /// </summary>
        public long SendCtr;
        /// <summary>
        /// 接收报文字节计数,不包含Header 20字节
        /// </summary>
        public long ReceiveCtr;
        /// <summary>
        /// 消息事件
        /// </summary>
        /// <param name="ERRID">消息ID</param>
        /// <param name="ErrDesc">消息补充描述</param>
        public delegate void RC_MessageHandle(int ERRID, string ErrDesc);
        public event RC_MessageHandle RC_MessageEvent;
        /// <summary>
        /// 帧流水号
        /// </summary>
        public int PackagesCtr = 0;

        //======================工具方法==========================
        /// <summary>
        /// 检查流是否争用
        /// </summary>
        /// <param name="ns">网络流实例</param>
        /// <returns></returns>
        private int CheckFCSNsTimeOut(FCSNetSteam ns)
        {
            int delay = 0;
            while (ns.State == RCErrors.NS_WRITE_LOCKED)
            {
              

                if (delay >= 1000)
                {
                    RC_MessageEvent?.Invoke(RCErrors.NS_WRITE_LOCKED, "流写操作未结束");
                    return RCErrors.NS_WRITE_LOCKED;
                }
                delay++;
                Thread.Sleep(1);
            }
            return RCErrors.NORMAL;
        }

        /// <summary>
        /// CRC16校验码计算
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public  byte[] AddCrc16(byte[] data)
        {
            //常数
            int Const = 0xa001;
            //设置CRC寄存器初始值
            int crc = 0xffff;
            for (int i = 0; i < data.Length; i++)
            {
                //CRC与命令字节异或
                crc ^= data[i];
                for (int j = 0; j < 8; j++)
                {
                    if ((int)(crc & 0x1) == 1) //二进制末尾是1,进行右移和常数异或运算
                    {
                        crc >>= 1;
                        crc ^= Const;
                    }
                    else //二进制末尾为0,仅仅进行右移运算
                    {
                        crc >>= 1;
                    }
                }
            }
            byte[] result = new byte[data.Length + 2];
            for (int i = 0; i < data.Length; i++)
            {
                result[i] = data[i];
            }
            result[result.Length - 2] = (byte)(crc & 0xFF);
            result[result.Length - 1] = (byte)(crc >> 8);
            return result;
        }


       /// <summary>
       /// 解密
       /// </summary>
       /// <param name="buffer"></param>
       /// <returns></returns>
        private byte[] Decrypt(byte[] buffer)
        {
            byte[] buffer_ = AESTools.Decrypt(AesKeys, buffer);

            return buffer_;
        }

        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        private byte[] Encrypt(byte[] buffer)
        {
            byte[] buffer_ = AESTools.Encrypt(AesKeys, buffer);

            return buffer_;
        }

        //---------------------------------------收发方法---------------------------

        /// <summary>
        /// 字符串发送
        /// </summary>
        /// <param name="ns">指定流</param>
        /// <param name="bufferStr">报文</param>
        public int SendFCSBuffer(FCSNetSteam ns, string bufferStr, int PackageSize)
        {
            int err = CheckFCSNsTimeOut(ns);
            if (err == RCErrors.NS_WRITE_LOCKED)
            {
                return RCErrors.NS_WRITE_LOCKED;
            }
            ns.State = RCErrors.NS_WRITE_LOCKED;
            byte[] bs0 = Encoding.UTF8.GetBytes(bufferStr);
            bs0 = Encrypt(bs0);
            SendBufferPackage(ns, bs0, PackageSize);
            ns.State = RCErrors.NORMAL;
            PackagesCtr++;
            return RCErrors.NORMAL;
        }
        /// <summary>
        /// 字节序列发送,用于文件类传输,文件的读存,用内存缓存后,与收发异步完成
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="Bs"></param>
        /// <param name="PackageSize"></param>
        /// <returns></returns>
        public int SendFCSBs(FCSNetSteam ns, byte[] Bs, int PackageSize)
        {
            int err = CheckFCSNsTimeOut(ns);
            if (err == RCErrors.NS_WRITE_LOCKED)
            {
                return RCErrors.NS_WRITE_LOCKED;
            }
            ns.State = RCErrors.NS_WRITE_LOCKED;
            SendBufferPackage(ns, Bs, PackageSize);
            ns.State = RCErrors.NORMAL;
            PackagesCtr++;
            return RCErrors.NORMAL;
        }

        /// <summary>
        /// 发送分包
        /// </summary>
        /// <param name="ns">指定流实例</param>
        /// <param name="bs">内容字节</param>
        /// <param name="PLen">分包长度</param>
        private void SendBufferPackage(FCSNetSteam ns, byte[] bs, int PLen)
        {
            double r = Math.IEEERemainder(bs.Length, PLen);
            byte[] bsTotalLen = BitConverter.GetBytes(bs.Length);
            byte Pctr = 0;
            if (r != 0)
            {
                Pctr = (byte)(bs.Length / PLen + 1);
            }
            else
            {
                Pctr = (byte)(bs.Length / PLen);
            }
            if (Pctr != 0)
            {
                for (byte i = 0; i < Pctr; i++)
                {
                    //从高位到低位:1,2,3,4字节为 帧流水号;5字节为分包序号;6字节为分包总数;7,8,9,10字节为当前包长度;11,12,13,14 为总字节长度;15,16为前14位CRC校验码
                    byte[] IDs = BitConverter.GetBytes(PackagesCtr);
                    byte Index = (byte)(i + 1);
                    byte PTotal = Pctr;
                    byte[] bs0;
                    byte[] bsw;
                    byte[] bsLen;
                    byte[] Header = new byte[14];

                    if (i == Pctr - 1)
                    {
                        int len = bs.Length - PLen * i;
                        bs0 = new byte[len];
                        Array.Copy(bs, PLen * i, bs0, 0, len);
                        bsLen = BitConverter.GetBytes(len);
                    }
                    else
                    {
                        bs0 = new byte[PLen];
                        Array.Copy(bs, PLen * i, bs0, 0, PLen);
                        bsLen = BitConverter.GetBytes(PLen);
                    }
                    bsw = new byte[bs0.Length + Header.Length+2];
              

                    //-组装帧头:
                    //1,2,3,4字节为 帧流水号;5字节为分包序号;6字节为分包总数;7,8,9,10字节为当前包长度;11,12,13,14 为总字节长度;15,16为前14位CRC校验码
                    Array.Copy(IDs, Header, IDs.Length);//ID
                    Header[4] = Index;//Index
                    Header[5] = PTotal;//Total
                    Array.Copy(bsLen, 0, Header, 6, bsLen.Length);//Len
                    Array.Copy(bsTotalLen, 0, Header, 10, bsTotalLen.Length);//TotalLen
                    Header = AddCrc16(Header);//Crc16

                    Array.Copy(Header, bsw, Header.Length);
                    Array.Copy(bs0, 0, bsw, Header.Length, bs0.Length) ;

                    byte[] bs_Final = new byte[bsw.Length + HeadBegin.Length];//起始符
                    Array.Copy(HeadBegin, bs_Final, HeadBegin.Length);
                    Array.Copy(bsw, 0,bs_Final,HeadBegin.Length,bsw.Length);
                    ns.nstem.Write(bs_Final, 0, bs_Final.Length);
                    SendCtr += bs0.Length;
                }
            }

        }


        /// <summary>
        /// 从网络流里诊断起始符位置,防止云服务器莫名其妙粘连字节
        /// </summary>
        /// <param name="ns">指定流实例</param>
        /// <returns></returns>
        private bool CheckHeaderBegin(FCSNetSteam ns)
        {
            int k = 0;
            byte[] bs = new byte[1];
            int b = ns.nstem.Read(bs, 0, bs.Length);
            while (b != -1)
            {
                if (bs[0] != HeadBegin[k])
                {

                }
                else
                {
                    k++;
                    if (k == HeadBegin.Length)
                    {
                        return true;
                    }
                }

                b = ns.nstem.Read(bs, 0, bs.Length);
            }
            return false;
        }
      
        /// <summary>
        /// 接收报文
        /// </summary>
        /// <param name="ns">指定流</param>
        public string ReceiveFCSBuffer(FCSNetSteam ns, int BufferSize)
        {
            byte[] bsR = new byte[0];
            int offset = 0;
            byte[] FirsPackage = ReceiveBufferPackage(ns);
            if (FirsPackage.Length!=0)
            {
                byte[] IDs = new byte[4];
                Array.Copy(FirsPackage, IDs, 4);
                byte Index = FirsPackage[4];
                byte PTotal = FirsPackage[5];
                int PLen = BitConverter.ToInt32(FirsPackage, 6);
                int PTotalLen = BitConverter.ToInt32(FirsPackage, 10);
                bsR = new byte[PTotalLen];
                Array.Copy(FirsPackage,14, bsR,0,PLen);
                offset = PLen;
                while (Index != PTotal)
                {
                    Index++;
                    byte[] Package = ReceiveBufferPackage(ns);
                    if (Package.Length == 0)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_READ_ERR, "包读取失败: " + Index + "/" + PTotal);
                        return null;
                    }
                    int _Index = Package[4];
                    if (_Index != Index)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,与预期值不一致: " + Index + "?" + _Index);
                        return null;
                    }
                    int _PTotal = Package[5];
                    if (_PTotal != PTotal)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_LEN_ERR, "数据总长不一致: " + PTotal + "?" + _PTotal);
                        return null;
                    }
                    PLen = BitConverter.ToInt32(Package, 6);
                    Array.Copy(Package,14,bsR, offset, PLen);
                    offset += PLen;
                }
            }

        
            if (bsR.Length != 0)
            {
                bsR = Decrypt(bsR);//解密
                string s = UTF8Encoding.UTF8.GetString(bsR);

                return s;
            }
            return null;
        }

        /// <summary>
        /// 字节序列接收,用于文件类传输,文件的读存,用内存缓存后,与收发异步完成
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="BufferSize"></param>
        /// <returns></returns>
        public byte[] ReceiveFCSBs(FCSNetSteam ns, int BufferSize)
        {
            byte[] bsR = new byte[0];
            int offset = 0;
            byte[] FirsPackage = ReceiveBufferPackage(ns);
            if (FirsPackage.Length != 0)
            {
                byte[] IDs = new byte[4];
                Array.Copy(FirsPackage, IDs, 4);
                byte Index = FirsPackage[4];
                byte PTotal = FirsPackage[5];
                int PLen = BitConverter.ToInt32(FirsPackage, 6);
                int PTotalLen = BitConverter.ToInt32(FirsPackage, 10);
                bsR = new byte[PTotalLen];
                Array.Copy(FirsPackage, 14, bsR, 0, PLen);
                offset = PLen;
                while (Index != PTotal)
                {
                    Index++;
                    byte[] Package = ReceiveBufferPackage(ns);
                    if (Package.Length == 0)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_READ_ERR, "包读取失败: " + Index + "/" + PTotal);
                        return null;
                    }
                    int _Index = Package[4];
                    if (_Index != Index)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,与预期值不一致: " + Index + "?" + _Index);
                        return null;
                    }
                    int _PTotal = Package[5];
                    if (_PTotal != PTotal)
                    {
                        RC_MessageEvent?.Invoke(RCErrors.PACKAGE_LEN_ERR, "数据总长不一致: " + PTotal + "?" + _PTotal);
                        return null;
                    }
                    PLen = BitConverter.ToInt32(Package, 6);
                    Array.Copy(Package, 14, bsR, offset, PLen);
                    offset += PLen;
                }
            }


            if (bsR.Length != 0)
            {

                return bsR;
            }
            return null;
        }

        /// <summary>
        /// 从网络流里提取包
        /// </summary>
        /// <param name="ns"></param>
        /// <returns>返回包,长度为0时 存在ERR,可通过消息事件获取ERR信息</returns>
        private byte[] ReceiveBufferPackage(FCSNetSteam ns)
        {
            byte[] bsR = new byte[0];
            if (!CheckHeaderBegin(ns))
            {
                return bsR;
            }
            byte[] bsHeader = new byte[16];
            int len = ns.nstem.Read(bsHeader, 0, bsHeader.Length);
            if (len == bsHeader.Length)
            {
                byte[] IDs = new byte[4];
                Array.Copy(bsHeader, IDs, 4);
                byte Index = bsHeader[4];
                byte PTotal = bsHeader[5];
                if (Index>PTotal)
                {
                    RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,大于总包数: " + Index + ">?" + PTotal);
                    return bsR;
                }
                int PLen = BitConverter.ToInt32(bsHeader, 6);
                int PTotalLen = BitConverter.ToInt32(bsHeader, 10);
                byte CRC_H = bsHeader[14];
                byte CRC_L = bsHeader[15];
                if (!CheckCRC(bsHeader))
                {
                    RC_MessageEvent?.Invoke(RCErrors.CRC16_ERR, "CRC16校验失败: " + CRC_H + "-" + CRC_L + "  包序列 : " + Index + "/" + PTotal);
                    return bsR;
                }
                bsR = new byte[16 + PLen];
                byte[] bs = new byte[PLen];
                ns.nstem.Read(bs, 0, PLen);
                Array.Copy(bsHeader, bsR, 16);
                Array.Copy(bs,0, bsR,14, bs.Length);
                ReceiveCtr += bs.Length;
            }
            return bsR;
        }


        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="bs">被校验的字节组,既得的校验码在最后两位H->L</param>
        /// <returns></returns>
        private bool CheckCRC(byte[] bs)
        {
            byte[] bs0 = new byte[bs.Length - 2];
            Array.Copy(bs, bs0, bs0.Length);
            bs0 = AddCrc16(bs0);
            if (bs0[15]==bs[15]& bs0[14] == bs[14])//为提升效率 长度写为常数,帧头部分发生变化时须手动修改。
            {
                return true;
            }
            return false;
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值