前言
MQTT協(xié)議專注于網(wǎng)絡(luò)、資源受限環(huán)境,建立之初不曾考慮WEB環(huán)境,倒也正常。雖然如此,但不代表它不適合HTML5環(huán)境。
HTML5 Websocket是建立在TCP基礎(chǔ)上的雙通道通信,和TCP通信方式很類似,適用于WEB瀏覽器環(huán)境。雖然MQTT基因?qū)用孢x擇了TCP作為通信通道,但我們添加個(gè)編解碼方式,MQTT Over Websocket也可以的。
這樣做的好處,MQTT的使用范疇被擴(kuò)展到HTML5、桌面端瀏覽器、移動(dòng)端WebApp、Hybrid等,多了一些想像空間。這樣看來(lái),無(wú)論是移動(dòng)端,還是WEB端,MQTT都會(huì)有自己的使用空間。
瀏覽器支持
話說(shuō),現(xiàn)代化瀏覽器都已經(jīng)支持Websocket,這里有一個(gè)所有瀏覽器支持列表:

更詳細(xì)列表,請(qǐng)直接訪問(wèn):http://caniuse.com/websockets
毫無(wú)疑問(wèn),火狐和谷歌瀏覽器帶動(dòng)了現(xiàn)代瀏覽器的發(fā)展,對(duì)HTML5標(biāo)準(zhǔn)的支持也是如此。支持Websocket的瀏覽器單純從上面數(shù)字來(lái)講,73.88%的支持率。但實(shí)際上還得參考瀏覽器市場(chǎng)占有率:
![1[1] 1[1]](http://www.aygfsteel.com/images/blogjava_net/yongboy/Windows-Live-Writer/2370eb1af277_10D86/1%5B1%5D_thumb_1.png)
上圖數(shù)據(jù),來(lái)源于: 2014年4月份全球主流瀏覽器市場(chǎng)份額排行榜
超過(guò)60%用戶機(jī)器上瀏覽器的支持Websocket,數(shù)據(jù)很可觀。
移動(dòng)hybrid型應(yīng)用會(huì)很歡迎Websocket
- 內(nèi)置瀏覽器支持HTML5,Javascript操作Websocket連接MQTT
- 借助于原生TCP socket通道連接MQTT服務(wù)器,暴露JavaScript接口,間接使用
不支持Websocket的桌面瀏覽器,可以考慮Flash socket來(lái)幫忙!
針對(duì)不支持websocker的部分歷史瀏覽器,可以考慮一下Flash socket,雖然使用Flashsocket用以模擬Websocket就很容易理解,但條件如下: - 需要單獨(dú)占用一個(gè)端口專用于安全跨域訪問(wèn)策略 - 需要瀏覽器支持二進(jìn)制Blob 支持二進(jìn)制操作的瀏覽器現(xiàn)狀:

來(lái)源于:http://caniuse.com/xhr2
比較一下支持Websocket和XHR2的桌面瀏覽器,重疊率很高,使用Flash Socket用以模擬Websocket必要性不大,在類似于IE平臺(tái)上,不如直接使用Flash版本的
https://github.com/yangboz/as3MQTT/tree/master/MQTTClient_AS3
不支持Websocket瀏覽器怎么辦
不是所有瀏覽器都支持Websocket,尤其是阻礙歷史發(fā)展的IE6/IE7/IE8/IE9。MQTT協(xié)議為二進(jìn)制協(xié)議壓根和HTTP純文本不兼容,尤其瀏覽器端JavaScript處理文本很合適,但二進(jìn)制就顯得笨手笨角,除非支持XHR2。
- 單獨(dú)使用Flash MQTT Client,這這方面見(jiàn)解不深,實(shí)踐很少,不便多說(shuō),您要是很了解,不妨告知一二。
- HTTP純文本方式進(jìn)行二進(jìn)制對(duì)接
這部分后面專門(mén)會(huì)講到。
服務(wù)器端支持
現(xiàn)有一些解決方案可能是后面為MQTT Broker,前面是添加一層代理。比如:例如 mod_websocket ,對(duì)應(yīng)在線示范:http://test.mosquitto.org/ws.html
表面上看著很解藕的,實(shí)際上模仿的還是傳統(tǒng)型的短連接反向代理架構(gòu):Nginx/Apache +Java/PHP/Python/Ruby。
客戶端建立一條連接,服務(wù)器端需要使用到至少兩個(gè)文件句柄,中間多了一層路徑。優(yōu)雅的解決方案,可以向socket.io看起。一套服務(wù)端程序,同時(shí)提供若干種協(xié)議供終端選擇。其實(shí),一臺(tái)MQTT Broker中間件服務(wù)器,可以綁定多個(gè)端口,一個(gè)面向純TCP的1883端口,一個(gè)面向Websocket的80/8080端口,共享基礎(chǔ)邏輯,面向不同協(xié)議。
Websocket協(xié)議適配
服務(wù)器添加對(duì)Websocket支持,基本不用做多大改動(dòng)。對(duì)比Tcp的附加到單個(gè)Channel的處理器列表:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
public class TcpChannelInitializer extends ChannelInitializer<SocketChannel> { |
|
|
|
@Override |
|
public void initChannel(SocketChannel ch) throws Exception { |
|
ChannelPipeline pipeline = ch.pipeline(); |
|
pipeline.addLast( |
|
new MqttMessageNewEncoder(), |
|
new MqttMessageNewDecoder(), |
|
new MqttMessageHandler()); |
|
} |
|
} |
Websocket對(duì)應(yīng)單個(gè)Channel的處理器列表:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
import io.mqtt.handler.HttpRequestHandler; |
|
import io.mqtt.handler.MqttMessageHandler; |
|
import io.mqtt.handler.coder.MqttMessageWebSocketFrameDecoder; |
|
import io.mqtt.handler.coder.MqttMessageWebSocketFrameEncoder; |
|
import io.mqtt.handler.http.HttpJsonpTransport; |
|
import io.netty.channel.ChannelInitializer; |
|
import io.netty.channel.socket.SocketChannel; |
|
import io.netty.handler.codec.http.HttpObjectAggregator; |
|
import io.netty.handler.codec.http.HttpServerCodec; |
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; |
|
|
|
public class WebsocketChannelInitializer extends ChannelInitializer<SocketChannel> { |
|
private final static String websocketUri = "/websocket"; |
|
|
|
private HttpRequestHandler httpRequestHandler = new HttpRequestHandler( |
|
websocketUri); |
|
|
|
static { |
|
HttpJsonpTransport httpJsonpTransport = new HttpJsonpTransport(); |
|
HttpRequestHandler.registerTransport(httpJsonpTransport); |
|
} |
|
|
|
@Override |
|
public void initChannel(final SocketChannel ch) throws Exception { |
|
ch.pipeline().addLast( |
|
new HttpServerCodec(), |
|
new MqttMessageWebSocketFrameEncoder(), |
|
new HttpObjectAggregator(65536), |
|
httpRequestHandler, |
|
new WebSocketServerProtocolHandler(websocketUri), |
|
new MqttMessageWebSocketFrameDecoder(), |
|
new MqttMessageHandler()); |
|
} |
|
} |
為了支持Websocket協(xié)議,僅僅額外增加了:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
@Sharable |
|
public class MqttMessageWebSocketFrameEncoder extends |
|
MessageToMessageEncoder<Message> { |
|
@Override |
|
protected void encode(ChannelHandlerContext ctx, Message msg, |
|
List<Object> out) throws Exception { |
|
if (msg == null) |
|
return; |
|
|
|
byte[] data = ((Message) msg).toBytes(); |
|
|
|
out.add(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(data))); |
|
} |
|
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
public class MqttMessageWebSocketFrameDecoder extends |
|
MessageToMessageDecoder<BinaryWebSocketFrame> { |
|
|
|
private MqttMessageNewDecoder messageNewDecoder; |
|
|
|
public MqttMessageWebSocketFrameDecoder() { |
|
messageNewDecoder = new MqttMessageNewDecoder(); |
|
} |
|
|
|
@Override |
|
protected void decode(ChannelHandlerContext ctx, |
|
BinaryWebSocketFrame wsFrame, List<Object> out) throws Exception { |
|
ByteBuf buf = wsFrame.content(); |
|
|
|
this.messageNewDecoder.decode(ctx, buf, out); |
|
} |
|
} |
小結(jié)
啰啰嗦嗦的講了一大通Websocket,總之對(duì)Websocket的支持還算容易。后面有時(shí)間寫(xiě)寫(xiě)如何使用HTTP協(xié)議達(dá)到MQTT OVER HTTP的效果。