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