mosquitto的多端口机制及其使用

欢迎加入QQ群:221779856,国内最活跃的Mosquitto沟通社区,关于MQTT、Mosquitto、IM、推送系统、物联网、高并发处理等技术。

一、mosquitto多端口机制的说明

mosquitto的多端口机制是指它能够同时监听多个端口,通过配置文件“mosquitto.conf”可以看到它默认的监听端口的配置参数是port,而其他扩展监听端口的配置使用参数:listener,并且可以通过listener参数配置多个扩展监听端口,注意:不同的监听端口可以支持不同的协议,mosquitto支持mqtt和websocket两种协议,如果不配置端口对应的协议,则默认使用mqtt协议。

1.1、示例1:多端口全部使用默认mqtt协议

  1. 建立一个配置文件“mosquitto-multiport.conf”,里面只配置多个监听端口,配置文件的内容为:

port 1883

listener 1884

listener 1885

listener 1886

由于mosquitto对所有的配置项都进行了默认参数设置,我们只需要提供这些监听端口号就行了,此时配置文件里的所有监听端口全部默认使用mqtt协议。

使用配置文件“mosquitto-multiport.conf”启动mosquitto(在启动时通过-c指定配置文件即可,如本例的 ./mosquitto-1.4.15/src/mosquitto -c mosquitto-multiport.conf),可以看到输出日志信息,如下图所示:

这里可以看到mosquitto已经按照配置文件中的设置参数同时打开了监听端口:1883、1884、1885、1886;需要注意一点:mosquitto对于每个监听端口都会启用ipv4和ipv6。

  1. 多端口的使用示例

mosquitto的多端口使用和单一端口的使用很类似,不过需要注意,如果使用时不指定端口号,则默认使用参数port指定的端口号。下面将使用mosquitto自带的测试客户端(注:编译完mosquitto源码之后,会在src目录下生成mosquitto的可执行程序:mosquitto,在client目录下生成测试客户端mosquitto_sub和mosquitto_pub)使用多端口进行订阅/发布测试:

  • 启动订阅测试程序,启动时指定监听端口号1884,并订阅topic“msg-jason”命令为:

./mosquitto_sub -h 172.31.124.235 -p 1884 -t "msg-jason"

  • 发布消息,通过端口1883、1884、1885、1886向topic“msg-jason”各pub一条消息,如下图所示:

可以看到订阅端将收到这些订阅消息:

通过这个例子,可以看到:mosquitto多监听端口机制只对应客户端连接,在mosquitto内部,来自这些监听端口的客户端共用一个订阅树,即只要你订阅了某个topic,无论消息从哪个端口pub到该主题,都能收到。

1.2、实例2:多端口支持不同的协议

  1. 修改配置文件“mosquitto-multiport.conf”,里面只配置多个端口,配置文件的内容为:

port 1883

listener 1884

 

listener 1885

protocol mqtt

 

listener 1886

protocol mqtt

 

listener 9001

protocol websockets

如下图所示:

这里的配置文件有如下不同:

  1. 默认的1883端口和扩展的1884端口都没有配置protocol参数,因此,mosquitto会使用默认的mqtt协议;
  2. 为扩展端口1885、1886配置了protocol参数为mqtt,则mosquitto在这两个端口上使用mqtt协议;
  3. 扩展端口9001配置了protocol参数为websocket,则mosquitto在该端口上使用websocket协议;
  1. 测试不同端口对不同协议的支持
  • 启动订阅测试程序,指定监听端口号1884,并订阅topic“msg-jason”,如下图所示:

  • 启动发布测试程序

在端口1883、1884、1885、1886上使用mqtt协议发布消息,如下图:

使用websocket协议向端口9001上发布消息:hello jason, this message from port 9001 with websocket,如下图:

 

此时可以看到订阅端从不同端口上使用不同协议收到的订阅消息:

  • mosquitto多端口机制的配置及用法

这里将介绍mosquitto涉及端口相关的参数,这些参数都与指定的监听端口相关,通过这些参数我们能针对不同端口配置各种策略,例如:同时让端口A使用mqtt并且不启用tls,让端口B使用mqtt协议并且启用tls,让端口C使用websocket协议并且不启用tls,让端口D使用websocket并且启用tls等等;总之,这些与监听端口相关的参数能让我们针对不同场景时设置不同的参数;因此,每新增加一个监听端口的时候,都可以配置这些参数,当然也可以不配置它们(mosquitto使用默认参数,例如默认的协议为mqtt,默认不开启tls等)。

2.1、mosquitto端口号相关的参数说明

mosquitto的默认端口和扩展端口相关参数基本类似(注意:端口参数不一样),如下表所示:

参数分类

默认端口号相关参数

扩展端口号相关参数

 

端口相关基本参数

bind_address

——

特别注意:扩展端口没有专门设置绑定ip地址的参数,它绑定ip地址时,直接把主机名放在listener 的port之后,作为其第二个参数即可;

port

listener

max_connections

max_connections

该参数可以限制此监听端口当前对应的最大连接数,默认为-1,表示该端口可以无限接受客户端连接;

——

mount_point

 

protocol

protocol

