簡介
在網速快速提升的時代,瀏覽器已經成為我們訪問各種服務的入口,很難想象如果離開了瀏覽器,我們的網路世界應該如何運作。現在恨不得把作業系統都搬上瀏覽器。但是並不是所有的應用都需要瀏覽器來執行,比如伺服器和伺服器之間的通訊,就需要使用到自建客戶端來和伺服器進行互動。
本文將會介紹使用netty客戶端連線websocket的原理和具體實現。
瀏覽器客戶端
在介紹netty客戶端之前,我們先看一個簡單的瀏覽器客戶端連線websocket的例子:
// 建立連線
const socket = new WebSocket('ws://localhost:8000');
// 開啟連線
socket.addEventListener('open', function (event) {
socket.send('沒錯,開啟了!');
});
// 監聽訊息
socket.addEventListener('message', function (event) {
console.log('監聽到伺服器的訊息 ', event.data);
});
這裡使用了瀏覽器最通用的語言javascript,並使用了瀏覽器提供的websocket API進行操作,非常的簡單。
那麼用netty客戶端實現websocket的連線是否和javascript使用一樣呢?我們一起來探索。
netty對websocket客戶端的支援
先看看netty對websocket的支援類都有哪些,接著我們看下怎麼具體去使用這些工具類。
WebSocketClientHandshaker
和websocket server一樣,client中最核心的類也是handshaker,這裡叫做WebSocketClientHandshaker。這個類有什麼作用呢?一起來看看。
這個類主要實現的就是client和server端之間的握手。
我們看一下它的最長引數的構造類:
protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String subprotocol,
HttpHeaders customHeaders, int maxFramePayloadLength,
long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl)
引數中有websocket連線的URI,像是:"ws://flydean.com/mypath"。
有請求子協議的型別subprotocol,有自定義的HTTP headers:customHeaders,有最大的frame payload的長度:maxFramePayloadLength,有強制timeout關閉的時間,有使用HTTP協議進行升級的URI地址。
怎麼建立handshaker呢?同樣的,netty提供了一個WebSocketClientHandshakerFactory方法。
WebSocketClientHandshakerFactory提供了一個newHandshaker方法,可以方便的建立各種不同版本的handshaker:
if (version == V13) {
return new WebSocketClientHandshaker13(
webSocketURL, V13, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis);
}
if (version == V08) {
return new WebSocketClientHandshaker08(
webSocketURL, V08, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis);
}
if (version == V07) {
return new WebSocketClientHandshaker07(
webSocketURL, V07, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis);
}
if (version == V00) {
return new WebSocketClientHandshaker00(
webSocketURL, V00, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis);
}
可以看到,根據傳入協議版本的不同,可以分為WebSocketClientHandshaker13、WebSocketClientHandshaker08、WebSocketClientHandshaker07、WebSocketClientHandshaker00這幾種。
WebSocketClientCompressionHandler
通常來說,對於webSocket協議,為了提升傳輸的效能和速度,降低網路頻寬佔用量,在使用過程中通常會帶上額外的壓縮擴充套件。為了處理這樣的壓縮擴充套件,netty同時提供了伺服器端和客戶端的支援。
對於伺服器端來說對應的handler叫做WebSocketServerCompressionHandler,對於客戶端來說對應的handler叫做WebSocketClientCompressionHandler。
通過將這兩個handler加入對應pipline中,可以實現對websocket中壓縮協議擴充套件的支援。
對於協議的擴充套件有兩個級別分別是permessage-deflate和perframe-deflate,分別對應PerMessageDeflateClientExtensionHandshaker和DeflateFrameClientExtensionHandshaker。
至於具體怎麼壓縮的,這裡就不詳細進行講解了, 感興趣的小夥伴可以自行了解。
netty客戶端的處理流程
前面講解了netty對websocket客戶端的支援之後,本節將會講解netty到底是如何使用這些工具進行訊息處理的。
首先是按照正常的邏輯建立客戶端的Bootstrap,並新增handler。這裡的handler就是專門為websocket定製的client端handler。
除了上面提到的WebSocketClientCompressionHandler,就是自定義的handler了。
在自定義handler中,我們需要處理兩件事情,一件事情就是在channel ready的時候建立handshaker。另外一件事情就是具體websocket訊息的處理了。
建立handshaker
首先使用WebSocketClientHandshakerFactory建立handler:
TestSocketClientHandler handler =
new TestSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
然後在channel active的時候使用handshaker進行握手連線:
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
然後在進行訊息接收處理的時候還需要判斷handshaker的狀態是否完成,如果未完成則呼叫handshaker.finishHandshake方法進行手動完成:
if (!handshaker.isHandshakeComplete()) {
try {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
log.info("websocket Handshake 完成!");
handshakeFuture.setSuccess();
} catch (WebSocketHandshakeException e) {
log.info("websocket連線失敗!");
handshakeFuture.setFailure(e);
}
return;
}
當handshake完成之後,就可以進行正常的websocket訊息讀寫操作了。
websocket訊息的處理
websocket的訊息處理比較簡單,將接收到的訊息轉換成為WebSocketFrame進行處理即可。
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
log.info("接收到TXT訊息: " + textFrame.text());
} else if (frame instanceof PongWebSocketFrame) {
log.info("接收到pong訊息");
} else if (frame instanceof CloseWebSocketFrame) {
log.info("接收到closing訊息");
ch.close();
}
總結
本文講解了netty提供的websocket客戶端的支援和具體的對接流程,大家可以再次基礎上進行擴充套件,以實現自己的業務邏輯。
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/25-netty-websocket-client/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!