• unity同步实现机制分享: 看了很多文章,也在自己的游戏中亲自编写,整理并实现了一套同步方案,废话不多,直接分享。 7.网络重播:这块是同步必须做的,我们需要把服务器发过来的每一,以及随机种子,...

    unity帧同步实现机制分享:

    看了很多文章,也在自己的游戏中亲自编写,整理并实现了一套帧同步方案,废话不多,直接分享。

    7.网络重播:这块是帧同步必须做的,我们需要把服务器发过来的每一帧,以及随机种子,英雄数据记录下来,用于重播,有人问为什么要重播呢?因为重播是查不同步的重要手段,你大概率都不知道你干什么导致不同步了,那么你可以利用你的保存的数据用每个玩家重播一遍,看看差异在哪里,从而定位到导致不同步的代码。

    8.定点数的使用:既然要同步,那必须使用定点数了,因为浮点数的计算结果不一定一样,使用定点数,可以保证结果的一致性。我们使用的是long 保存,把浮点数左移14位的做法,然后重载所有云算符。

     

    7.重播:检查不同步的重要手段, 这个其实就是保存的所有战斗的初始化数据跟所有的操作,然后客户端依次执行,得到同步log,比对以查问题。

    8.定点数:使用long保存,把浮点数左移14位,再重载操作符,这个网上应该有很多,就不啰嗦了。

    1. 传输协议:战斗使用冗余UDP(我们暂时用的是3),什么是冗余呢,就是说UDP协议不保证客户端肯定能收到,那么每次下发一帧的时候,会携带前2帧的数据,这样,即便丢包,也能保证大概率客户端稳定。
    2. 超时TCP重传机制,网络非常不好的情况,那么,就会造成丢帧较多,即便冗余也可能缺帧
       //如果客户端的帧和服务器的帧误差小于Margin的帧数,则为安全边际,
       //否则需要重新从服务器请求丢失的帧信息
       ret = (serverFrame - clientFrame) <= nFrames; 

       

    3. 位置计算:所有的逻辑帧,只计算起始位置,朝向,速度,表现帧计算具体:
    4.  ///距离大于1.5s的位移,暂时先直接拉扯
              ///如果x/z小于一帧位移距离,也直接拉扯
              ///其余情况 匀速移动,减速,加速情况后续会加
              private Vector3 LerpPos(Vector3 start, Vector3 end, float intepoation)
              {
                  Vector3 pos = new Vector3(start.x, start.y, start.z);
      
                  //pve加速模式 或 pvp落后很多帧情况下,直接移动到对应位置
                  if (GameLogic.TestMode || FrameDriver.Current.GetIsRunFast())
                  {
                      pos.x = end.x;
                      pos.z = end.z;
                  }
      
                  if (Vector3.Distance(start, end) > Speed * 1.5f)
                  {
                      pos.x = end.x;
                      pos.z = end.z;
                  }
                  else
                  {
                      float dis = Mathf.Sqrt((end.x - start.x) * (end.x - start.x) + (end.z - start.z) * (end.z - start.z));
                      if (dis != 0)
                      {
                          float moveX = (end.x - start.x) / dis * Speed * intepoation;
                          float moveZ = (end.z - start.z) / dis * Speed * intepoation;
                          if (Mathf.Abs(start.x - end.x) > Mathf.Abs(moveX))
                          {
                              pos.x = start.x + moveX;
                          }
                          else
                          {
                              pos.x = end.x;
                          }
      
                          if (Mathf.Abs(start.z - end.z) > Mathf.Abs(moveZ))
                          {
                              pos.z = start.z + moveZ;
                          }
                          else
                          {
                              pos.z = end.z;
                          }
                      }
                  }
                  var positionY = GameScene.Current.mMapHypsogramInfo.GetHeight_View(pos.x, pos.z) + L2VRiseHigh;
                  pos.y = Mathf.Lerp(start.y, positionY, LERP_POS_T);
                  return pos;
              }
              private int LerpToAngle(ref float angle, int dstAngle, float intepoation)
              {
                  var delta = Tr.DeltaAngle((int)angle, dstAngle);
                  if (Abs(delta) > 5F)
                  {
                      var d = Mathf.Lerp(0, delta, 0.4F * Time.deltaTime * 60);
                      angle += d;
                  }
                  else
                  {
                      angle = dstAngle;
                  }
                  return delta;
              }

      上面是所有可控制物体的位置以及角度运算,位置采用匀速,角度采用每次update40%的方式插值。

    5. 帧缓存算法:我这里提供一下最简单的一种,当然也是最稳定有效的算法,网上有很多可查的jitterbuffer算法,实现起来,理解起来都需要费电李琦,不如来个简单的,- -,好用就行。方法很简单,我缓存最近100帧的延迟(到客户端解析的时候算时间,这个才是真正能驱动游戏的时间),然后排序,去掉几个最大最小值,然后去5个最大最小值的差平均为jitter值,直接上代码:
       using System;
      using System.Text;
      
      namespace YHP1
      {
          /// <summary>
          /// 网络抖动值
          /// </summary>
          public class NetJitterTime
          {
              private const int MAX_CALC_DELAY_LEN = 100;
              private const int AVERAGE_NUM = 5;
              private const int DISCARD_NUM = 2;
              private float[] delayArr = new float[MAX_CALC_DELAY_LEN];
              private float[] sortedDelayArr = new float[MAX_CALC_DELAY_LEN];
              private bool isInitialed = false;
              private int oldestIndex = 0;
      
      
              public void Update(float aDelay)
              {
                  if (!isInitialed)
                  {
                      Initial(aDelay);
                      return;
                  }
                  InsertOneDelay(aDelay);
              }
      
              //按照升序,初始化所有的延迟
              private void Initial(float aDelay)
              {
                  sortedDelayArr[oldestIndex] = aDelay;
                  delayArr[oldestIndex] = aDelay;
                  int currentindex = oldestIndex - 1;
                  while (currentindex >= 0)
                  {
                      if (sortedDelayArr[currentindex] > sortedDelayArr[currentindex + 1])
                      {
                          Swap(ref sortedDelayArr[currentindex], ref sortedDelayArr[currentindex + 1]);
                          currentindex--;
                      }
                      else
                      {
                          break;
                      }
                  }
                  oldestIndex++;
                  if (oldestIndex >= MAX_CALC_DELAY_LEN)
                  {
                      isInitialed = true;
                      oldestIndex = 0;
                  }
              }
      
              public void InsertOneDelay(float aDelay)
              {
                  oldestIndex = oldestIndex % MAX_CALC_DELAY_LEN;
                  float deletDelayVale = delayArr[oldestIndex];
                  delayArr[oldestIndex] = aDelay;
                  oldestIndex++;
                  int sortIndex = BinarySearch(sortedDelayArr, 0, MAX_CALC_DELAY_LEN - 1, deletDelayVale);
                  if (sortIndex < 0 || sortIndex >= MAX_CALC_DELAY_LEN)
                  {
                      Logger.LogError("InsertOneDelay sortIndex is a invalid number : " + sortIndex);
                      return;
                  }
                  sortedDelayArr[sortIndex] = aDelay;
      
                  //替换最老数据以后使用插入排序
                  if (sortIndex - 1 > 0 && sortedDelayArr[sortIndex - 1] > sortedDelayArr[sortIndex])
                  {
                      sortIndex--;
                      while (sortIndex >= 0)
                      {
                          if (sortedDelayArr[sortIndex] > sortedDelayArr[sortIndex + 1])
                          {
                              Swap(ref sortedDelayArr[sortIndex], ref sortedDelayArr[sortIndex + 1]);
                              sortIndex--;
                          }
                          else
                          {
                              break;
                          }
                      }
                  }
                  else if (sortIndex + 1 < MAX_CALC_DELAY_LEN && sortedDelayArr[sortIndex + 1] < sortedDelayArr[sortIndex])
                  {
                      sortIndex++;
                      while (sortIndex < MAX_CALC_DELAY_LEN)
                      {
                          if (sortedDelayArr[sortIndex] < sortedDelayArr[sortIndex - 1])
                          {
                              Swap(ref sortedDelayArr[sortIndex], ref sortedDelayArr[sortIndex - 1]);
                              sortIndex++;
                          }
                          else
                          {
                              break;
                          }
                      }
                  }
              }
      
      
              private float GetJitterTime()
              {
                  if (isInitialed)
                  {
                      float minSum = 0;
                      for (int i = DISCARD_NUM; i < AVERAGE_NUM + DISCARD_NUM; i++)
                      {
                          minSum += sortedDelayArr[i];
                      }
                      float maxSum = 0;
                      for (int i = MAX_CALC_DELAY_LEN - 1 - DISCARD_NUM; i >= MAX_CALC_DELAY_LEN - AVERAGE_NUM - DISCARD_NUM; i--)
                      {
                          maxSum += sortedDelayArr[i];
                      }
                      float jitter = (maxSum - minSum) / AVERAGE_NUM;
                      if (jitter < GameConfig.FrameIntervalL / 2)
                      {
                          return 0;
                      }
                      return jitter;
                  }
                  else
                  {
                      return 0;
                  }
              }
      
              public int GetJitterLength()
              {
                  float jitterTime = GetJitterTime();
                  int result = UnityEngine.Mathf.CeilToInt(jitterTime / (GameConfig.FrameIntervalL * 1000));
                  return result;
              }
      
              public static void Swap<T>(ref T a, ref T b)
              {
                  T t = a;
                  a = b;
                  b = t;
              }
      
              //二分搜索找到排序中的最老的值的index
              public static int BinarySearch(float[] arr, int low, int high, float key)
              {
                  int mid = (low + high) / 2;
                  if (low > high)
                      return -1;
                  else
                  {
                      if (arr[mid] == key)
                          return mid;
                      else if (arr[mid] > key)
                          return BinarySearch(arr, low, mid - 1, key);
                      else
                          return BinarySearch(arr, mid + 1, high, key);
                  }
              }
      
              public void Clear()
              {
                  isInitialed = false;
                  oldestIndex = 0;
              }
      
          }
      }
      

      上面代码很多参数可调,根据实际项目决定,基本可以实现客户端流畅运行,不过会适当增加自己的操作延迟,其实就是用延迟换流畅。

    6.  帧执行逻辑:为了应付网络抖动,可能缺帧或者多帧的情况,客户端要流畅,至少保证每逻辑帧能有一帧的数据。我的处理是,没有帧的时候就等,因为项目原因,做不到预测回滚,或者说比较麻烦,因此这里暂时就是没有帧的时候会小卡一下,多帧的时候,采用每update一次执行一帧,当执行到jitterbuffer保存的值的时候,那么就每逻辑帧时间执行一帧(1/16s)。
              protected void ExecutesLogicFrames(float delta)
              {
                  if (mFrameBuffer.LastFrame == 0)
                      return;
      
                  int jitterLength = NetworkSyncManager.Instance.GetJitterLength();
      
                  if (ShouldRunFast(jitterLength))
                  {
                      int K = 0;
                      while (true)
                      {
                          var L = mFrameBuffer.NextFrame();
                          if (!ExecuteEachFrame(L))
                          {
                              return;
                          }
                          ++K;
                          if (K > 50)
                          {
                              break;//一次最多跑50帧,避免主线程卡死
                          }
                      }
                  }
                  else
                  {
                      mTickTime += delta;
                      if (mTickTime < GameConfig.FrameIntervalL && mFrameBuffer.IsConsumeBuffer(jitterLength))
                      {
                          return;
                      }
      
                      var L = mFrameBuffer.NextFrame();
                      if (L != null)
                      {
                          mTickTime = 0;
                          ExecuteEachFrame(L);
                      }
                  }
              }

      哈,看到快速模式了对吧,这个是下面要讲的。

    7. 10.快速模式呢,就是追帧,跟大家差的太远了,正常执行已经很难追上了:

    8.         private bool ShouldRunFast(int jitterLength)
              {
                  if (FastMode)
                      return true;
      
                  if (mIsRunFast)
                  {
                      if (mFrameBuffer.CacheFrameNum() <= jitterLength)
                          mIsRunFast = false;
                  }
                  else
                  {
                      if (FrameInfo.ServerFrame - FrameInfo.CurFrame > 90)
                          mIsRunFast = true;
                  }
      
                  return mIsRunFast;
              }

      这里简单给了一下是否快速执行,

    9.  

    展开全文
  • 在C/S端编程的时候,经常要在C端S端之间传数据时自定义一下报文的头,如果是在C/C++,封装头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没有提供直接从struct到byte[]的转换...

      在C/S端编程的时候,经常要在C端和S端之间传数据时自定义一下报文的帧头,如果是在C/C++,封装帧头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没有提供直接从struct到byte[]的转换,这个时候就需要用到Marshal等非托管的方法了。

    自定义帧


    我们可以在C#中写出如下代码:

     1 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1,Size = 12)]
     2 [Serializable()]
     3  public struct DatagramHeaderFrame
     4 {
     5             // MessageType类型:
     6             public MessageType MsgType;
     7 
     8             //一个四个字节的特征码
     9             public uint FeatureCode;
    10 
    11             //用于标识报文的长度,用于校验
    12             public int MessageLength;
    13   }

      首先我们说明一下,StructLayout是一个用于管理struct的布局特性,

    CharSet指示在默认情况下是否应将类中的字符串数据字段作为 LPWSTR 或 LPSTR 进行封送处理;

    Pack控制类或结构的数据字段在内存中的对齐方式。

    Size指示类或结构的绝对大小。

      LayoutKind是布局的类型,这个枚举有三个值:

    Auto运行库自动为非托管内存中的对象的成员选择适当的布局。 使用此枚举成员定义的对象不能在托管代码的外部公开。 尝试这样做将引发异常。

    Explicit在未管理内存中的每一个对象成员的精确位置是被显式控制的,服从于 StructLayoutAttribute. Pack 字段的设置。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。在MSDN文档中为我们展示了下面的一个例子:

    1 [StructLayout(LayoutKind.Explicit)]
    2 public struct Rect 
    3 {
    4    [FieldOffset(0)] public int left;
    5    [FieldOffset(4)] public int top;
    6    [FieldOffset(8)] public int right;
    7    [FieldOffset(12)] public int bottom;
    8 }   

    Sequential对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。 这些成员根据在 StructLayoutAttribute. Pack 中指定的封装进行布局,并且可以是不连续的。

      Serialzable是一个用于指示对象是否能序列化的特性,简单的来说序列化的用处就是,比如我客户端给你传一定的数据,这个数据不是标准的类型的时候,如果我们不使用序列化的时候,我们就要把数据的每个部分都转成二进制然后存储,就很麻烦,而且容易出错,所以C#就提供了这样一个机制给程序员简单使用并且转成二进制(或者其他格式)来使用(使用formatter),而且当一个对象没有被标明为可序列化的时候,我们使用formatter的时候会报错,具体怎么使用请查看MSDN文档即可。比如文档有这样一段代码:

    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Soap;
    //using System.Runtime.Serialization.Formatters.Binary;
    
    public class Test {
       public static void Main()  {
    
          //Creates a new TestSimpleObject object.
          TestSimpleObject obj = new TestSimpleObject();
    
          Console.WriteLine("Before serialization the object contains: ");
          obj.Print();
    
          //Opens a file and serializes the object into it in binary format.
          Stream stream = File.Open("data.xml", FileMode.Create);
          SoapFormatter formatter = new SoapFormatter();
    
          //BinaryFormatter formatter = new BinaryFormatter();
    
          formatter.Serialize(stream, obj);
          stream.Close();
    
          //Empties obj.
          obj = null;
    
          //Opens file "data.xml" and deserializes the object from it.
          stream = File.Open("data.xml", FileMode.Open);
          formatter = new SoapFormatter();
    
          //formatter = new BinaryFormatter();
    
          obj = (TestSimpleObject)formatter.Deserialize(stream);
          stream.Close();
    
          Console.WriteLine("");
          Console.WriteLine("After deserialization the object contains: ");
          obj.Print();
       }
    }
    
    
    // A test object that needs to be serialized.
    [Serializable()]        
    public class TestSimpleObject  {
    
        public int member1;
        public string member2;
        public string member3;
        public double member4;
    
        // A field that is not serialized.
        [NonSerialized()] public string member5; 
    
        public TestSimpleObject() {
    
            member1 = 11;
            member2 = "hello";
            member3 = "hello";
            member4 = 3.14159265;
            member5 = "hello world!";
        }
    
    
        public void Print() {
    
            Console.WriteLine("member1 = '{0}'", member1);
            Console.WriteLine("member2 = '{0}'", member2);
            Console.WriteLine("member3 = '{0}'", member3);
            Console.WriteLine("member4 = '{0}'", member4);
            Console.WriteLine("member5 = '{0}'", member5);
        }
    }

     

    封装和解析


      要解析一个struct并且把他变成bytes,需要用到非托管的方法:

            public static byte[] StructToBytes(object structObj)
            {
                int size = Marshal.SizeOf(structObj);
                IntPtr buffer = Marshal.AllocHGlobal(size);
                try
                {
                    Marshal.StructureToPtr(structObj, buffer, false);
                    byte[] bytes = new byte[size];
                    Marshal.Copy(buffer, bytes, 0, size);
                    return bytes;
                }
                finally
                {
                    Marshal.FreeHGlobal(buffer);
                }
            }

    要把bytes变成sturct,反过来即可:

     1         public static object BytesToStruct(byte[] bytes, Type strcutType)
     2         {
     3             int size = Marshal.SizeOf(strcutType);
     4             IntPtr buffer = Marshal.AllocHGlobal(size);
     5             try
     6             {
     7                 Marshal.Copy(bytes, 0, buffer, size);
     8                 return Marshal.PtrToStructure(buffer, strcutType);
     9             }
    10             finally
    11             {
    12                 Marshal.FreeHGlobal(buffer);
    13             }
    14         }

    下面演示一下在socket上传输报文+帧头:

     1         public static byte[] PackingMessageToBytes
     2             (MessageType messageType, uint featureCode, int messageLength, byte[] msgBytes)
     3         {
     4             DatagramHeaderFrame frame = new DatagramHeaderFrame();
     5             frame.MsgType = messageType;
     6             frame.FeatureCode = featureCode;
     7             frame.MessageLength = messageLength;
     8 
     9             byte[] header = StructToBytes(frame);
    10 
    11             byte[] datagram = new byte[header.Length + msgBytes.Length];
    12             header.CopyTo(datagram, 0);
    13             msgBytes.CopyTo(datagram, FrameSize);
    14 
    15             return datagram;
    16         }
    17 
    18         /// <summary>
    19         /// 封装消息和报文
    20         /// </summary>
    21         /// <param name="headerFrame">报文帧头</param>
    22         /// <param name="message">报文</param>
    23         /// <param name="encoding">编码器</param>
    24         /// <returns></returns>
    25         public static byte[] PackingMessageToBytes
    26             (DatagramHeaderFrame headerFrame, byte[] msgBytes)
    27         {
    28             byte[] header = StructToBytes(headerFrame);
    29 
    30             byte[] datagram = new byte[header.Length + msgBytes.Length];
    31             header.CopyTo(datagram, 0);
    32             msgBytes.CopyTo(datagram, FrameSize);
    33 
    34             return datagram;
    

     

    接收端代码节选:

    1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
    2 headerFrame.MsgType = messageType;
    3 headerFrame.MessageLength = bytes.Length;
    4 byte[] datagram = PackingMessageToBytes(headerFrame, bytes);
    5 
    6 GetStream().BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, this);

     

    发送端代码节选:

     1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();
     2 byte[] datagramBytes = new byte[0];
     3 
     4 byte[] datagramBuffer = (byte[])ar.AsyncState;
     5 byte[] recievedBytes = new byte[numberOfRecievedBytes];
     6 
     7 Buffer.BlockCopy(datagramBuffer, 0, recievedBytes, 0, numberOfRecievedBytes);
     8                 PrasePacking(recievedBytes, numberOfRecievedBytes, ref headerFrame, ref datagramBytes);
     9 
    10 GetStream().BeginRead(datagramBuffer, 0, datagramBuffer.Length, HandleDatagramReceived, datagramBuffer);

     

    C++端解析和封装的代码(用Qt写的)

     1 QByteArray TcpHeaderFrameHelper::bindHeaderAndDatagram(const TcpHeaderFrame &header,const QByteArray &realDataBytes)
     2 {
     3     QByteArray byteArray, temp;
     4     temp.resize(4);
     5     
     6     unsignedToQByteArray((unsigned)header.messageType, temp);
     7     byteArray += temp;
     8     
     9     unsignedToQByteArray((unsigned)header.featureCode, temp);
    10     byteArray += temp;
    11     
    12     unsignedToQByteArray((unsigned)header.messageLength, temp);
    13     byteArray += temp;
    14     
    15     byteArray +=realDataBytes;
    16     return byteArray;
    17 }
    18 
    19 void TcpHeaderFrameHelper::praseHeaderAndDatagram(const QByteArray &dataBytes,TcpHeaderFrame &headerFrame,QByteArray &realDataBytes)
    20 {
    21     realDataBytes.resize(dataBytes.size() - TcpHeaderFrameHelper::headerSize);
    22     headerFrame.messageType = qByteArrayToInt(dataBytes.left(4));
    23     headerFrame.featureCode = qByteArrayToInt(dataBytes.mid(4,4));
    24     headerFrame.messageLength = qByteArrayToInt(dataBytes.mid(8,4));
    25     
    26     realDataBytes = dataBytes.mid(12, dataBytes.size());
    27 }
    28 
    29 unsigned TcpHeaderFrameHelper::qByteArrayToInt(QByteArray bytes)
    30 {
    31     int result = 0;
    32     result |= ((bytes[0]) & 0x000000ff); 
    33     result |= ((bytes[1] << 8) & 0x0000ff00); 
    34     result |= ((bytes[2] << 16) & 0x00ff0000); 
    35     result |= ((bytes[3] << 24) & 0xff000000); 
    36     
    37     return result;
    38 }
    39 
    40 void TcpHeaderFrameHelper::unsignedToQByteArray(unsigned num, QByteArray &bytes)
    41 {
    42     bytes.resize(4);
    43     bytes[0] = (char)( 0x000000ff & num);
    44     bytes[1] = (char)((0x0000ff00 & (num)) >> 8);
    45     bytes[2] = (char)((0x00ff0000 & (num)) >> 16);
    46     bytes[3] = (char)((0xff000000 & (num)) >> 24);
    47 }

     

    转载于:https://www.cnblogs.com/Philip-Tell-Truth/p/6149539.html

    展开全文
  • TCP/IP协议(1):各层协议帧格式 一、 1、OSI与TCP/IP对应:  TCP/IP各层功能: 链路层:包括操作系统的设备驱动程序计算机的网卡,提供底层传输服务。 网络层:为数据选择路由,在众多计算机网络设备组成的...

    TCP/IP协议(1):各层协议帧格式
    一、

    1、OSI与TCP/IP对应:

    在这里插入图片描述
     TCP/IP各层功能:

    链路层:包括操作系统的设备驱动程序和计算机的网卡,提供底层传输服务。
    网络层:为数据选择路由,在众多计算机和网络设备组成的网络中选择一条传输路线进行传输。
    传输层:提供两台主机端对端的通信服务,进行传输控制。
    应用层:复制应用程序的特定处理。

    3、协议帧封装

    当发送数据时,对于一帧数据,每一层会封装上自己的头,而收数据时去掉上层的头,如图:

    在这里插入图片描述

    二、

    1、Ethernet帧格式

    在这里插入图片描述

    可以看到以太网帧头包含三部分:目的地址、源地址、类型。以太网帧类型有IP数据报(0800)、ARP请求应答报文(0806)、RARP请求应答报文(8035)。

    2、IP数据帧格式

    在这里插入图片描述
    IP首部一般为20字节,除非含有选项字段,根据IP头中的“8位协议”字段,数据选项可以分为TCP报文、UDP报文、ICMP报文等。

    3、TCP数据帧格式

    在这里插入图片描述

    4、UDP数据帧格式
    在这里插入图片描述

    5、ICMP报文格式

    在这里插入图片描述

    展开全文
  • 在下位机通讯中,经常使用带尾的协议。此时需要将采集上来的数据进行识别处理。 代码示例 string factMessage = &amp;quot;Extension methods have all the capabilities of regular static methods.&...

    描述

    在下位机通讯中,经常使用带帧头帧尾的协议。此时需要将采集上来的数据进行识别处理。

    代码示例

    string factMessage = "Extension methods have all the capabilities of regular static methods.";
    
    // Write the string and include the quotation marks.
    Console.WriteLine($"\"{factMessage}\"");
    
    // This search returns the substring between two strings, so 
    // the first index is moved to the character just after the first string.
    int first = factMessage.IndexOf("methods") + "methods".Length;
    int last = factMessage.LastIndexOf("methods");
    string str2 = factMessage.Substring(first, last - first);
    Console.WriteLine($"Substring between \"methods\" and \"methods\": '{str2}'");
    

    运行结果

    "Extension methods have all the capabilities of regular static methods."
    Substring between "methods" and "methods": ' have all the capabilities of regular static '
    
    展开全文
  • 串口通信之协议格式 缓存收到的所有数据,找到一条完整数据,分析数据,界面通知。   信令格式: 同步头 + 数据正文 + 同步尾 同步头 + 命令字 + 数据长度 + 数据正文 + 同步尾 同步头 + 地址...

     

    转自:https://www.cnblogs.com/fyhui/articles/2477733.html

     

    串口通信之协议格式

    缓存收到的所有数据,找到一条完整数据,分析数据,界面通知。

     

    信令格式:

    同步头 + 数据正文 + 同步尾

    同步头 + 命令字 + 数据长度 + 数据正文 + 同步尾

    同步头 + 地址码 + 命令字 + 数据长度 + 数据正文 + 同步尾

     

    同步头 + 数据正文 + 校验码

    同步头 + 命令字 + 数据长度 + 数据正文 + 校验码

    同步头 + 地址码 + 命令字 + 数据长度 + 数据正文 + 校验码

     

      文本格式可以直观的定义回车换行是协议的结尾,所以我们可以省略数据长度,增加协议尾。即:协议头 + 数据 + 校验 + 数据尾

      

      一般情况下,同步头2B,地址码1B,命令字1B,数据长度1B,数据正文nB,同步尾2B,校验码2B。

     

      信令的格式为了3个目的:a>符合业务需求;b>方便识别一条完整的数据;c>进一步验证数据,如有需要进行数据还原。

             地址码、命令字为了目的a;

             同步头、数据长度、同步尾、校验码为了目的b;

             校验码为了目的c。

     

    建议:

    1>    尽量采用数据内容不会出现的字符作为同步头尾。比如数据用BCD码,则同步头可以用A-E,如5E;

    2>    如果同步头尾字符可能会在数据正文出现,则可能要用转义符,当然同步头+数据长度+同步尾3个条件同时成立的正文数据出现概率应该非             常非常低;

    3>    如果数据较短,请使用校验码进一步验证数据的正确性;

    4>    如果正确性要求特别高,请采用校验码还原可能的错误数据,如果校验码不能完成数据还原,请重发信令请求以保证正确。

    展开全文
  • c# 解码(未完成)

    2019-06-30 12:00:39
    在dsp学习过程中,dsp使用UDP协议把图像通过xvid编码发送到PC机,虽然有基于MFC开发的上位机可以使用,但是还是想自己写一个,就开始作死…… 既然下位机有enc_main,那么PC端肯定有对应的dec_main,找了找好像都...
  • c# MODBUS协议 上位机

    2018-08-11 16:22:33
    C#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构: 采用定时器(Timer控件)为时间片。 串口采用serialPort1_DataReceived中断接收,并进行MO
  • 在上位机与PLC通信中,为了更好的校验发送与接收的数据的准确性,一般都会加一位校验位,校验码的算法有多种,校验序列码FCS就是其中的一种。  校验序列码FCS ( Frame Check Sequences)是为提高通信的可靠性...
  • Modbus 协议命令

    2018-12-27 09:54:20
    一、 Rtu 命令(注意CRC校验的高低字节,实际算法有可能有区别) 1、 读取继电器状态(单个多个) 发送命令:(地址从0开始计算) 设备地址 功能码 地址H 地址L ...
  • 通信数据报采用了简单的封装结构,此协议基于SLIP(Serial Line IP,串行线路IP协议,具体内容参见RFC1055),但加入一个校验(Checksum)字节,其值为除首尾END字符外的所有字符字节按位。 这里面遇到了两
  • 测试 发送数据的参数,设置温度频率时延输出功率,,这些数据组成一并且发送,然后接收,,,
  • 要对某种协议进行编解码操作,就必须知道协议的基本定义,首先我们来看一下 CJ/T188 的数据定义(协议定义),了解请求数据与响应数据的基本结构。 1.1 CJ/T188 水表通讯协议 请求: 字节 值 描述 0 0x68 ...
  • H264 ipb详解

    2019-10-17 20:47:23
    一、前言 H264是新一代的编码标准,以高压缩高质量支持多种网络的流...所以对于一段变化不大图像画面,我们可以先编码出一个完整的图像A,随后的B就不编码全部图像,只写入与A的差别,这样B的大小就只有...
  • 同步这部分比较复杂,细枝末节有很多优化点,也有一些不同的优化方向,根据不同项目类型、对操作手感的要求、联机玩家的个数等,会有不同的难点痛点。不同的优化方向,优化手法的差异,可能导致一些争论。并且,...
  • CAN2.0B 数据详解

    2017-10-27 09:12:59
    CAN的类型分为数据、遥控、错误、过载以及间空隙,本文将对数据结构展开说明: CAN2.0协议分为A版本B版本,A版本协议为11位标识符(标准),B版本在兼容11位ID标识符的同时,向上扩展到29位...
  •  //接收数据并显示函数:可用:测试的接收数据为:0x01 0x02(镇头) 0x02(长度) 0x03(尾、即校验位) 0x34 0x05(数据):这是硬件发送过来的测试数据  private void DataReceivedHandler(object sender, ...
  • 数据传输使用的是一系列数据,出于安全考虑避免网络截获,客户端发送的数据必须进行掩码处理后才能发送到服务器,不论是否是在TLS安全协议上都要进行掩码处理。服务器如果没有收到掩码处理的数据时应该关闭...
  • c#can解析多报文

    2019-06-18 16:46:43
    这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、...
  • 同步状态同步

    2019-01-11 05:52:49
    本文简单讨论了同步状态同步。 同步 什么是同步:同步常被RTS(即时战略)游戏常采用。在游戏中同步的是玩家的操作指令,操作指令包含当前的索引。一般的流程是客户端上传操作到服务器, 服务器收到...
  • 通信协议里面的结构  在网络上发送消息会有许多问题值得注意,所以现在纯文本的协议越来越火。 首先在c/C++中 使用struct会有对其格式,而且这种对其格式是编译器决定的。 所以你不能确定你定义的struct会...
1 2 3 4 5 ... 20
收藏数 4,753
精华内容 1,901