簡介
在網速快速提升的時代,瀏覽器已經成為我們訪問各種服務的入口,很難想象如果離開了瀏覽器,我們的網絡世界應該如何運作。現在恨不得把操作系統都搬上瀏覽器。但是並不是所有的應用都需要瀏覽器來執行,比如服務器和服務器之間的通信,就需要使用到自建客戶端來和服務器進行交互。
本文將會介紹使用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/
最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!