分享

mosquitto服务端 + paho客户端的通信试验

 风雪夜归人_95 2014-10-29
          在上一篇文章中,笔者介绍了如何在linux环境下搭建mosquitto的服务端,并且在终端中成功订阅、发布主题,并完成信息的交流(详见《mosquitto在Linux环境下的部署》)。但是,这样毕竟只是试验环境,你也不能认为mosquitto每一个客户端都是一个linux环境下的终端。
         这里的客户端主要有两种:一种是Android平台,一种是MCU开发板。对于前者,实际上非常简单,只需加入wmqtt.jar文件,即可做相应实验,此处不详细介绍。而对于后者,则困难较大。因为对于这一部分的介绍比较少。笔者在搜索MQTT C库的时候,发现了一个叫做paho的网站,它里面定义有MQTTC库,并且介绍中称它就是以开源服务器mosquitto为实例的,这样就不用担心它与我们搭建的服务端有什么兼容性问题。它的源码被发步在github之上。paho除了有纯C语言编写的客户端,还有基于C++、符合Posix标准的C语言、java、python、go语言的客户端,选择还是比较多的。可以在官网根据需要自由选择。
        一般地,一个MQTT客户端运行的环境必须要满足三个条件支持TCP/IP协议的socket编程、多线程处理和内存分配。而paho还提供了更简单的客户端实验,即不考虑多线程的处理和内存分配。(至少要支持socket编程吧,不然怎么通信)。
    笔者在最开始是尝试在mosquitto提供的源码中根据其发布、订阅主题的相关函数移植到MCU上,但是在移植了2天后,不得不停止。因为mosquitto提供的源码中涉及到了多线程方面的内容,使得移植的代价太大。最后找到了paho作为替代品。paho提供了最精简的库文件来支持MQTT协议,不用依赖于

步骤1:获取源码
 地址:git://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.embedded-c.git
        仍在linux环境下,通过“git clone”命令将源码拷贝到本地。你会在本地找到一个名为“org.eclipse.paho.mqtt.embedded-c”的文件夹。打开这个文件夹,除去和linux下编译相关的makefile文件和一些提示的信息外,最重要的就是三个文件夹:MQTTClient、MQTTClient-C、MQTTPacket。笔者大致看了一下三个文件夹的结构,发现,每一个文件夹所对应环境或语言有所不同,且每个文件夹下都有可以直接运行的例子,可以参考学习。
       例如:MQTTClient对应的是linux环境下和arduino的板子,MQTTClient-C对应的应该是支持Posix标准的C语言,MQTTPacket则是纯C语言编写的,不依赖于任何库函数。
(这些是笔者根据其中包含的文件名猜测的,编写本文的时候,笔者只研究了MQTTPacket一个文件夹,另外2个还没有分析)
接下来,也会主要从MQTTPacket这个文件夹的内容入手分析。
这个文件夹下有三个子文件,分别是samples(三个小实验)、src(所需要的所有函数定义和头文件)、test(包含一个较为完整的测试用例)。其中samples和test文件夹下都有已经编写好的可执行文件(包含linux环境下编译命令),帮助你编译生成相应的执行文件。

