简介

近年来随着 Web 前端的快速发展,浏览器新特性层出不穷,越来越多的应用可以在浏览器端或通过浏览器渲染引擎实现,Web 应用的即时通信方式 WebSocket 得到了广泛的应用。

WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 通信协议于2011年被 IETF 定为标准 RFC 6455,并由 RFC 7936 补充规范。WebSocket API 也被 W3C 定为标准。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 —— 摘自 维基百科 WebSocket

MQTT 协议第 6 章详细约定了 MQTT 在 WebSocket [RFC6455] 连接上传输需要满足的条件

我们可以使用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的有效性

EMQ ---websocket的更多相关文章

  1. EMQ、Websocket、MQTT

    mqtt.fx的安装和使用 https://blog.csdn.net/nicholaszao/article/details/79211965 EMO 使用说明 http://emqtt.com/d ...

  2. 使用EMQ搭建MQTT服务器

    前言寒假的时候开始搭建mqtt服务器,一开始使用的是RabbitMQ,基于Erlang语言.但是RabbitMQ的本职工作是AMQP,MQTT只是他的一个插件功能,似乎有些大材小用,很多MQTT的功能 ...

  3. 使用 WebSocket 客户端连接 MQTT 服务器

    简介 近年来随着 Web 前端的快速发展,浏览器新特性层出不穷,越来越多的应用可以在浏览器端或通过浏览器渲染引擎实现,Web 应用的即时通信方式 WebSocket 得到了广泛的应用. WebSock ...

  4. 物联网架构成长之路(6)-EMQ权限控制

    1. 前言 EMQTT属于一个比较小众的开源软件,很多资料不全,很麻烦,很多功能都是靠猜测,还有就是看官方提供的那几个插件,了解. 2. 说明 上一小节的插件 emq_plugin_wunaozai ...

  5. EMQ ---问题集

    1)emqttd 使用 SSL遇到的问题:服务器直接布了一份emqttd ,然后什么都没管,端口默认的ws 8083,wss8084,mqtt 1883,mqtt(ssl) 8883. 结果跑起来之后 ...

  6. centos7(vmware install) 安装EMQ注意事项 ---控制台远程访问

    若想远端访问控制台,需打开对于端口 TCP 服务端口占用 EMQ 2.0 消息服务器默认占用的 TCP 端口包括: 1883 MQTT 协议端口 8883 MQTT/SSL 端口 8083 MQTT/ ...

  7. emq(centos 7) 使用

    配置文件: EMQ 配置文件: /etc/emqttd/emq.conf 插件配置文件: /etc/emqttd/plugins/*.conf 日志文件 日志文件目录: /var/log/emqttd ...

  8. [emqttd] (EMQ)

    [emqttd] (EMQ)是采用Erlang语言开发,全面支持MQTT V3.1.1协议,支持集群和大规模连接的开源MQTT消息服务器. [emqttd]致力于发布一个基于Erlang/OTP语言平 ...

  9. 转:EMQ(emqttd) 2.x 安装和使用(物联网传输控制协议的Broker)

    支持下国产开源. MQTT物联网传输控制协议:<MQTT-3.1.1-CN.pdf> 下载:emqttd-centos64-v2.0-rc.2-20161019.zip 安装: $ unz ...

随机推荐

  1. JVM调优总结 -Xms -Xmx -Xmn -Xss等

                  首先介绍一下新生代.老年代.所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区.加上老年代就这三个区.数据会首先分配到Eden区 ...

  2. Tasker, Android系统增强神器

    Tasker是一个让系统根据用户定制的”配置文件”(Profiles),在特定的”背景”下(Contexts),执行指定”任务”(Tasks)的软件, 除此之外,它还提供”可供点击”的(Clickab ...

  3. jQuery向父辈遍历的方法

      通过DOM树可以可容易的访问到html文档中的所有元素 例如向上访问父辈的元素有以下方法 1.parent()方法可以得到所定元素的直接父元素 $("span").parent ...

  4. arcengine帮助http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/what_s_new_for_developers_at_10_/0001000002zp000000/

    http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/what_s_new_for_develope ...

  5. C++入门级 一

    如果您想学习电脑编程,却又不知从何入手,那么您不妨看看下面的几种学习方案,可能会给您一些启示吧! 方案一 Basic语言 & Visual Basic 优点 (1)Basic 简单易学,很容易 ...

  6. Silverlight:《Pro Silverlight5》读书笔记 之 Dependency Properties And Routed Event

    Dependency Properties And Routed Event Dependency Properties Dynamic Value Resolution As you’ve alre ...

  7. [翻译] TSActivityIndicatorView 自定义指示器

    TSActivityIndicatorView 自定义指示器 https://github.com/tomkowz/TSActivityIndicatorView TSActivityIndicato ...

  8. 《C++反汇编与逆向分析技术揭秘》之十——析构函数

    局部对象 当对象所在作用域结束之后,销毁栈空间,此时析构函数被调用. 举例: 函数返回时自动调用析构函数: 堆对象 调用析构代理函数来处理析构函数: 为什么使用析构代理函数来调用析构函数?考虑到如果d ...

  9. jQuery 对象和 DOM 对象

    jQuery(DOM对象) 或者 $(DOM对象) 此函数的作用是将DOM对象,转换为jQuery的对象 DOM对象其实就是javascript的函数对象,可以用来操作所有HTML元素.比如: a标签 ...

  10. mongodb,redis,memcached,mysql对比

    1.性能都比较高,性能对我们来说应该都不是瓶颈总体来讲,TPS方面redis和memcache差不多,要大于mongodb 2.操作的便利性memcache数据结构单一redis丰富一些,数据操作方面 ...