我的mqtt协议和emqttd开源项目个人理解(23) - websocket客户端连接过程分析(Wireshark抓包+源码分析)

简介: 我的mqtt协议和emqttd开源项目个人理解(23) - websocket客户端连接过程分析(Wireshark抓包+源码分析)

我们可以使用emq自带的Dashboard插件,进行websocket调试,打开谷歌浏览器输入网址,其中192.168.83.128是emq所在的IP地址:


http://192.168.83.128:18083/#/websocket


用户名:admin,密码:public


WebSocket URI:ws(s)://192.168.83.128:8083/mqtt


TCP URI:tcp://192.168.83.128:1883



一、测试实践


测试环境:使用MQTT 3.1.1版本协议,Wireshark 2.4.1版本,使用wireshark抓包分析(如果是虚拟机,要抓VMnet8虚拟网卡)。


clientid:861694030142473

username:libaineu2004

password:12345678


cleanSession:true


keepalive:60s


1、测试方案1,使用普通的tcp连接,TCP URI:tcp://192.168.83.128:1883,wireshark过滤条件tcp.port == 1883。

(1)、MQTT Connect Command

鼠标点击wireshark的Frame页面,右键菜单,复制,选中树的所有可见项目

MQ Telemetry Transport Protocol, Connect Command

   Header Flags: 0x10 (Connect Command)

   Msg Len: 51

   Protocol Name Length: 4

   Protocol Name: MQTT

   Version: 4

   Connect Flags: 0xc2

   Keep Alive: 60

   Client ID Length: 15

   Client ID: 861694030142473

   User Name Length: 12

   User Name: libaineu2004

   Password Length: 8

   Password: 12345678


右键菜单,复制,字节为HEX + ASCII转储

0000   10 33 00 04 4d 51 54 54 04 c2 00 3c 00 0f 38 36  .3..MQTT...<..86

0010   31 36 39 34 30 33 30 31 34 32 34 37 33 00 0c 6c  1694030142473..l

0020   69 62 61 69 6e 65 75 32 30 30 34 00 08 31 32 33  ibaineu2004..123

0030   34 35 36 37 38                                   45678


(2)、MQTT Connect Ack

MQ Telemetry Transport Protocol, Connect Ack

   Header Flags: 0x20 (Connect Ack)

   Msg Len: 2

   Acknowledge Flags: 0x00

   Return Code: Connection Accepted (0)


0000   20 02 00 00                                       ...



2、测试方案2,使用websocket连接,WebSocket URI:ws(s)://192.168.83.128:8083/mqtt,wireshark过滤条件tcp.port == 8083。我们可以观察到mqtt连接的过程,websocket协议,完整的数据被拆分了好几帧传输。每一帧报文,都有WebSocket和Data字段。我们仅仅需要关注Data字段即可。我们发现,把所有Data字段组合拼接起来,就是和测试方案1完全相同的数据。MQTT Connect Command和Ack都完全相同。说明了即使使用了Websocket方式来传输,仍然遵循mqtt协议。


从本质上来讲,Websocket也是基于 TCP 协议的,同时借用了HTTP的协议来完成一部分握手。

主要解决 HTTP 协议中一个 request 对应一个 response 的尴尬。(http server 不能主动发送消息给 http client) 。通过 HTTP 完成 websocket 的握手过程,接着按照 websocket 协议进行通讯。


websocket 也有他自己的数据帧格式: http://blog.csdn.net/u010487568/article/details/20569027


(1)MQTT Connect Command


0000   10                                               .

0000   33                                               3

0000   00 04                                            ..

0000   4d 51 54 54                                      MQTT

0000   04                                               .

0000   c2                                               .

0000   00 3c                                            .<

0000   00 0f                                            ..

0000   38 36 31 36 39 34 30 33 30 31 34 32 34 37 33     861694030142473

0000   00 0c                                            ..

0000   6c 69 62 61 69 6e 65 75 32 30 30 34              libaineu2004

0000   00 08                                            ..

0000   31 32 33 34 35 36 37 38                          12345678


(2)MQTT Connect Ack