步骤2:运行例程
          主要运行了samples目录下的三个实验,分别是pub0sub1.c(阻塞接收订阅的主题信息,并将收到的信息发送到另一个主题上)、pub0sub1_nb.c(非阻塞接收订阅的主题信息,并将收到的信息发送到另一个主题上)、qos0pub.c(将信息发送到某个主题上)。
         这些例程原来连接的服务器端是其官方网站,这里需要做修改,改成mosquitto部署的地方对应的IP地址(mosquitto部署在本地局域网环境)。还可能涉及的修改就是主题和需要发送的信息(当然也可以不改,笔者经过实际实验发现,例程提供的外网环境和本地局域网环境下工作其实都是正常的,信息都能正常收发)。
        修改完毕后,和这些C文件处于同一级的还有2个文件,一个是null.c(正如它取的名字一样,里面什么有价值的代码也没有),一个是名为“build”的可执行文件。查看其内容,就是编译上面三个C文件的编译命令。运行build文件,则会在相同一级目录得到三个C文件对应的可执行文件。笔者通过mosquitto库提供的订阅和发布主题的命令来测试这三个例子。
        奇怪的现象:这几个例程单独运行都没问题,后来笔者想通过修改主题,希望pub0pub1能收到来自qos0pub的消息,然后pub0pub1再把收到的消息发出去。笔者先运行pub0pub1(因为它是接收端嘛,循环接收,而qos0pub则是发送一次消息),但以运行qos0pub时,发送的终端显示发送成功(通过mosquitto的订阅主题,也确实收到了),但早期pub0pub1这边显示断开连接,后来则是循环打印某句话,并迅速占满整个终端屏幕。(这个循环打印的现象困扰了笔者2天,通过分析源码,后来发现还是因为pub0pub1中的socket断开了连接

                                                                      循环打印某一行
         这个现象反映的问题就是当运行了pub0pub1等待接收后,再运行qos0pub,则会导致接收程序pub0pub1与服务器断开。这个问题,笔者一直分析源码的逻辑,比较发送函数,比较接收到的数据。但均未解决问题。后来终于在mosquitto 服务端的打印消息中找到了问题的所在。
 
 原来在socket连接函数前的配置中有一个用户ID的配置,最开始的两个程序配置了相同的用户ID,这两个程序同时执行时则会发生用户冲突,相当于后者把前者挤下去了。才会有前面说的那些现象。

步骤3:正式移植到MCU上
       因为笔者的终极目的就是在MCU上能够使用MQTT服务,故到这里才算是进入正题,前面都是辅助实验。笔者使用的MCU是庆科公司的MX1801开发板。在实验前,根据paho提供的例程分别编写发布消息函数(pub_client.c)和订阅消息函数(sub_client.c)。移植工作主要集中在修改transport.c文件和pub_client.c、sub_client.c的逻辑编写。
步骤4:测试实验(传输速度、传输文件大小、局域网测试、外网测试,重新连接)

实验1:分别单独测试pub_client.c 和 sub_client.c
 
                                                                     发布消息

                                                                      订阅消息
         可以发现,发布消息与前面在linux环境下是一样的,但是订阅消息却不一样。linux环境下在等待数据到来过程中会循环打印“publishing reading”(不过可以明显感觉中间停顿,因为有判断过程,而上面提到的断开连接时也是打印“publishing reading”,但速度明显更快,因为判断过程因为断开连接已经跳过),而在MX1801上则只有在收到数据才会打印一次,而在等待数据时什么也不会打印。
        这是因为MX1801中有一种阻塞模式,在使用DNS解析时需开启这种模式,在这种模式下其在实现recv()函数时也使用了阻塞模式等待数据,而linux环境下不会阻塞等待。这点很关键,在后面的实验还会遇到它造成的困惑。

实验2:配合使用pub_client.c 和 sub_client.c
       先简单介绍下这两个函数主要的的逻辑:
       pub_client.c :与服务器建立socket连接,发送消息,然后断开连接。
      sub_client.c : 与服务器建立socket连接,循环接收数据,跳出该循环后,断开连接。
  
        笔者首先想直接同时使用这两个方法。逻辑设计为在MX1801main函数中的while大循环中先调用sub_client循环等待数据的到来,然后在某个Button触发回调函数汇总调用pub_client发送主题数据,想按下Button后让sub_client接收到这个数据。
       但实际上一旦按下Button,就像前面linux环境下,接收数据端不断打印“publishing reading”,这说明前面的sub_client已经断开了与服务器的连接。这个应该是和MCU端有关。因为在建立socket时都是pub_client.c 和 sub_client.c各自建立,即相当于两次建立了一个IP和端口号都相同的socket,自然server会把前者关闭。
        当然,如果你想做到自发自收还是可以的,不过需要对sub_client.c进行修改。因为一个socket连接本就是全双工的工作模式,无需通过再建立一个socket完成(即不使用pub_client.c)。

        在上面的实验基础之上,又做了一个实验:即在while大循环中调用sub_client循环等待数据的到来,按下BUtton后,让sub_client循环等待数据的条件不成立,然后执行一次pub_client,发布完主题消息后,断开该socket,重新调用sub_client继续监听。
        这个实验唯一的问题就在于按下Button后,程序虽然不会循环等待数据,但仍会阻塞在接收数据的函数中(原因前面已说明,阻塞等待)。因而采取一个非正规手段,即在Button按下触发的函数中又调用了一次pub_client(pub的内容还是发出去了),这个动作的本意是让接收程序能够跳过阻塞接收数据部分继续向后执行,但其实质上已经把sub_client创建的socket关闭了(为什么关闭了?还不清楚)。虽然程序表面上按预期执行了,但这里提到的深层次原因还是很值得注意的。(笔者之所以发现了这一点,是因为sub_client在跳过接收数据时的返回值是不正常的,进而分析出这一点

总结:可以看到,实际上现在的实验遇到的所有的问题都是因为两个socket冲突造成的。因而,在实际的环境中,支持多进程的环境对发挥MQTT的作用的影响还是很明显的。

实验3.添加重连函数
        C库客户端在每次连接前的配置信息中会有一个keepAlive的参数配置,一旦超过这个时间,socket会自动断开连接,并且没有任何后续处理。在实际应用中这肯定是不行的。当然希望可以自己控制这个连接是否断开,而非因为超时被动断开。这里有两种方式解决这个问题:
1.将这个keepAlive的时间设置的足够大
2.断开后能自动重连
笔者在阅读Android端的程序时发现采用的就是后者这种方式,因为第二种方法Client端可以控制是否自动重连,更加灵活。
       笔者本来打算在客户端与服务端连接后使用MCU端自带的库函数SetTimer()来设置在keepalive时间结束后,重新创建一个socket,然后循环等待数据的到来。其实这就是sub_client的逻辑嘛,相当于SetTimer()函数在规定时间后再调用一次sub_client,那么第一次的sub_client怎么办呢?判断socket失效后直接关闭这个文件描述符。这种逻辑是可行的。还有一种方法,就是在判断当前socket失效后,直接跳到程序的末尾,然后关闭当前socket,调用sub_client.

实验4. MCU大文件传输
在mosquitto的源码中的Client/pub_client.c中有对文件的处理,其中有一个方法load_file,从某个路径读取数据放入缓冲区中,然后作为一条message传出去。其中有对文件大小限制,超过256M则不处理。不知道这个限制是基于什么原因出来的。笔者仿照其逻辑编写了MCU端的大文件传输,发现,由于MCU本身的限制,其最大的传输文件大小为17KB。当使用mosquitto作为服务器时,由于在局域网内,所以消息的推送还比较快;而当使用外网来做测试时,发现消息推送有大概4到6秒的延时。

实验5.与m2m.eclipse.org通讯,完成MCU、Android端在广域网的通讯测试
实际上,前面的实验过程中,做了以mosquitto作为服务器MCU、Android通信的实验,是没有问题的。但那时局域网,说服力有限。这个测试IP还是速度很不错的,基本上感觉不到延时,但不知道其具体性能会不会随着用户量和时间的变化而变化。
    


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多