## **PanaCIM松下扣料机服务** #### **项目介绍** 生产车间拥有松下扣料机设备,需要采集读取多个送下设备生产报文,数据用于MES WMS计算生产排产和报表等功能。 #### **工作内容** 搭建TCP Server框架,并在现场调通设备通信。 #### **技术栈** C# .NET Core TCP通信框架: DotNetty #### **成果** 目前在江苏天宝汽车电子有限公司生产服务器局域网运行。 #### **难点** 该程序将会启用一个TCP服务器,车间内的松下设备会是客户端全部与服务器进行连接,同时不断发送XML报文。需要将XML报文拼成一个完整的报文发送到接下来的Channel进行处理。 再此期间需要编写解码器应对报文的沾包,断包,多包等问题。 相关:https://cybersicko.net/article/25.html #### **代码片段(解码器)** ```C# using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Transport.Channels; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; namespace He3.PanaCIM.SocketServer.Handler { /// /// 解码 /// public class DecoderHandler : ByteToMessageDecoder { private static log4net.ILog log = log4net.LogManager.GetLogger("Log.DecoderHandler"); private IByteBuffer byteBuffer = ByteBufferUtil.DefaultAllocator.Buffer(); protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List output) { int inputLen = input.ReadableBytes; byte[] inputBytes = new byte[inputLen]; input.GetBytes(0, inputBytes); log.Debug(context.Channel.RemoteAddress.ToString() + " Received: " + Encoding.Default.GetString(inputBytes)); #region 处理心跳 int pingReqBegin = ByteUtil.Search(inputBytes, ByteUtil.PING_REQ); if (pingReqBegin > -1) { int pingReqEnd = pingReqBegin + ByteUtil.PING_REQ_LEN; if (pingReqEnd == ByteUtil.PING_REQ_LEN && inputLen == ByteUtil.PING_REQ_LEN) { inputBytes = new byte[0]; } else { inputBytes = inputBytes.Skip(0).Take(pingReqBegin).Concat(inputBytes.Skip(pingReqEnd)).ToArray(); log.Debug(context.Channel.RemoteAddress.ToString() + " Received after skip ping:" + Encoding.Default.GetString(inputBytes)); } output.Add(createResponseMessage()); //context.WriteAndFlushAsync(ByteBufferUtil.DefaultAllocator.Buffer().WriteBytes(ByteUtil.PING_RSP)); } #endregion while (inputBytes.Length > 0) { //log.Info("PipleLineId = " + "本次处理 : " + Encoding.Default.GetString(bytes)); int stxIndex = Array.IndexOf(inputBytes, ByteUtil.STX); int etxIndex = Array.IndexOf(inputBytes, ByteUtil.ETX); //缓存没有 if (byteBuffer.ReadableBytes == 0) { //有头 if (stxIndex != -1) { //有尾 if (etxIndex != -1) { int shoIndex = Array.IndexOf(inputBytes, ByteUtil.SOH); byte[] bodyLen = inputBytes.Skip(stxIndex + 1).Take(shoIndex - stxIndex - 1).ToArray(); byte[] message = inputBytes.Skip(shoIndex + 1).Take(etxIndex - shoIndex - 1).ToArray(); output.Add(createDecodeMessage(bodyLen, message)); inputBytes = inputBytes.Skip(etxIndex + 1).ToArray(); } else { //如果stxIndex 头部前有报文,丢弃前面的报文 byteBuffer.WriteBytes(inputBytes.Skip(stxIndex).ToArray()); int tmpLen = byteBuffer.ReadableBytes; byte[] tmpBytes = new byte[tmpLen]; byteBuffer.GetBytes(0, tmpBytes); //log.Info("PipleLineId = " + PipleLineId + "缓存1 : " + Encoding.Default.GetString(tmpBytes)); inputBytes = new byte[0]; } } else { //缓存为空,报文没头,跳过 inputBytes = new byte[0]; } } else { if (stxIndex != -1) { if (stxIndex < etxIndex || etxIndex == -1) { int tmpLen = byteBuffer.ReadableBytes; byte[] tmpBytes = new byte[tmpLen]; byteBuffer.GetBytes(0, tmpBytes); //log.Info("PipleLineId = " + PipleLineId + "丢弃 : " + Encoding.Default.GetString(tmpBytes)); inputBytes = inputBytes.Skip(stxIndex).ToArray(); byteBuffer.Clear(); continue; } } if (etxIndex != -1) { byteBuffer.WriteBytes(inputBytes.Take(etxIndex + 1).ToArray()); int tmpLen = byteBuffer.ReadableBytes; byte[] tmpBytes = new byte[tmpLen]; byteBuffer.GetBytes(0, tmpBytes); //log.Info("PipleLineId = " + PipleLineId + "缓存3 : " + Encoding.Default.GetString(tmpBytes)); int buffLen = byteBuffer.ReadableBytes; byte[] currentBytes = new byte[buffLen]; byteBuffer.GetBytes(0, currentBytes); int shoIndex = Array.IndexOf(currentBytes, ByteUtil.SOH); byte[] bodyLen = currentBytes.Skip(1).Take(shoIndex - 1).ToArray(); //byte[] message = currentBytes.Skip(shoIndex + 1).SkipLast(1).ToArray(); byte[] message = currentBytes.Skip(shoIndex + 1).Take(currentBytes.Length - shoIndex - 2).ToArray(); output.Add(createDecodeMessage(bodyLen, message)); byteBuffer.Clear(); inputBytes = inputBytes.Skip(etxIndex + 1).ToArray(); } else { //断包 进缓冲 byteBuffer.WriteBytes(inputBytes); inputBytes = new byte[0]; int tmpLen = byteBuffer.ReadableBytes; byte[] tmpBytes = new byte[tmpLen]; byteBuffer.GetBytes(0, tmpBytes); //log.Info("PipleLineId = " + PipleLineId + "缓存2 : " + Encoding.Default.GetString(tmpBytes)); } } } input.SkipBytes(inputLen); } private DecodeMessage createDecodeMessage(byte[] length, byte[] message) { DecodeMessage decodeMessage = new DecodeMessage(); decodeMessage.Length = int.Parse(Encoding.Default.GetString(length)); int messageIdStartIndex = Array.IndexOf(message, ByteUtil.DQTS); int messageIdEndIndex = Array.IndexOf(message, ByteUtil.DQTS, messageIdStartIndex + 1); decodeMessage.MessageId = message.Skip(messageIdStartIndex + 1).Take(messageIdEndIndex - messageIdStartIndex - 1).ToArray(); decodeMessage.MessageBody = message; return decodeMessage; } private DecodeMessage createResponseMessage() { DecodeMessage decodeMessage = new DecodeMessage(); decodeMessage.Length = 8; decodeMessage.MessageId = ByteUtil.MessageId_RSP; decodeMessage.MessageBody = ByteUtil.PING_RSP; return decodeMessage; } } } ```