我的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
相关文章
|
10月前
|
监控 负载均衡 安全
WebSocket网络编程深度实践:从协议原理到生产级应用
蒋星熠Jaxonic,技术宇宙中的星际旅人,以代码为舟、算法为帆,探索实时通信的无限可能。本文深入解析WebSocket协议原理、工程实践与架构设计,涵盖握手机制、心跳保活、集群部署、安全防护等核心内容,结合代码示例与架构图,助你构建稳定高效的实时应用,在二进制星河中谱写极客诗篇。
WebSocket网络编程深度实践:从协议原理到生产级应用
|
消息中间件 存储 数据采集
4步实现状态机驱动的MQTT客户端,快速接入OneNet (1)
本文介绍了基于状态机驱动的MQTT客户端快速接入OneNet平台的实现方法,通过4步完成模块设计。文章以开源项目`Sparrow`为基础,引入`OneNetMqtt`业务模块,采用事件驱动模型和双层状态机设计,实现设备状态管理、消息处理及定时任务等功能。模块分为三层:`OneNetManager`负责核心逻辑,`OneNetDevice`管理设备信息,`OneNetDriver`处理Socket与MQTT通信。验证结果显示设备连接、数据上报及下线功能正常,稳定性良好。该设计简化了复杂条件判断,增强了系统灵活性与可扩展性,适用于实际项目参考。文末提供源码获取方式,助力读者实践与学习。
837 113
|
消息中间件 架构师 Java
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
|
弹性计算 JavaScript Ubuntu
WebSocket协议相关的测试命令工具使用简介
本文介绍了针对WebSocket的测试工具wscat和websocat的基本使用方法,以及通过curl命令测试HTTP/HTTPS协议的方式。对于WebSocket,直接使用curl测试较为复杂,推荐使用wscat或websocat。文中详细说明了这两种工具的安装步骤、常用参数及连接示例,例如在ECS上开启8080端口监听并进行消息收发测试。此外,还提供了curl命令的手动设置头部信息以模拟WebSocket握手的示例,但指出curl仅能作为客户端测试工具,无法模拟服务器。
4058 5
|
存储 消息中间件 缓存
RocketMQ原理—3.源码设计简单分析下
本文介绍了Producer作为生产者是如何创建出来的、启动时是如何准备好相关资源的、如何从拉取Topic元数据的、如何选择MessageQueue的、与Broker是如何进行网络通信的,Broker收到一条消息后是如何存储的、如何实时更新索引文件的、如何实现同步刷盘以及异步刷盘的、如何清理存储较久的磁盘数据的,Consumer作为消费者是如何创建和启动的、消费者组的多个Consumer会如何分配消息、Consumer会如何从Broker拉取一批消息。
632 11
RocketMQ原理—3.源码设计简单分析下
|
网络协议 Java 开发工具
全平台开源即时通讯IM框架MobileIMSDK:7端+TCP/UDP/WebSocket协议,鸿蒙NEXT端已发布,5.7K Stars
全平台开源即时通讯IM框架MobileIMSDK:7端+TCP/UDP/WebSocket协议,鸿蒙NEXT端已发布,5.7K Stars
826 1
|
消息中间件 Java 数据管理
RocketMQ原理—2.源码设计简单分析上
本文介绍了NameServer的启动脚本、启动时会解析哪些配置、如何初始化Netty网络服务器、如何启动Netty网络服务器,介绍了Broker启动时是如何初始化配置的、BrokerController的创建以及包含的组件、BrokerController的初始化、启动、Broker如何把自己注册到NameServer上、BrokerOuterAPI是如何发送注册请求的,介绍了NameServer如何处理Broker的注册请求、Broker如何发送定时心跳
|
弹性计算 JSON 自然语言处理
语音交互产品通过WebSocket协议对外提供实时语音流语音转写功能
阿里云智能语音交互产品通过WebSocket协议提供实时语音转写功能,支持长语音。音频流以Binary Frame上传,指令和事件为Text Frame。支持单声道、16 bit采样位数的PCM、WAV等格式,采样率8000Hz/16000Hz。可设置返回中间结果、添加标点、中文数字转阿拉伯数字,并支持多语言识别。服务端通过临时Token鉴权,提供外网和上海ECS内网访问URL。交互流程包括StartTranscription、StopTranscription指令及多种事件反馈。
1189 15
|
移动开发 JSON Java
Jmeter实现WebSocket协议的接口测试方法
WebSocket协议是HTML5的一种新协议,实现了浏览器与服务器之间的全双工通信。通过简单的握手动作,双方可直接传输数据。其优势包括极小的头部开销和服务器推送功能。使用JMeter进行WebSocket接口和性能测试时,需安装特定插件并配置相关参数,如服务器地址、端口号等,还可通过CSV文件实现参数化,以满足不同测试需求。
1342 7
Jmeter实现WebSocket协议的接口测试方法
|
消息中间件 网络协议 安全
C# 一分钟浅谈:WebSocket 协议应用
【10月更文挑战第6天】在过去的一年中,我参与了一个基于 WebSocket 的实时通信系统项目,该项目不仅提升了工作效率,还改善了用户体验。本文将分享在 C# 中应用 WebSocket 协议的经验和心得,包括基础概念、C# 实现示例、常见问题及解决方案等内容,希望能为广大开发者提供参考。
1450 0