简介

近年来随着 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. JMX操作实例--做一回技术控

    我来做一回技术控,这部分内容也是简单的API调用例子而已,做一回技术控,发点小骚文,不过你看了,也许知道JConsole是怎么做出来的了,呵呵! 先不用管他干什么,代码运行后,自己改改自然知道做什么的 ...

  2. JAVA多线程读写文件范例

    在写之前先声明,本文是基于之前在博客园网站上检索到的一份JAVA多线程读写文件的示例,我在写自己的程序时是在那位作者写的基础上做了改良,但已不记得原文的地址.如果有知情者,烦请帖出地址,我在此文上加入 ...

  3. Visual Studio 2015创建Shared Project时出错

    今天使用Visual Studio 2015创建共享项目的时候发现如下错误: 网上搜了一下,发现了同样有人问这个问题的问题:Why can't I create Shared Project in V ...

  4. Struts2 @ResultPath注释示例

    在Struts 2中, @ResultPath 注解用于控制Struts2找到存储的结果或JSP页面.默认情况下,它会找到结果页在 “WEB-INF/content/” 文件夹. 不知道为什么在Str ...

  5. Delegates and Events

    People often find it difficult to see the difference between events and delegates. C# doesn't help m ...

  6. 深入解析OkHttp3

    OkHttp是一个精巧的网络请求库,有如下特性: 
1)支持http2,对一台机器的所有请求共享同一个socket 
2)内置连接池,支持连接复用,减少延迟 
3)支持透明的gzip压缩响应体 
4) ...

  7. stl map中的lower_bound和 upper_bound

    map中的lower_bound和upper_bound的意思其实很简单,就两句话: map::lower_bound(key):返回map中第一个大于或等于key的迭代器指针 map::upper_ ...

  8. MFC中显示图像的放大、缩小、移动功能

    StretchBlt函数直接对图片进行放大,缩小,显示位置变换. 这个函数有两种形态一种全局函数是这样的:  BOOL StretchBlt(HDC hdcDest, int nXOriginDest ...

  9. 安装 ibm-java-x86_64-sdk-6.0-9.3.x86_64.rpm 的三步骤

    yum install libstdc++-devel.x86_64 yum install compat-libstdc++-33.x86_64 rpm -ivh ibm-java-x86_64-s ...

  10. js 判断页面元素是否存在

    转自:http://www.0fish.com/js/416 原生js写法案例: obj = document.getElementById("someID"); if (obj) ...