netty系列之:分離websocket處理器

簡介

在上一篇文章中,我們使用了netty構建了可以處理websocket協議的伺服器,在這個伺服器中,我們構建了特製的handler用來處理HTTP或者websocket請求。

在一個handler中處理兩種不同的請求,對於某些有程式碼潔癖的人可能忍受不了。那麼,有沒有可能將普通的HTTP請求和websocket請求使用不同的handler來進行處理呢?答案是肯定的。

netty的消息處理

我們知道netty中所有的消息處理都是通過handler來實現的,為了方便起見,netty提供了一個簡單的消息處理類SimpleChannelInboundHandler,大家通過繼承它來重寫channelRead0方法即可:

protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

我們再看一下SimpleChannelInboundHandler的定義:

public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter

可以看到SimpleChannelInboundHandler本身是帶有泛型I的,而這個I就是我們要探討的方向。

如果我們要使用這個handler來處理所有的消息,那麼可以將I取值為Object。

如果我們只需要處理String消息,那麼可以這樣:

       public class StringHandler extends
               SimpleChannelInboundHandler<String> {
  
            @Override
           protected void channelRead0(ChannelHandlerContext ctx, String message)
                   throws Exception {
               System.out.println(message);
           }
       }

同樣的,如果要同時處理HTTP和WebSocket消息,只需要將I設置為不同的類型即可。

對於WebSocketFrame,我們有:

public class Server2FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> 

對於FullHttpRequest,我們有:

public class Server2HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> 

處理WebSocketFrame

對於WebSocketFrame消息,從上一節我們知道它有6種類型,分別是:

BinaryWebSocketFrame
CloseWebSocketFrame
ContinuationWebSocketFrame
PingWebSocketFrame
PongWebSocketFrame
TextWebSocketFrame

其中真正包含內容的是TextWebSocketFrame和BinaryWebSocketFrame,這裡我們對TextWebSocketFrame進行專門處理:

    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {

        if (frame instanceof TextWebSocketFrame) {
            // 將接收到的消息轉換成為大寫
            String request = ((TextWebSocketFrame) frame).text();
            ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.CHINA)));
        } else {
            String message = "不支援的Frame類型: " + frame.getClass().getName();
            throw new UnsupportedOperationException(message);
        }
    }

處理HTTP

對於HTTP請求中的FullHttpRequest,我們就安裝正常的HTTP服務請求的處理流程來就行。

這裡不做過多闡述。

編碼和解碼器

等等,我們是不是忘記了什麼東西?對,那就是編碼和解碼器。

在上一節中,我們使用的是WebSocketServerHandshaker來對websocket消息進行編碼和解碼。不過其實是放在我們自定義的hadler程式碼裡面的,使用起來略顯不優雅。

沒關係,netty為我們提供了一個WebSocketServerProtocolHandler類,專門負責websocket的編碼和解碼問題。

除了處理正常的websocket握手之外,WebSocketServerProtocolHandler類還為我們處理了Close, Ping, Pong這幾種通用的消息類型。而我們只需要專註於真正的業務邏輯消息即可,十分的方便。

對於剩下的Text或者Binary frame數據,會被交由pipline中的下一個handler進行處理。

其中Handshake有兩個狀態,分別是:

HANDSHAKE_COMPLETE 和 HANDSHAKE_TIMEOUT。

而HandshakeComplete又包含了requestUri,requestHeaders和selectedSubprotocol這三個方面的資訊。

最後,將WebSocketServerProtocolHandler加入到pipeline中,最終得到:

    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(new WebSocketServerCompressionHandler());
        pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true));
        pipeline.addLast(new Server2HttpHandler());
        pipeline.addLast(new Server2FrameHandler());
    }

總結

一個分離了HTTP請求和webSocket請求的伺服器就完成了。簡單直觀才是一個程式設計師追求的世界!

本文的例子可以參考:learn-netty4

本文已收錄於 //www.flydean.com/24-netty-websocket-server2/

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

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