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

flydean 發表於 2021-10-08
WebSocket Netty

簡介

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

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

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

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