该监听端口派生出的业务端口所使用的应用层协议,可选项为mqtt或者websocket;

http_dir

http_dir

 

use_username_as_clientid

use_username_as_clientid

 

 

cafile

cafile

 

capath

capath

 

certfile

certfile

 

keyfile

keyfile

 

tls_version

——

 

require_certificate

require_certificate

 

use_identity_as_username

use_identity_as_username

 

crlfile

crlfile

 

ciphers

ciphers

 

 

psk_hint

psk_hint

 

use_identity_as_username

use_identity_as_username

 

ciphers

ciphers

 

通过上面的配置参数表可以看到:默认端口号和扩展端口号的配置参数大部分都一样;尤其是在配置多个扩展端口号的时候,这些参数在一个配置文件里都会出现很多次,那么mosquitto是怎么搞清楚那个参数属于哪个参数的呢?答案是,要把这些参数紧跟着放在所属的端口之后,不要交叉(不能多端口号之间的配置参数交叉)。总之,如果需要配置mosquitto的一个监听端口,就在所配置的监听端口之后紧跟着上面这些参数的配置,一定不能让端口相关的配置参数,在多个端口配置时交叉配置

2.2、mosquitto内部是怎么读取这种配置参数的呢?

mosquitto关于配置文件读取的相关代码位于mosquitto源码里的src目录下的conf.c文件中,核心读取配置的的函数为:_config_read_file_core(源码为mosquitto的1.4.15版本),它是这样实现配置参数的读取逻辑的:

  1. 每个监听端口都对应一个结构体struct _mqtt3_listener(在src/mosquitto_broker.h文件中定义),用于保存该监听端口的所有信息,也包括其配置信息;
  2. 在读取配置文件时,每遇到一个配置参数的key为“listener”的参数,就会创建一个结构体struct _mqtt3_listener的对象,该对象由指针cur_listener管理,如下图所示(下面这段代码来自配置文件读取的核心函数_config_read_file_core):

对于读取到的其他与端口相关的配置项,直接就存储到当前的端口结构体里(cur_listener指针所指),以配置参数“protocol”为例:

通过上述mosquitto读取各端口的配置参数可以看到:每个端口相关的配置信息一定要放在对应的listener参数之后,如下图所示,每组的监听端口的配置信息放在一起,一定不能交叉。

  • mosquitto多端口机制的实现原理

3.1、端口与socket、监听socket与业务socket