Data (4 bytes)

   Data: 20020000

   [Length: 4]

0000   20 02 00 00                                       ...



二、以下是源码分析,先来看emq v1.1.3版本的源码:


1、-module(emqttd_app).


%%--------------------------------------------------------------------

%% Start Servers

%%--------------------------------------------------------------------

start_servers(Sup) ->

   Servers = [

              {"emqttd wsclient supervisor", {supervisor, emqttd_ws_client_sup}},

%% Start http listener

start_listener({http, ListenOn, Opts}) ->

   mochiweb:start_http(http, ListenOn, Opts, {emqttd_http, handle_request, []});

打开8083端口,开启http服务器。Websocket也是基于 TCP 协议的,同时借用了HTTP的协议来完成一部分握手。



2、-module(emqttd_http).


%%--------------------------------------------------------------------

%% MQTT Over WebSocket

%%--------------------------------------------------------------------

handle_request('GET', "/mqtt", Req) ->

   lager:info("WebSocket Connection from: ~s", [Req:get(peer)]),

   Upgrade = Req:get_header_value("Upgrade"),

   Proto   = Req:get_header_value("Sec-WebSocket-Protocol"),

   case {is_websocket(Upgrade), Proto} of

       {true, "mqtt" ++ _Vsn} ->

           emqttd_ws:handle_request(Req);

       {false, _} ->

           lager:error("Not WebSocket: Upgrade = ~s", [Upgrade]),

           Req:respond({400, [], <<"Bad Request">>});

       {_, Proto} ->

           lager:error("WebSocket with error Protocol: ~s", [Proto]),

           Req:respond({400, [], <<"Bad WebSocket Protocol">>})

   end;

websocket的入口函数。重点关注emqttd_ws:handle_request(Req);



3、-module(emqttd_ws).

ws_loop(Data, State = #wsocket_state{peer = Peer, client_pid = ClientPid,
                                     parser_fun = ParserFun}, ReplyChannel) ->
    ?WSLOG(debug, Peer, "RECV ~p", [Data]),
    case catch ParserFun(iolist_to_binary(Data)) of
        {more, NewParser} ->
            State#wsocket_state{parser_fun = NewParser};
        {ok, Packet, Rest} ->
            gen_server:cast(ClientPid, {received, Packet}),
            ws_loop(Rest, reset_parser(State), ReplyChannel);
        {error, Error} ->
            ?WSLOG(error, Peer, "Frame error: ~p", [Error]),
            exit({shutdown, Error});
        {'EXIT', Reason} ->
            ?WSLOG(error, Peer, "Frame error: ~p", [Reason]),
            ?WSLOG(error, Peer, "Error data: ~p", [Data]),
            exit({shutdown, parser_error})
    end.


gen_server:cast(ClientPid, {received, Packet}), 接收来自客户端的消息。



4、-module(emqttd_ws_client).


