亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Netty WebSocket 協議

1. 前言

上一節,我們主要講解了 Http 協議,本節我們來講解 Netty 支持的另外一個 WebSocket 協議,WebSocket 是 HTML5 開始支持的全雙工協議。

在真實的開發當中,其實 WebSocket 使用非常的廣泛,特別是在目前前端技術越來越完善的情況下,很多應用都是基于前端技術去實現,比如:APP、小程序等主流應用,前端的技術完全可以開發出類似原生技術一樣的產品,并且開發效率上比原生更加的快、用戶體驗更好。
這些應用涉及通信、聊天、推送等業務,則可以使用 WebSocket 來實現,因此,WebSocket 已經是目前非常主流的瀏覽器和服務端建立長連接的通信技術。

WebSocket 協議架構圖如下所示:
圖片描述

2. 學習目的

Netty 雖然可以開發客戶端和服務端的應用,但是 Netty 和 WebSocket 結合則是另外一個不同的應用方向。

應用方向一: 純后端的應用,比如:框架、中間件通信等,則完全可以使用 Netty 來開發客戶端和服務端,并且雙方通過 TCP 協議來進行通信。

應用方向二: 前端(瀏覽器)和服務端之間通信,并且想實現類似長連接的效果,那么 WebSocket 和 Netty 則是主流,并且這部分的應用場景非常的廣泛和運用非常的多。

因此,我們在學習完本章內容之后,我們就可以掌握這類的需求的開發(比如:聊天、消息推送),自己可以嘗試去開發一款小程序在線聊天系統。

當真實的開發當中,也許很多人直接使用 Spring 封裝的 WebSocket 或者其它第三方的框架(比如:netty-socketio)去實現。本節我們主要講解原生 Netty 來開發 WebSocket 協議格式的數據請求,畢竟會用和知道知其所以然還是有區別的。

3. WebSocket 的 API

創建 WebSocket 對象,第一個參數 url, 指定連接的 URL。第二個參數 protocol 是可選的,指定了可接受的子協議。

var Socket = new WebSocket(url, [protocol] );

WebSocket 事件

事件 事件處理程序 描述
open Socket.onopen 連接建立時觸發
message Socket.onmessage 客戶端接收服務端數據時觸發
error Socket.onerror 通信發生錯誤時觸發
close Socket.onclose 連接關閉時觸發

4. 環境搭建

本節,我們主要開發一個 Demo,主要功能如下:

  1. 前端頁面(html)啟動的時候,連接 WebSocket 服務端;
  2. 服務端往前端每隔 5s 推送一條消息。

環境搭建步驟:

  1. 創建一個 Maven 項目;
  2. 導入 Netty 坐標。

實例:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.6.Final</version>
</dependency>

5. 代碼實現

5.1. Netty 主啟動類

public class MyWebSocketServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();

                    //因為基于http協議,使用http的編碼和解碼器
                    pipeline.addLast(new HttpServerCodec());

                    //是以塊方式寫,添加ChunkedWriteHandler處理器
                    pipeline.addLast(new ChunkedWriteHandler());
                    
                    //http數據在傳輸過程中是分段, HttpObjectAggregator ,就是可以將多個段聚合
                    pipeline.addLast(new HttpObjectAggregator(8192));
                    
                    //將 http協議升級為 ws協議 , 保持長連接
                    pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));
                    
                    //自定義的handler ,處理業務邏輯
                    pipeline.addLast(new MyWebSocketHandler());
                }
            });

            //啟動服務器
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

代碼說明:

  1. WebSocket 比 TCP 和 Http 協議都稍微復雜有點,它其實是 TCP 和 Http 協議的結合,首先是連接之前發送的是 Http 協議請求,但是新增 Http 所沒有的附加頭信息 Upgrade: WebSocket 表明是一個申請協議升級的 Http 請求。其次,真正建立連接之后,其實底層是 TCP 協議長連接;
  2. HttpServerCodec 將請求和應答消息解碼為 HTTP 消息;
  3. ChunkedWriteHandler 向客戶端發送 HTML5 文件;
  4. http 數據在傳輸過程中是分段,當瀏覽器發送大量數據時,就會發出多次 http 請求, HttpObjectAggregator 可以將 HTTP 消息的多個部分合成一條完整的 HTTP 消息;
  5. WebSocketServerProtocolHandler 定義了 WebSocket 的對外暴露地址。

5.2 WebSocket 業務類

public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
    private SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //1.獲取Channel通道
        final Channel channel=ctx.channel();
        
        //2.創建一個定時線程池
        ScheduledExecutorService ses=Executors.newScheduledThreadPool(1);
        //3.一秒鐘之后只需,并且每隔5秒往瀏覽器發送數據
        ses.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                String sendTime=format.format(new Date());
                channel.writeAndFlush(new TextWebSocketFrame("推送時間=" + sendTime));
            }
        },1,5, TimeUnit.SECONDS);
    }

    //接受瀏覽器消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        System.out.println("收到消息 " + msg.text());
    }

    //當web客戶端連接后,觸發方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
		
    }

    //當web客戶端斷開后,觸發方法
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

    }
}

代碼說明:

其實 WebSocket 對于的 Handler 跟我們普通業務的 Handler 沒有什么區別,這里主要使用定時線程池定時往瀏覽器推送消息,這個是傳統的 Http+Ajax 請求無法實現的逆向推送效果。

5.3. 前端代碼

function WebSocketTest() {
    if ("WebSocket" in window) {
        var ws = new WebSocket("ws://localhost:7000/hello2");
        
        //發送數據
        ws.onopen = function() {
            ws.send("發送數據");
        };

        //接受數據
        ws.onmessage = function(evt) {
            var received_msg = evt.data;
            console.log(">>>"+received_msg)
        };

        //監聽連接關閉
        ws.onclose = function() {
            alert("連接已關閉...");
        };
    } else {
        alert("您的瀏覽器不支持 WebSocket!");
    }
}

5.4 測試效果

瀏覽器控制臺每隔 5s 鐘就能收到服務端推送過來的消息,如下圖所示:

圖片描述

6. 小結

  1. 我們需要了解 WebSocket 的應用方向,主要是前端和服務端建立長連接通信;
  2. 掌握 WebSocket 幾個事件監聽方法,open ()、message ()、error ()、close ();
  3. 掌握 Netty 編寫的服務端寫法,主要是幾個類 HttpServerCodec、ChunkedWriteHandler、HttpObjectAggregator、WebSocketServerProtocolHandler,Handler 業務跟普通業務沒啥區別。