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.服务端:
  先想办法获取消息头
    如果消息头一直没满,那就一直获取
    如果消息头满了,就计算出消息体长度,并向下获取消息体
  再想办法获取消息体
    根据消息头包含的消息体长度,获取消息体
    如果消息体一直没满,那就一直获取
    如果消息体满了,那就重新获取消息头

posted @ 2020-03-25 20:59  屌丝大叔的笔记  阅读(3830)  评论(0编辑  收藏  举报