handle_cast({received, Packet}, State = #wsclient_state{peer = Peer, proto_state = ProtoState}) ->
    case emqttd_protocol:received(Packet, ProtoState) of
        {ok, ProtoState1} ->
            noreply(State#wsclient_state{proto_state = ProtoState1});
        {error, Error} ->
            ?WSLOG(error, Peer, "Protocol error - ~p", [Error]),
            shutdown(Error, State);
        {error, Error, ProtoState1} ->
            shutdown(Error, State#wsclient_state{proto_state = ProtoState1});
        {stop, Reason, ProtoState1} ->
            stop(Reason, State#wsclient_state{proto_state = ProtoState1})
    end;

emqttd_protocol:received(Packet, ProtoState)。接收处理消息。



5、-module(emqttd_protocol).

process(Packet = ?CONNECT_PACKET(Var), State0) ->
    #mqtt_packet_connect{proto_ver  = ProtoVer,
                         proto_name = ProtoName,
                         username   = Username,
                         password   = Password,
                         clean_sess = CleanSess,
                         keep_alive = KeepAlive,
                         client_id  = ClientId} = Var,
    State1 = State0#proto_state{proto_ver  = ProtoVer,
                                proto_name = ProtoName,
                                username   = Username,
                                client_id  = ClientId,
                                clean_sess = CleanSess,
                                keepalive  = KeepAlive,
                                will_msg   = willmsg(Var),
                                connected_at = os:timestamp()},
    trace(recv, Packet, State1),
    {ReturnCode1, SessPresent, State3} =
    case validate_connect(Var, State1) of


validate_connect(Var, State1),校验clientid,username和password的有效性。


相关实践学习
快速体验阿里云云消息队列RocketMQ版
本实验将带您快速体验使用云消息队列RocketMQ版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
9月前
|
数据采集 传感器 监控
Modbus 与 MQTT 协议兼容:MyEMS 的泛在能源数据采集技术实现
MyEMS深度融合Modbus与MQTT协议,破解能源数据采集中协议碎片化、网络异构、数据孤岛等难题。通过Modbus接入95%以上工业设备,实现现场数据精准“拉取”;依托MQTT构建高效物联网传输通道,支持多源数据主动“推送”与云端集成。边缘侧采集规整,中心侧汇聚分析,形成统一、可靠、低延迟的数据流。该架构兼具高兼容性、强扩展性与低运维成本,广泛应用于工业园区、商业楼宇及集团型企业,支撑实时监控、AI分析与跨系统融合,打造泛在互联的能源数据底座,助力企业实现全面智慧能源管理。
597 6
|
消息中间件 存储 数据采集
4步实现状态机驱动的MQTT客户端,快速接入OneNet (1)
本文介绍了基于状态机驱动的MQTT客户端快速接入OneNet平台的实现方法,通过4步完成模块设计。文章以开源项目`Sparrow`为基础,引入`OneNetMqtt`业务模块,采用事件驱动模型和双层状态机设计,实现设备状态管理、消息处理及定时任务等功能。模块分为三层:`OneNetManager`负责核心逻辑,`OneNetDevice`管理设备信息,`OneNetDriver`处理Socket与MQTT通信。验证结果显示设备连接、数据上报及下线功能正常,稳定性良好。该设计简化了复杂条件判断,增强了系统灵活性与可扩展性,适用于实际项目参考。文末提供源码获取方式,助力读者实践与学习。
801 118
|
数据可视化 关系型数据库 MySQL
嵌入式C++、STM32、MySQL、GPS、InfluxDB和MQTT协议数据可视化
通过本文的介绍,我们详细讲解了如何结合嵌入式C++、STM32、MySQL、GPS、InfluxDB和MQTT协议,实现数据的采集、传输、存储和可视化。这种架构在物联网项目中非常常见,可以有效地处理和展示实时数据。希望本文能帮助您更好地理解和应用这些技术,构建高效、可靠的数据处理和可视化系统。
866 82
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)。
2566 0
|
数据采集 传感器 监控
多协议网关BL110钡铼6路RS485转MQTT协议云网关
BL110钡铼6路RS485转MQTT协议云网关是一款高性能、易配置的工业级设备,适用于各种需要远程监控和数据采集的物联网应用场景。通过将传统RS485设备的数据转换为MQTT协议并上传至云平台,实现了设备的远程管理和智能控制,极大地提升了系统的管理效率和响应速度。
644 2
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
3871 1
SpringBoot快速搭建WebSocket服务端和客户端
由于工作需要,研究了SpringBoot搭建WebSocket双向通信的过程,其他的教程看了许多,感觉讲得太复杂,很容易弄乱,这里我只展示快速搭建过程。
2967 1
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
2344 127
|
消息中间件 XML 前端开发
springBoot集成websocket实时消息推送
本文介绍了如何在Spring Boot项目中集成WebSocket实现实时消息推送。首先,通过引入`spring-boot-starter-websocket`依赖,配置`WebSocketConfig`类来启用WebSocket支持。接着,创建`WebSocketTest`服务器类,处理连接、消息收发及错误等事件,并使用`ConcurrentHashMap`管理用户连接。最后,前端通过JavaScript建立WebSocket连接,监听消息并进行相应处理。此方案适用于需要实时通信的应用场景,如聊天室、通知系统等。
2677 2