zoukankan      html  css  js  c++  java
  • c# socket通信,定义消息体长度同步接收数据

    因为没有文件上传,没有大的字节传输,数据过来就放到队列,所以没有用异步,使用同步方式接收数据。

    原理:

    1.前面四个字节是消息头,存放消息体长度;

    2.后面字节定义消息体;

    3.服务端收到消息后,先获取消息头部,如果不够继续接收;如果够了则根据头部计算出消息体长度;

    4.根据消息头标记的长度获取消息体,如果不够,继续接收;如果够了或者有多余,重新获取消息头部,不停的循环;

            private void Receive(Socket socket, string ip)
            {
                Task.Factory.StartNew(() =>
                {
                    var pack = new BytePkg();
    
                    while (true)
                    {
                        try
                        {
                            //如果socket已经断开,结束循环
                            if (!socket.Connected)
                            {
                                _logger.Warn($"IP:{ip},socket已断开,停止接收数据;");
                                break;
                            }
    
                            byte[] prevBytes = new byte[1024];//单次最多可接收的字节
                            int len = socket.Receive(prevBytes, prevBytes.Length, SocketFlags.None);
                            var bytes = prevBytes.Take(len).ToArray();//实际接收的字节
    
                            this.RcvHeadData(pack, bytes);
                        }
                        catch (Exception ex)
                        {
                            _logger.Error($"IP:{ip},接收socket数据异常,message:{ex.Message},stackTrace:{ex.StackTrace};");
                        }
                    }
                });
            }
            /// <summary>
            /// 接收消息头
            /// </summary>
            /// <param name="pack"></param>
            /// <param name="bytes"></param>
            private void RcvHeadData(BytePkg pack, byte[] bytes)
            {
                var len = bytes.Length;
    
                pack.headIndex += len;
                if (pack.headIndex < pack.headLen)
                {
                    for (int x = 0; x < len; x++)
                    {
                        pack.headBuff[pack.headIndex - len + x] = bytes[x];
                    };
                }
                else
                {
                    var actualHeadLen = pack.headIndex - len;//head的实际长度
                    var skipHeadLen = pack.headLen - actualHeadLen;//需要补上的长度;head定义长度 - head的实际长度 = body需要跳过的长度
                    for (int x = 0; x < skipHeadLen; x++)
                    {
                        pack.headBuff[actualHeadLen + x] = bytes[x];
                    }
    
                    //★★★★★开始处理消息体部分★★★★★
                    var bodyLen = len;//身体长度
                    if (skipHeadLen > 0)
                    {
                        bodyLen = len - skipHeadLen;//第一次,获取剩余部分的长度
                        pack.InitBodyBuff();//第一次,需要初始化body
                    }
    
                    this.RcvBodyData(pack, bytes.Skip(skipHeadLen).Take(bodyLen).ToArray());
                }
            }
    
            /// <summary>
            /// 接收消息体
            /// </summary>
            /// <param name="pack"></param>
            /// <param name="bytes"></param>
            private void RcvBodyData(BytePkg pack, byte[] bytes)
            {
                var len = bytes.Length;
    
                pack.bodyIndex += len;
    
                if (pack.bodyIndex < pack.bodyLen)
                {
                    for (int x = 0; x < len; x++)
                    {
                        pack.bodyBuff[pack.bodyIndex - len + x] = bytes[x];
                    };
                }
                else
                {
                    var actualBodyLen = pack.bodyIndex - len;//body的实际长度
                    var skipBodyLen = pack.bodyLen - actualBodyLen;//需要补上的长度;body定义长度 - body的实际长度 = 本次需要获取的body长度
                    for (int x = 0; x < skipBodyLen; x++)
                    {
                        pack.bodyBuff[actualBodyLen + x] = bytes[x];
                    }
    
                    //处理接收到的数据
                    NetMsg msg = ByteHelper.DeSerialize<NetMsg>(pack.bodyBuff);
                    this.OnReceiveMsg(msg);
    
                    //重置消息包
                    pack.ResetData();
    
                    //★★★★★开始处理消息头部分★★★★★
                    this.RcvHeadData(pack, bytes.Skip(skipBodyLen).ToArray());
                }
            }

    如果不放心,可以在该加日志的地方加日志,观察代码的逻辑是否正确。说明:这是借鉴一个叫做PESocket的开源项目改的。

    下面是用到的相关类:

        /// <summary>
        /// 消息接收类
        /// </summary>
        public class BytePkg
        {
            public int headLen = 4;
            public byte[] headBuff = null;
            public int headIndex = 0;
    
            public int bodyLen = 0;
            public byte[] bodyBuff = null;
            public int bodyIndex = 0;
    
            public BytePkg()
            {
                headBuff = new byte[4];
            }
    
            public void InitBodyBuff()
            {
                bodyLen = BitConverter.ToInt32(headBuff, 0);
                bodyBuff = new byte[bodyLen];
            }
    
            public void ResetData()
            {
                headIndex = 0;
                bodyLen = 0;
                bodyBuff = null;
                bodyIndex = 0;
            }
        }
        /// <summary>
        /// 字节辅助类
        /// </summary>
        public class ByteHelper
        {
            public static byte[] PackNetMsg<T>(T msg) where T : NetMsg
            {
                return PackLenInfo(Serialize(msg));
            }
            
            public static byte[] PackLenInfo(byte[] data)
            {
                int len = data.Length;
                byte[] pkg = new byte[len + 4];
                byte[] head = BitConverter.GetBytes(len);
                head.CopyTo(pkg, 0);
                data.CopyTo(pkg, 4);
                return pkg;
            }
    
            public static byte[] Serialize<T>(T pkg) where T : NetMsg
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, pkg);
                    ms.Seek(0, SeekOrigin.Begin);
                    return ms.ToArray();
                }
            }
    
            public static T DeSerialize<T>(byte[] bs) where T : NetMsg
            {
                using (MemoryStream ms = new MemoryStream(bs))
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    T pkg = (T)bf.Deserialize(ms);
                    return pkg;
                }
            }
        }
        /// <summary>
        /// 自定义请求数据格式
        /// </summary>
        [Serializable]
        public class NetMsg
        {
            public string EPCstring;
            public DateTime Time;
            public string IP;
            public string CommandType;
        }

    1.客户端:先将实体序列化,转为字节数组,然后同步发送即可,无需异步发送
    2.服务端:
      先想办法获取消息头
        如果消息头一直没满,那就一直获取
        如果消息头满了,就计算出消息体长度,并向下获取消息体
      再想办法获取消息体
        根据消息头包含的消息体长度,获取消息体
        如果消息体一直没满,那就一直获取
        如果消息体满了,那就重新获取消息头

  • 相关阅读:
    Python基础综合练习
    熟悉常用的Linux操作
    大数据概述
    C语言简易文法(无左递归)
    自动机
    C语言简易文法
    词法分析实验报告
    词法分析
    综合练习:词频统计
    组合数据类型综合练习
  • 原文地址:https://www.cnblogs.com/subendong/p/12570289.html
Copyright © 2011-2022 走看看