精华内容
下载资源
问答
  • 一个简单的自定义通信协议(socket)
                   

    转自:http://vtrtbb.javaeye.com/blog/849336

    这是转自javaeye的一篇文章,作者是vtrtbb。

    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。

     

     

    一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。

     

    先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体

     

    包类型 byte 型

    包长度 int 型

    消息体 byte[]

     

    包总长度为 1 + 4 +  消息体.getBytes().length

     

    发包方法如下:

    private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {          byte[] bytes= msg.getBytes();          int totalLen = 1 + 4 + bytes.length;                                  out.writeByte(1);          out.writeInt(totalLen);          out.write(bytes);          out.flush();      }   

     

    客户端发送消息类为:

    import java.io.DataOutputStream;  import java.io.IOException;  import java.io.InputStream;  import java.io.OutputStream;  import java.net.Socket;  import java.net.UnknownHostException;  import java.util.Scanner;    public class MsgClient {        private DataOutputStream outs;            public static void main(String[] args) {          try {              MsgClient client = new MsgClient();              client.connServer("127.0.0.1", 9292);                     } catch (UnknownHostException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          }      }                  private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {          byte[] bytes= msg.getBytes();          int totalLen = 1 + 4 + bytes.length;          out.writeByte(1);          out.writeInt(totalLen);          out.write(bytes);          out.flush();      }               public void connServer(String ip,int port) throws UnknownHostException, IOException {          Socket client = new Socket(ip,port);          InputStream in = client.getInputStream();          OutputStream out = client.getOutputStream();          outs = new DataOutputStream(out);          while(true) {              Scanner scaner = new Scanner(System.in);              sendTextMsg(outs, "测试消");          }             }   

     

    服务端接收类为:

    import java.io.DataInputStream;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.InputStream;  import java.net.ServerSocket;  import java.net.Socket;    public class MsgServer {      public static void main(String[] args) {                  try {              MsgServer server = new MsgServer();              server.setUpServer(9090);          } catch (IOException e) {              e.printStackTrace();          }      }            public void setUpServer(int port) throws IOException {          ServerSocket server = new ServerSocket(port);          while(true) {              Socket client = server.accept();              System.out.println("客户端IP:"+client.getRemoteSocketAddress());              processMesage(client);          }      }            private void processMesage(Socket client) throws IOException {          InputStream ins = client.getInputStream();                DataInputStream dins = new DataInputStream(ins);          //服务端解包过程          while(true) {              int totalLen = dins.readInt();              byte flag = dins.readByte();              System.out.println("接收消息类型"+flag);                            byte[] data = new byte[totalLen - 4 - 1];              dins.readFully(data);              String msg = new String(data);              System.out.println("发来的内容是:"+msg);            }      }  }   

     

     

    这样就基本完成了,但实际还有好多问题,比如说服务端用如何用多线程服务来完成客户端的请求已提高效率,如果是NIO方式怎么来实现?多个消息类型时候怎么抽象?这些都没有考虑

     

    另外有两个开源的框架不错,一个是apache  mina 还有个是netty ,有机会试试。

     

     

    另一篇文章中叙述:

    ------------------

    TCP Socket协议定义

    ------------------

    本文从这里开始,主要介绍TCP的socket编程。

    新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个"Helloworld",接收方收到了这个字符串,就“精通”了Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:

     

    1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello   +   world,一个分段就是一个包(Package),这个就是分包问题

     

    2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了hello+world,而接收方可能一次就接受了helloworld.

     

    3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。

     

    4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可以使用以下代码去测试:

    代码
             public   void  test()
            {
                 int  myInt  =   1 ;
                 byte [] bytes  =   new   byte [ 1024 ];
                BinaryWriter writer  =   new  BinaryWriter( new  MemoryStream(bytes));
                writer.Write(myInt);
                writer.Write( " j " );
                writer.Close();
            }

     

     

    尽管socket环境如此恶劣,但是TCP的链接也至少保证了:

    • 包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
    • 如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不能保证helloworld和下一个命令之间没有其他的byte。

     

    因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。具体如下:

     

    1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议的时候要特别小心。

     

    2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。

     

    3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手工压入byte操作)。

     

    一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:

       173173173               8                  1         2

    |------包头------|----包体长度----|--------包体--------|

    这个数据包就是4个整型,总长度 = 4*4  = 16。

     

               

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • 如何自定义一个通信协议

    万次阅读 多人点赞 2018-01-21 14:07:27
    借鉴简单的OSI和TCP/IP通信模型来讨论如何自定义一个适应自己的通信协议 前言 经典的OSI七层模型 1TCPIP模型解析 11整体介绍 22数据链路层 23网络层 24传输层 25应用层 不同类型的通信方式的总结 1从上述模型...

    借鉴简单的OSI和TCP/IP通信模型来讨论如何自定义一个适应自己的通信协议

    1.前言

    在物联网的通信中,很多地方需要自定义协议。但考虑到平时工作中接触到的自定义协议,都或多或少存在一些问题和缺陷。所以想借鉴之前看过的书上的知识以及一些国际标准的协议,来简单谈谈如何设计一个自定义通信协议,并且通信方都遵守这个协议,并如何谈谈根据自己的需求对协议进行简化。
    除此之外,考虑到后面可能不太会接触这一块,所以可能是一次集中性的大清理,后面可能会整理一下底层(单片机)上一般如何处理数据接受发送的问题,大概会以lwip为例。
    收集的资料会有些不全,如果有什么比较特别的协议可以在评论指出。下面也是按我自己对这个协议理解的角度来讲。为了节省篇幅着重点,中间会省去一些基本的概念。

    这里会省去一些通信的基本概念,着重分析协议,分析每层的目的和协议组成,不分析原理。

    这里补充一点,也是突然上次有人和我说我才想起来,除了我们用相互规定的数据流来表示内容外,还有一种协议的表示方法:可以用字符串解析,这种可以参考HTTP没什么太多需要解释的。只不过字符串的内容和方式通信方自定义。不过个人认为这种方式对嵌入式来说没太多必要,太浪费有限的带宽,同时解析速度也很慢。不过就算是HTTP的下面层也是TCP,IP数据链路之类的,依然是要通过数据流的层次上进行包装,只是对应用层的解析上比较直观罢了。

    2.经典的OSI七层模型

    这里首先以经典的OSI七层模型介绍整个通信从物理层到应用层的的层次分层。分层有助于分离出不同层的处理任务和职责,不同层之间通过接口由下一层向上一层提供服务。此外,分层设计的也会过分的模块化,使处理变得繁重。
    下面是模型的图示:

    这里写图片描述

    (盗了一张图,各层次的简单功能都写在旁边了。附上出处地址:http://blog.sina.com.cn/s/blog_4770ef020101i1wd.html)

    这个模型分的很细,中间增加了很多保证速度,数据可靠性,安全等的机制。在我们应用的时候,根据需求不同,有着不同的侧重点。有的时候需要的只是一些简单的数据传输功能,有的时候我们借助了硬件模块(模块自身已经实现了协议),或者需要实现协议栈,每种还需要根据结合情况,制定的协议有所不同。

    从很多通信模型中都不难看出,其实基本都逃不出这个模型,他们很多都是根据自己的需要在模型的基础上做些删减和简化。

    2.1.TCP/IP模型解析

    2.1.1.整体介绍

    TCP/IP是将OSI模型进行部分的简化,将7层模型压缩为4层模型。不过两者的侧重点不太一样。OSI是列出了通信的基本协议,以及具体如何分层,划分职责。TCP/IP则侧重具体协议的实现。

    对应分层如下图所示。图片出处:http://www.lxway.com/925941824.htm。

    TCP/IP协议整体的感觉大概就是下面这个样子。每一层会在数据的前面加一个首部,中间包含了那一层对应的控制信息。(这里补充一下:数据可以用包,帧,数据包,段,消息来描述。)

    这里写图片描述

    这里写图片描述

    2.2.2.数据链路层

    首先是把物理层和数据链路层合并,定义了通信媒介互联设备间的传输规范。这一层往往对应的是设备驱动程序和网络接口。物理层实现数据0,1转化,数据链路层把数据集合成帧,同时需要保证电子线路上的可靠性。这层还是比较注重数据的传输方式,网络的拓扑结构等物理方面的处理。
    除去线路上的物理连接外,还使用了MAC地址来识别不同的连接对象。因此这层的数据首部中会加入MAC地址信息。通常MAC地址是有设备制造商决定的,并且是唯一的(虚拟机的那种??除外)。

    以太网是数据链路中最著名的一种。这里以以太的数据链路层的协议规定示范,其他的不同协议的数据链路层规范也都不一样,如下图。

    这里写图片描述

    可以看出这层协议的首部主要有MAC地址和控制以太类型。在以太帧格式前面有一段前导码。由0,1交替组成,用来做前导码(8字节),以一个SFD的域(11)作为前导码的结尾。
    以太帧的头部一共有14字节,目的MAC和源MAC,再加2字节的上层协议类型(决定下一层网络层接受的数据类型)。帧尾的FCS为帧校验序列,用来排除硬件噪声的干扰导致的错误

    2.2.3.网络层

    网络层单独对应一层,和OSI的网络层相同。这层主要负责将数据送到正确的目的地址。这里的地址是IP地址,每个连接入网络的设备都需要有个IP地址,IP地址由网络号和主机号组成。这样看上去就可以按地区分配(这里是有ISP和区域网分配出来的),并且比较固定和集中,而不会像MAC地址一样无规律分布。这样加快了数据传输双方寻址的速度(对于很大的大网络更加明显)。这里有个关系,IP寻址是靠路由控制表,同时IP和MAC地址由地址转发表控制。

    这层可以不管底层是用那种数据链路进行通信的(可以跨越不同的数据链路,比如WIFI和网线的混连),只负责路由实现节点间的路由通信,同时也不会涉及重发机制,这部分是传输层需要做的工作。

    对于下一层的数据链路层来说,网络层同时可以屏蔽不同数据链路的传输问题,这里使用了IP分片处理方式。IP包会根据不同链路进行重组。这应该都是路由器的工作,从而使上层忽略掉数据链路层的影响。而对于上层来说,IP提供的是无连接的服务,这样虽然会有很多冗余,但会提高速度。如果要保证可靠性,可以靠上的传输层来保证。

    这层的核心是IP协议,它是基于地址转发分包数据的,跟在数据链路层首部的后面,协议格式具体如图所示:此外还有些辅助性的测试协议比如ICMP,地址解析协议ARP等。
    这里写图片描述

    前面有4bit为版本号,标记着这个是什么数据,比如说是IPV4还是IPV6。接着4bit表示首部长度用来表示首部大小,以4字节为基本单位,默认为5,即20字节。区分服务用一个字节来表示,说明服务质量。总长度表示IP首部与数据部分结合起来的总字数。标识表(2字节),标志(3bit)片偏移,用于分片重组;生存时间表示以秒为单位当前包在网络中都应该的生存期限。协议表示IP首部的下一个首部隶属于哪个协议。首部校验和用来保证首部的正确性。源地址和目的地址为通信双方的ip地址。

    2.2.4.传输层

    传输层也是单独对应,负责建立连接断开,保证传输的可靠性。这层包括2类很著名的传输层协议:TCP和UDP。
    这里以TCP为例,TCP是一种面向连接的通信协议,它比UDP复杂。这里TCP主要实现数据传输时的控制功能,确认应答,重发机制,连接管理等策略来保证通信的可靠性的。
    分析它的格式,具体如下:

    这里写图片描述

    源/目的端口号就是我们平时TCP/IP所说的端口号。序列号是用来确认发送位置的,每发送一次自增1。确认序列号指下一次应该收到数据的序列号,发送端收到这个序列号以后可以认为前面的数据都被正常接收。数据偏移表示首部的长度。控制位有8为,每一位都有特殊的控制标志位。如下图:

    窗口大小是用来表示确认应答号所示位置开始能接受数据的大小。通常不允许超过测出的大小。校验和来表示数据正确性(注意这里的校验和,是用于防止数据链路层以上的干扰而做的)。紧急指针在应用中处理,一般在暂停中断通行或者中断通信情况下使用。选项用于提高TCP的传输能力。

    这里写图片描述

    SYN建立连接;FIN断开连接;RST连接出现异常必须强制断开;ACK为应答;ECE通知网络拥塞,CWR通知缩小拥塞窗口。

    2.2.5.应用层

    应用层包括了原来的应用层,表示层,会话层,中间有很多是为了实现某种特定的应用而制定的协议,所以对于具体的应用有不同的处理。这里以http举例。
    HTTP属于TCP/IP协议族中的常见的应用服务。HTTP主要应用在Web中。Web中有三个重要的概念:URI,HTML,HTTP。

    URI用于识别资源的具体位置。就是我们打开在浏览器地址栏输入的地址。URI可以和HTTP或者HTTPS组合来访问Web,就像输入网页一样。

    HTTP是在接入要访问的网页是开始工作的,它的传输层协议采用TCP连接,端口号为80,然后再在这个连接上进行请求应答发送数据报文。HTTP提供了一些命令,像常见的有GET,HEAD,POST等。

    HTML是WWW通用的数据表现协议,是一种标记语言,类似于OSI的表示层。可以通过标签的方式将浏览器中的内容显示出阿里,或者设置等。

    3.不同类型的通信方式的总结

    3.1.从上述模型/协议中获得的经验

    TCP/IP模型采用分层设计,从OSI模型中演变过来的。但是TCP/IP相对来说比较复杂,毕竟我们上个网用的就是TCP/IP。但是有一说一,讲讲每个层的特点。
    先说数据链路层吧。数据链路这层是为了规范各种不同的数据链路。一个是拓扑结构,一个是物理连接的不同。物理连接不同也会造成数据速度,单次传输长度间的差异。从协议的格式上也可以看出这层实现了解决了。
    1.硬件地址的表示MAC,解决多机通信的问题。
    2.消除了数据链路的电磁干扰,保证数据正确性。
    3.标记了当前数据链路的类型,使上层方便处理。
    4.有段前导码来识别帧的起始。

    接着是网络层,这层主要的任务是数据路由,另外对上层隔离(消除)了数据链路层差别的影响。通过IP地址来辅助数据发送或接受。如果没有那么多的节点,其实这里也不需要,如果节点较少用简单的地址也能解决。下面是协议中的针对改层的一些重点:
    1.标记IP协议相关信息,在这层可以进行不同的处理。
    2.对不同的数据链路的数据进行分组操作。
    3.转化成IP地址
    4.记录了数据的长度。

    传输层主要保证连接,确保传输的可靠性。但也根据不同的需要,实现有了不同的策略,像分出TCP,UDP等。但这层总体的目的还是控制数据传输为主。协议中包括了一些:
    1.处理端口号。
    2.控制不同的传输层协议。
    3.连接,断开,应答,重发等控制。
    3.流量控制策略。

    应用层其实就是我们最后需要给我们应用量身制定的协议,中间可以按自己的需要给加一些特定的内容,甚至数据加密等。

    3.2.几种的情况的通信协议

    3.2.1.简单的数据传输功能

    这种方式一般是指数据量较少,而且大多数为一对一,不进行组网的方式。而且选择的是一些简单的通信方式,像是USB,串口,485,I2C总线之类的。这个时候协议相当于直接和硬件接触了。这时候保证数据不受电磁干扰就非常必要了,所以可以在数据的最后加入数据正确性校验。

    3.2.2.借助了硬件模块/协议栈的通信

    这种方式类似于,比如我们用了硬件集成协议栈的模块,我们只需要对模块进行配置,剩下就通过借助上一种方式中提到的总线,传输数据给模块或者从模块读取数据,模块会自动帮你发送和接收。但因为和模块的通信还是有硬件连接的过程,所以还是需要保证硬件的干扰,但节点和节点间的一些问题有的模块已经忙你处理了。

    3.2.3.直接使用socket通信

    直接使用socket通信其实和上面的差不多,只不过不需要通过硬件模块连接这部分,所以协议的内容上也可以功能加自由。但是如果其他端有涉及到硬件的,一些硬件保护措施还是必不可少,除非在集中通信的地方做次协议转换。

    3.3.其他补充

    上述只是说要注意写什么,并不是绝对的,大多数时候还是要根据自己的应用需求来定协议,如果处理速度,数据量跟得上,可以加入足够的预留,保证除真实数据意外的部分不变,这样方便后续拓展协议的兼容。预留部分可以层次分开来,但不处理,以后需要拓展的时候再加进来,而不影响整体。但是如果是一次性的开发,或者保证不会出现那么多的,可以适当裁剪来减少负担。

    另外TCP/IP中经常出现一些协议的版本的分辨标记,如果不是专门去做协议栈,个人认为这部分可以适当忽略。因为我们的通信数据并没有那么复杂,而且大多数时候我们都会用socket,TCP/IP的方式进行通信了,实际上已经少了很多工作,所以也就是在其一些高级平台上开发并不需要你太了解底层。这样就开发的就相当于只用开发应用层协议,定义一个自己开发功能相关的协议,而可以忽略了一些TCP/IP那些协议帮你做过的工作。如果是纯底层开始那考虑的东西就比较多了。

    3.3.1.参考协议

    借鉴前面的一些协议,不过这里的协议其实是应用层的协议。前面也说了,所以中间省略了一些特定策略的,没必要做TCP/IP协议族里面那些那么复杂,但是真如果有需要还是按目的添加一些参考的协议,增加对应层的策略来解决,但还是按照上下两层提供接口的原则。

    大概简单画了一下参考的组成,主要还是针对总线的单片机通信。其实中间有很多也可以是不需要的,具体见自己的应用需求,进行删减和增加。

    这里写图片描述

    前导码可以按借鉴一下以太帧的前导码,主要的是那个01交替,可以容易区分受到的电磁干扰。同时后面可以加校验,验证整段数据。

    地址这里仿数据链路层和网络层的地址以及传输层的端口,为了确定接受发送双方。但如果是一对一可以省略,同时如果没特殊需要,源地址可以省略。同时把地址放前面,如果不是自己对应的地址数据不处理也可以加快速度。

    数据长度参照网络层,来标识数据长度,也可以用来验证数据是否有丢失。

    序号和确认应答,这里主要参照网络层,防止有重复发送或者数据丢失的问题,数据丢失好理解,数据重复发送,有时候在有些超时重发或者接受错误重发很常见。

    标识可以区分数据的不同类型,如果没有那么多控制功能,可以把功能和应答号都混在一起。

    数据包分块的问题主要针对的是数据长度过大,单帧发送不能够完全放下的问题。现在是把数据包分块的放在了后面。但其实如果按照通信的原则这个还是属于数据链路层的应该解决的问题(不同的通信方式对应的MTU不一致),应该放在前面,这样可以让应用层不要关注底层的事情。单如果在一些嵌入式的简单应用中,往往收发双方的数据链路是一致(如果不一致,估计由模块里的协议栈已经帮你统一成一致了),但有的时候数据长度还是大于一次发送的长度(或者那个协议栈没做之类的处理),这时候就需要将发送的数据拆包和合包了。这时候放在这个地方,相当于让应用层来处理这些事情。结合标识,可以得到完整的数据。其实也是为了防止有的标准一次发送数据太小的问题。

    数据校验的话可以看自己的需求,如果觉得数据传输很稳定可以不用加= =,不过这里的数据校验也是包括几个层次的。比如(硬件干扰/路由器自身的问题之类)的错误,此外还有一些恶意篡改(如果数据重要),可以在应用HASH校验验证数据的完整性等。同时也可以使用一些加密手段防止数据内容被截取。

    注:如果考虑可拓展性,省略的部分可以用保留来填充,增加灵活性。

    3.3.2.透明传输

    另外补充一下透明传输的知识。我之前忘记在那本书上看到了,我只记得自己截了图,下面放上图。

    这里写图片描述

    透明传输可以把关键部分从你的数据中排除,这样更加清晰了数据的界限,这样也可以减轻程序的负担。之前记得有人问过我,数据校验应该是部分数据还是全部数据。如果用了透明传输那么结果就很明显了:肯定是先校验全部数据,在进行处理数据。

    3.3.3.程序处理

    协议处理在程序中的处理表现也是分层次的。以3.3.1中的自定义协议为例,像前导码,地址,长度可以归为一层,这一层可以保证数据内容的正确性和接受地址的正确性;确认序列号,确认应答则控制数据传输过程中的可靠性。后面的标识,数据分包和数据内容则可以归为应用的内容。

    3.3.4.协议的内容

    协议的内容是由自己的应用需要传输的数据自定义的。定义的自由度比较高,只要通信双方按相同的规定装数据和取数据就可以了。

    展开全文
  • 自定义串口通信协议,如何实现?

    千次阅读 2021-05-30 00:17:57
    关注+星标公众号,不错过精彩内容作者 | strongerHuang微信公众号|嵌入式专栏有一些初学者总觉得通信协议是一个很复杂的知识,把它想的很高深,导致不知道该怎么学。同时,偶尔有...

    关注+星标公众,不错过精彩内容

    作者 | strongerHuang

    微信公众号 | 嵌入式专栏

    有一些初学者总觉得通信协议是一个很复杂的知识,把它想的很高深,导致不知道该怎么学。

    同时,偶尔有读者问关于串口自定义通信协议相关的问题,今天就来写写串口通信协议,并不是你想想中的那么难?

    1什么通信协议?

    通信协议不难理解,就是两个(或多个)设备之间进行通信,必须要遵循的一种协议。

    百度百科的解释:

    通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。

    相应该有很多读者都买过一些基于串口通信的模块,市面上很多基于串口通信的模块都是自定义通信协议,有的比较简单,有的相对复杂一点。

    举一个很简单的串口通信协议的例子:比如只传输一个温度值,只有三个字节的通信协议:

    帧头温度值帧尾
    5A一字节数值3B

    这种看起来是不是很简单?它也是一种通信协议。

    只是说这种通信协议应用的场合相对比较简单(一对一两个设备之间),同时,它存在很多弊端。

    2过于简单的通信协议引发的问题

    上面那种只有三个字节的通信协议,相信大家都看明白了。虽然它也能通信,也能传输数据,但它存在一系列的问题。

    比如:多个设备连接在一条总线(比如485)上,怎么判断传输给谁?(没有设备信息)

    还比如:处于一个干扰环境,你能保障传输数据正确吗?(没有校验信息)

    再比如:我想传输多个不确定长度的数据,该怎么办?(没有长度信息)。

    上面这一系列问题,相信做过自定义通信的朋友都了解。

    所以,在通信协议里面要约定更多的“协议信息”,这样才能保证通信的完整。

    3通信协议常见内容

    基于串口的通信协议通常不能太复杂,因为串口通信速率、抗干扰能力以及其他各方面原因,相对于TCP/IP这种通信协议,是一种很轻量级的通信协议。

    所以,基于串口的通信,除了一些通用的通信协议(比如:Modubs、MAVLink)之外,很多时候,工程师都会根据自己项目情况,自定义通信协议。

    下面简单描述下常见自定义通信协议的一些要点内容。

    (这是一些常见的协议内容,可能不同情况,其协议内容不同)

    1.帧头

    帧头,就是一帧通信数据的开头。

    有的通信协议帧头只有一个,有的有两个,比如:5A、A5作为帧头。

    2.设备地址/类型

    设备地址或者设备类型,通常是用于多种设备之间,为了方便区分不同设备。

    这种情况,需要在协议或者附录中要描述各种设备类型信息,方便开发者编码查询。

    当然,有些固定的两种设备之间通信,可能没有这个选项。

    3.命令/指令

    命令/指令比较常见,一般是不同的操作,用不同的命令来区分。

    举例:温度:0x01;湿度:0x02;

    4.命令类型/功能码

    这个选项对命令进一步补充。比如:读、写操作。

    举例:读Flash:0x01; 写Flash:0x02;

    5.数据长度

    数据长度这个选项,可能有的协议会把该选项提到前面设备地址位置,把命令这些信息算在“长度”里面。

    这个主要是方便协议(接收)解析的时候,统计接收数据长度。

    比如:有时候传输一个有效数据,有时候要传输多个有效数据,甚至传输一个数组的数据。这个时候,传输的一帧数据就是不定长数据,就必须要有数据长度来约束。

    有的长度是一个字节,其范围:0x01 ~ 0xFF,有的可能要求一次性传输更多,就用两个字节表示,其范围0x0001 ~ 0xFFFFF。

    当然,有的通信长度是固定的长度(比如固定只传输、温度、湿度这两个数据),其协议可能没有这个选项。

    6.数据

    数据就不用描述了,就是你传输的实实在在的数据,比如温度:25℃。

    7.帧尾

    有些协议可能没有帧尾,这个应该是可有可无的一个选项。

    8.校验码

    校验码是一个比较重要的内容,一般正规一点的通信协议都有这个选项,原因很简单,通信很容易受到干扰,或者其他原因,导致传输数据出错。

    如果有校验码,就能比较有效避免数据传输出错的的情况。

    校验码的方式有很多,校验和、CRC校验算是比较常见的,用于自定义协议中的校验方式。

    还有一点,有的协议可能把校验码放在倒数第二,帧尾放在最后位置。

    4通信协议代码实现

    自定义通信协议,代码实现的方式有很多种,怎么说呢,“条条大路通罗马”你只需要按照你协议要写实现代码就行。

    当然,实现的同时,需要考虑你项目实际情况,比如通信数据比较多,要用消息队列(FIFO),还比如,如果协议复杂,最好封装结构体等。

    下面分享一些以前用到的代码,可能没有描述更多细节,但一些思想可以借鉴。

    1.消息数据发送

    a.通过串口直接发送每一个字节

    这种对于新手来说都能理解,这里分享一个之前DGUS串口屏的例子:

    #define DGUS_FRAME_HEAD1          0xA5                     //DGUS屏帧头1
    #define DGUS_FRAME_HEAD2          0x5A                     //DGUS屏帧头2
    
    
    #define DGUS_CMD_W_REG            0x80                     //DGUS写寄存器指令
    #define DGUS_CMD_R_REG            0x81                     //DGUS读寄存器指令
    #define DGUS_CMD_W_DATA           0x82                     //DGUS写数据指令
    #define DGUS_CMD_R_DATA           0x83                     //DGUS读数据指令
    #define DGUS_CMD_W_CURVE          0x85                     //DGUS写曲线指令
    
    
    /* DGUS寄存器地址 */
    #define DGUS_REG_VERSION          0x00                     //DGUS版本
    #define DGUS_REG_LED_NOW          0x01                     //LED背光亮度
    #define DGUS_REG_BZ_TIME          0x02                     //蜂鸣器时长
    #define DGUS_REG_PIC_ID           0x03                     //显示页面ID
    #define DGUS_REG_TP_FLAG          0x05                     //触摸坐标更新标志
    #define DGUS_REG_TP_STATUS        0x06                     //坐标状态
    #define DGUS_REG_TP_POSITION      0x07                     //坐标位置
    #define DGUS_REG_TPC_ENABLE       0x0B                     //触控使能
    #define DGUS_REG_RTC_NOW          0x20                     //当前RTCS
    
    
    //往DGDS屏指定寄存器写一字节数据
    void DGUS_REG_WriteWord(uint8_t RegAddr, uint16_t Data)
    {
      DGUS_SendByte(DGUS_FRAME_HEAD1);
      DGUS_SendByte(DGUS_FRAME_HEAD2);
      DGUS_SendByte(0x04);
    
    
      DGUS_SendByte(DGUS_CMD_W_REG);                 //指令
      DGUS_SendByte(RegAddr);                        //地址
    
    
      DGUS_SendByte((uint8_t)(Data>>8));             //数据
      DGUS_SendByte((uint8_t)(Data&0xFF));
    }
    
    
    //往DGDS屏指定地址写一字节数据
    void DGUS_DATA_WriteWord(uint16_t DataAddr, uint16_t Data)
    {
      DGUS_SendByte(DGUS_FRAME_HEAD1);
      DGUS_SendByte(DGUS_FRAME_HEAD2);
      DGUS_SendByte(0x05);
    
    
      DGUS_SendByte(DGUS_CMD_W_DATA);                //指令
    
    
      DGUS_SendByte((uint8_t)(DataAddr>>8));         //地址
      DGUS_SendByte((uint8_t)(DataAddr&0xFF));
    
    
      DGUS_SendByte((uint8_t)(Data>>8));             //数据
      DGUS_SendByte((uint8_t)(Data&0xFF));
    }
    

    b.通过消息队列发送

    在上面基础上,用一个buf装下消息,然后“打包”到消息队列,通过消息队列的方式(FIFO)发送出去。

    static uint8_t  sDGUS_SendBuf[DGUS_PACKAGE_LEN];
    
    
    //往DGDS屏指定寄存器写一字节数据
    void DGUS_REG_WriteWord(uint8_t RegAddr, uint16_t Data)
    {
      sDGUS_SendBuf[0] = DGUS_FRAME_HEAD1;           //帧头
      sDGUS_SendBuf[1] = DGUS_FRAME_HEAD2;
      sDGUS_SendBuf[2] = 0x06;                       //长度
      sDGUS_SendBuf[3] = DGUS_CMD_W_CTRL;            //指令
      sDGUS_SendBuf[4] = RegAddr;                    //地址
      sDGUS_SendBuf[5] = (uint8_t)(Data>>8);         //数据
      sDGUS_SendBuf[6] = (uint8_t)(Data&0xFF);
    
    
      DGUS_CRC16(&sDGUS_SendBuf[3], sDGUS_SendBuf[2] - 2, &sDGUS_CRC_H, &sDGUS_CRC_L);
      sDGUS_SendBuf[7] = sDGUS_CRC_H;                //校验
      sDGUS_SendBuf[8] = sDGUS_CRC_L;
    
    
      DGUSSend_Packet_ToQueue(sDGUS_SendBuf, sDGUS_SendBuf[2] + 3);
    }
    
    
    //往DGDS屏指定地址写一字节数据
    void DGUS_DATA_WriteWord(uint16_t DataAddr, uint16_t Data)
    {
      sDGUS_SendBuf[0] = DGUS_FRAME_HEAD1;           //帧头
      sDGUS_SendBuf[1] = DGUS_FRAME_HEAD2;
      sDGUS_SendBuf[2] = 0x07;                       //长度
      sDGUS_SendBuf[3] = DGUS_CMD_W_DATA;            //指令
      sDGUS_SendBuf[4] = (uint8_t)(DataAddr>>8);     //地址
      sDGUS_SendBuf[5] = (uint8_t)(DataAddr&0xFF);
      sDGUS_SendBuf[6] = (uint8_t)(Data>>8);         //数据
      sDGUS_SendBuf[7] = (uint8_t)(Data&0xFF);
    
    
      DGUS_CRC16(&sDGUS_SendBuf[3], sDGUS_SendBuf[2] - 2, &sDGUS_CRC_H, &sDGUS_CRC_L);
      sDGUS_SendBuf[8] = sDGUS_CRC_H;                //校验
      sDGUS_SendBuf[9] = sDGUS_CRC_L;
    
    
      DGUSSend_Packet_ToQueue(sDGUS_SendBuf, sDGUS_SendBuf[2] + 3);
    }
    

    c.用“结构体代替数组SendBuf”方式

    结构体对数组更方便引用,也方便管理,所以,结构体方式相比数组buf更高级,也更实用。(当然,如果成员比较多,如果用临时变量方式也会导致占用过多堆栈的情况)

    比如:

    typedef struct
    {
      uint8_t  Head1;                 //帧头1
      uint8_t  Head2;                 //帧头2
      uint8_t  Len;                   //长度
      uint8_t  Cmd;                   //命令
      uint8_t  Data[DGUS_DATA_LEN];   //数据
      uint16_t CRC16;                 //CRC校验
    }DGUS_PACKAGE_TypeDef;
    

    d.其他更多

    串口发送数据的方式有很多,比如用DMA的方式替代消息队列的方式。

    2.消息数据接收

    串口消息接收,通常串口中断接收的方式居多,当然,也有很少情况用轮询的方式接收数据。

    a.常规中断接收

    还是以DGUS串口屏为例,描述一种简单又常见的中断接收方式:

    void DGUS_ISRHandler(uint8_t Data)
    {
      static uint8_t sDgus_RxNum = 0;                //数量
      static uint8_t sDgus_RxBuf[DGUS_PACKAGE_LEN];
      static portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    
    
      sDgus_RxBuf[gDGUS_RxCnt] = Data;
      gDGUS_RxCnt++;
    
    
      /* 判断帧头 */
      if(sDgus_RxBuf[0] != DGUS_FRAME_HEAD1)       //接收到帧头1
      {
        gDGUS_RxCnt = 0;
        return;
      }
      if((2 == gDGUS_RxCnt) && (sDgus_RxBuf[1] != DGUS_FRAME_HEAD2))
      {
        gDGUS_RxCnt = 0;
        return;
      }
    
    
      /* 确定一帧数据长度 */
      if(gDGUS_RxCnt == 3)
      {
        sDgus_RxNum = sDgus_RxBuf[2] + 3;
      }
    
    
      /* 接收完一帧数据 */
      if((6 <= gDGUS_RxCnt) && (sDgus_RxNum <= gDGUS_RxCnt))
      {
        gDGUS_RxCnt = 0;
    
    
        if(xDGUSRcvQueue != NULL)                    //解析成功, 加入队列
        {
          xQueueSendFromISR(xDGUSRcvQueue, &sDgus_RxBuf[0], &xHigherPriorityTaskWoken);
          portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
        }
      }
    }
    

    b.增加超时检测

    接收数据有可能存在接收了一半,中断因为某种原因中断了,这时候,超时检测也很有必要。

    比如:用多余的MCU定时器做一个超时计数的处理,接收到一个数据,开始计时,超过1ms没有接收到下一个数据,就丢掉这一包(前面接收的)数据。

    static void DGUS_TimingAndUpdate(uint16_t Nms)
    {
      sDGUSTiming_Nms_Num = Nms;
      TIM_SetCounter(DGUS_TIM, 0);                   //设置计数值为0
      TIM_Cmd(DGUS_TIM, ENABLE);                     //启动定时器
    }
    
    
    void DGUS_COM_IRQHandler(void)
    {
      if((DGUS_COM->SR & USART_FLAG_RXNE) == USART_FLAG_RXNE)
      {
        DGUS_TimingAndUpdate(5);                     //更新定时(防止超时)
        DGUS_ISRHandler((uint8_t)USART_ReceiveData(DGUS_COM));
      }
    }
    

    c.更多

    接收和发送一样,实现方法有很多种,比如接收同样也可以用结构体方式。但有一点,都需要结合你实际需求来编码。

    5最后

    以上自定义协议内容仅供参考,最终用哪些、占用几个字节都与你实际需求有关。

    基于串口的自定义通信协议,有千差万别,比如:MCU处理能力、设备多少、通信内容等都与你自定义协议有关。

    有的可能只需要很简单的通信协议就能满足要求。有的可能需要更复杂的协议才能满足。

    最后强调两点:

    1.以上举例并不是完整的代码(有些细节没有描述出来),主要是供大家学习这种编程思想,或者实现方式。

    2.一份好的通信协议代码,必定有一定容错处理,比如:发送完成检测、接收超时检测、数据出错检测等等。所以说,以上代码并不是完整的代码。

    ------------ END ------------

    后台回复『嵌入式软件设计与开发』『通信』阅读更多相关文章。

    欢迎关注我的公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。

    欢迎关注我的视频号:

    点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

    展开全文
  • QT tcp通信之自定义通信协议

    千次阅读 2018-09-20 09:52:14
    转载自:https://blog.csdn.net/omg_orange/article/details/74075104,以下通信协议为转载,本人没有亲自测试正确否。 在已经实现socket通信的前提下,设计了如下的通信格式: 假设cmd定义如下: #ifndef CMD...

    转载自:https://blog.csdn.net/omg_orange/article/details/74075104,以下通信协议为转载,本人没有亲自测试正确否。

    在已经实现socket通信的前提下,设计了如下的通信格式:

    这里写图片描述

    假设cmd定义如下:

    #ifndef CMD_H
    #define CMD_H
    
    //服务器------->客户端
    #define     Connet_Success         0x0F00   //连接成功应答包
    #define     Login_answer           0x0F01   //登陆结果(也是QString 的一种)
    #define     QString_send           0x0F02   //发送字符串
    #define     QFile_send             0x0F03   //发送文件
    #define     Struct_send            0x0F03   //发送结构体
    
    //客户端------->服务器
    #define     Login                  0x0D00   //客户端登陆
    
    #endif // CMD_H

    一些变量说明:

    展开全文
  • Qt TCP之自定义通信协议

    千次阅读 2017-07-01 17:10:30
    在已经实现socket通信的前提下,设计了如下的通信格式:假设cmd定义如下:#ifndef CMD_H #define CMD_H//服务器------->客户端 #define Connet_Success 0x0F00 //连接成功应答包 #define Login_answer 0x0F01 //登陆...
  • 一个简单的自定义通信协议 socket

    千次阅读 2018-11-05 12:38:55
    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。     一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。   先来说说数据包的定义,我这里是包头+内容 ...
  • 自定义通信协议举例

    2008-12-08 18:47:00
    自定义通信协议 自定义通信协议功能介绍: 1、数据传送:机器A需要向机器B发送9个开关量,2个数字量(0-31之间的位置,0-120之间的速度值)。 2、发送方9个开关量以9个多选框的状态为开关数据,接收方图形...
  • 上期,转载了一篇自定义通信协议的制定以及使用的一篇帖子,个人觉得相当不错。 但是就目前而已,谷歌的PB使用要更广泛一些,不管是哪个方面。 对于PB的话,也就是Google Protocol Buffer。假如您在网上搜索,...
  • 本文主要介绍注册表的概念与其相关根项的功能,以及浏览器如何通过连接调用自定义协议并与客户端进行数据通信。文中讲及如何通过C#程序、手动修改、安装项目等不同方式对注册表进行修改。其中通过安装项目对注册表...
  • 上次分享的《分享一个很酷的上位机软件》中,有如下协议:有位读者朋友问数据为什么要按这样的格式来发。其实这是个自定义协议,这是上位机开发者定义的一个数据交互协议。我们下位机往伏特加上位机发...
  • 一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。   先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体   包...
  • 自定义Socket通信协议

    2015-01-15 22:57:45
    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。 一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。 先来说说数据包的定义,我这里是包头+内容 组成的:...
  • 自定义网络协议总结

    千次阅读 2019-08-04 09:15:21
    目前网络通信常用的应用层协议已经有了HTTPS和HTTP,为什么还要自定义协议呢? 个人认为主要有两个好处: 更加安全。 目前网络上最常见的攻击手段就是抓HTTP/HTTPS包,如果自定义网络协议,抓包工作无法使用,能...
  • 浅谈自定义通讯协议——TLV

    千次阅读 2019-03-28 11:14:19
           最近在做实验室的温度上报项目时,需要用到TLV来...在具体了解TLV协议之前,我们先对通讯协议做一个简单的介绍。        所谓通讯协...
  • 用qt C++写的udp简单demo。带ui界面。简单易懂。代码量少。
  • 上次分享的《分享一个很酷的上位机软件》中,有如下协议:有位读者朋友问数据为什么要按这样的格式来发。其实这是个自定义协议,...
  • 通信教程07_通信协议

    千次阅读 2020-01-07 18:30:05
    关注、星标公众号,不错过精彩内容本教程由作者strongerHuang于2020年01月原创发布。标签:串口、通信协议版权所有:禁止商用申明:该文档仅供个人学习使用,转载请公众号联...
  • https://blog.csdn.net/qq_34719168/article/details/87915825 博文项目源代码
  • TCP通讯socket自定义协议的实现

    千次阅读 2017-03-17 18:46:29
    转发(JAVA):... 一个简单的自定义通信协议(socket),http://blog.csdn.net/lincyang/article/details/6109076  http://vtrtbb.iteye.com/blog/849336  socket编程,实先
  • 在单片机刚开始学习的时候,串口通信是经常要用到的,但是实际产品中串口通信是需要通信协议的。好多人不明白为什么要用通信协议,如何定义通信协议,带通信协议的程序要怎么写。今天就来说一下如何串口通信协议是...
  • 我也聊聊串口通信协议:用户层通信协议的编制

    万次阅读 多人点赞 2016-10-26 23:19:57
    1.闲话闲聊 最重要的写在前面:用户层通信协议就是数据包格式!!! ...没有进行串口通讯实践的朋友可能会想:...串口通信协议一般可以从两个角度来思考:底层通信协议和用户层协议。底层协议一般有计算机硬件提供商
  • 通信协议格式

    千次阅读 2019-03-26 14:32:03
    其中sioconnector基于socket.io,使用json作为其通信格式,hybridconnector则用于tcp/websocket的通信,它底层使用的是二进制协议。虽然在sioconnector中,socket.io的实现很好,对于超时、握手等都做了处理,并且...
  • 例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,101
精华内容 17,640
关键字:

自定义通信协议例子