一般在服务端进行端口配置,端口配置完成之后,将被用于监听socket和业务socket。通过《关于socket的一些总结》(https://blog.csdn.net/houjixin/article/details/18737881)中可知,socket实质是一个linux数据结构体struct socket(详见总结https://blog.csdn.net/houjixin/article/details/51923919)。无论是监听socket还是业务socket在linux系统里定义都一样(见https://blog.csdn.net/houjixin/article/details/51923919关于对socket的总结),它们只是状态不一样,从分工角度来看:监听socket在服务端主要负责接收客户端的连接,而业务socket主要负责客户端与服务端之间业务数据的传输;业务socket与监听socket之间存在着如下关系:

业务socket派生于监听socket,它很多属性信息也继承自监听socket;在服务端工作过程中:监听socket负责接收客户端的连接(即负责三次握手建立连接),它每接收到三次握手的syn包就创建一个socket,这时socket的信息还不完整(它的五元组还不够,见https://blog.csdn.net/houjixin/article/details/18737881),这个不完整的socket被暂时放在监听socket的队列中,一旦三次握手完成,也就意味着业务socket的信息被补充完成,它就可以正常收发业务数据。业务socket和监听socket的区分只是在服务端存在,这两个socket只是状态不同,工作分工不同,对于客户端而言,他的socket就是业务socket;

3.2、mosquitto对于多端口的内部实现

mosquitto多监听端口机制下,每个监听端口都会产生多个独立的业务socket用于和客户端的业务通信,由于每个监听端口所使用的协议可以不一样(可选择mqtt或者websocket),因此,这些业务socket进行通信时所使用的应用层协议也不一样,mosquitto在内部需要对针对不同的业务socket按照它所使用的应用层协议(就是配置文件中,对该业务socket的监听socket所配置的协议)对TCP报文进行解析,进而完成与不同协议的客户端进行有效通信,如下图所示。

在前文2.2中介绍,mosquitto读取配置文件时,为每个监听端口创建一个结构体struct _mqtt3_listener,在该结构体中保存了该监听端口的所有信息,例如监听端口对应的监听socket、所使用的通信协议。

在mosquitto内部每个连接建立之后,mosquitto都会为它创建一个结构体struct mosquitto(该结构体定义在src/mosquitto_internal.h文件中)对象,相关代码在文件src/net.c文件中的mqtt3_socket_accept函数里,struct mosquitto结构体中有个指针listener用于指向当前业务socket对应的监听socket,因此通过每个业务socket的struct mosquitto结构体都能找到它所对应的监听socket结构体struct _mqtt3_listener。

另外,struct mosquitto结构体中还有个指针struct lw*s用于指向websocket,如果当前连接使用websocket进行通信,那么该指针将被赋值。在mosquitto从一个socket上读取数据时,先找到这个socket对应的struct mosquitto结构体,然后通过该结构体的struct lws*指针是否为空,判断该socket连接是否使用了websocket协议,如果采用了websocket则后续接收到的报文将按照websocket协议解析,否则直接按照mqtt协议进行解析,这部分操作位于文件src/loop.c的函数loop_handle_reads_writes中,其中按照websocket协议的读取操作被放在宏WITH_WEBSOCKETS里处理,按照mqtt协议的读取操作则调用函数_mosquitto_packet_read完成。

  • mosquitto多端口机制的应用举例

在某些应用场景下,mosquitto的多端口功能非常有用,尤其是针对线上的生产环境。下面将以一个推送系统为例,说明mosquitto的多端口功能在生产环境上的使用。

4.1、推送系统的功能概述

推送系统与所有在线终端设备保持长连接,并能够给在线设备推送消息,推送系统支持多种类型的推送服务:

  1. 给在线设备发送通知,不在线的设备不需要推送;
  2. 查询设备是否在线;
  3. 给在线和离线的设备都发送消息;

推送系统与设备之间的长连接功能采用MQTT协议通信,MQTT协议的broker使用mosquitto,为支持大量的终端设备连接,推送系统内部有个专门的mosquitto管理服务MosqProxy,它支持mosquitto的水平扩展,并且在redis中记录哪个设备连接到了哪个mosquitto上。

为支持离线消息的存储,推送系统内部有个专门的消息中心服务MsgCenter,它内部连接了Mysql和Redis缓存,用于存储待客户端拉取的消息。

推送系统工作时有如下需要说明的点:

  1. 终端设备启动时要先向后台申请mosquitto地址,然后再连接到后台动态分配的mosquitto;
  2. 对于要求必达终端的通知,推送系统采用拉模式,即:后端先为设备缓存必达消息,然后通过长连接服务为设备发送通知,终端设备收到通知之后,再向后台拉取这些消息,处理完消息之后,再发送请求删除。另外,在设备断线重连成功之后也要无条件拉取自己的离线消息。

4.2、推送系统的结构分析

本例中的推送系统分为以下几个部分:

  1. nginx接入网关,用于接受客户端的HTTPS的请求,并将之转发给内网的推送管理器(PushManager);
  2. PushManager,服务管理器,它负责接收Nginx转发过来的客户端请求,并将请求路由到MosqProxy服务或者MsgCenter服务;
  3. MosqProxy服务,主要管理多个mosquitto,并根据mosquitto的负载情况为终端设备分配相应的mosquitto服务地址;同时还通过mosquitto给其上的终端设备发送通知;
  4. MsgCenter服务,主要负责消息的管理,包括:根据设备ID按顺序存储、拉取、删除消息。

上述服务之间的关系示意图为:

在上述系统开发、测试完成之后,需要进行上线部署,在上线部署的时候基于安全考虑有要求:

  1. 关于外网IP,线上生产环境的主机组成了一个独立的内网环境,服务之间的通信都是通过内网IP(内网域名),对外网提供服务的主机需要单独申请外网IP;
  2. 关于端口,只有主机必须的运维监控端口和系统本身所需要的端口之外,其他端口全部封闭,如果业务服务需要开启某个端口,则需要向运维人员申请开通。
  3. mosquitto与终端设备的通信协议多种多样,可根据应用场景选择基于tls加密之后的mqtt或websocket,还可能是基于普通tcp通道上的mqtt或websocket;
  4. MosqProxy与各mosquitto之间采用基于普通tcp的mqtt协议通信,以保证数据发送的效率;

在上述推送系统中,只有接入网关Nginx服务和mosquitto两个服务需要外网IP,其他服务全部为内网地址。

 

这里重点关注mosquitto所在的主机O_2,它为4核8G的主机,OS为:CentOS 7,网络配置为:一个内网IP和一个外网IP;在实际部署时,我们在这台主机上部署了两个mosquitto服务,其中:

服务名

监听端口

绑定IP

通信方式

说明

mosq_1

i_p1

内网ip

mqtt

内网通信

o_p1

外网ip

tls+mqtt

外网加密mqtt通信

o_p2

外网ip

tls+websocket

外网加密websocket通信

mosq_2

i_p2

内网ip

mqtt

内网通信

o_p3

外网ip

mqtt

外网通信

在这样的配置下能够做到,内网服务MosqProxy在管理多个mosquitto的时候只需要使用普通的mqtt协议即可,这样可以保证传输效率,而且内网服务之间通信业务无需加密操作。对于终端设备,则应用场景不一样,采用的通信方式也不会一样,例如:在智能家居相关的项目中,要求对设备发送的空指令采用tls+mqtt;如果要对前端网页进行推送服务,则可以使用tls+websocket;还可以采用应用层的安全措施等之后,使用非加密的mqtt协议即可与设备进行通信。

综合上述,mosquitto的这种多监听端口机制,可以让一个mosquitto同时支持多种通信方式(mqtt、websocket、tls+mqtt、tls+websocket等),减少了服务端的部署、维护甚至是开发的工作量。

 

展开阅读全文

没有更多推荐了,返回首页