netty系列之:使用netty搭建websocket客戶端

簡介

在網速快速提升的時代,瀏覽器已經成爲我們訪問各種服務的入口,很難想象如果離開了瀏覽器,我們的網絡世界應該如何運作。現在恨不得把操作系統都搬上瀏覽器。但是並不是所有的應用都需要瀏覽器來執行,比如服務器和服務器之間的通信,就需要使用到自建客戶端來和服務器進行交互。

本文將會介紹使用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/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注我的公衆號:「程序那些事」,懂技術,更懂你!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章