Netty案例介紹(websocket服務)

  本文我們來實現一個基於WebSocket協議的案例。

WebSocket案例

1.需求分析

  Http協議是無狀態的, 瀏覽器和服務器間的請求響應一次,下一次會重新創建連接.所有在有些情況下並不是太適用。這時websocket就是我們的一種實現方案,具體的websocket的內容網上很多,自行查閱哦,本文主要是介紹基於netty如何實現websocket通信。

要求

  1. 實現基於webSocket的長連接的全雙工的交互
  2. 改變Http協議多次請求的約束,實現長連接了, 服務器可以發送消息給瀏覽器
  3. 客戶端瀏覽器和服務器端會相互感知,比如服務器關閉了,瀏覽器會感知,同樣瀏覽器關閉了,服務器會感知

2.案例代碼實現

2.1 服務端處理器

  服務器處理器中我們僅僅需要處理請求和客戶端連接和端口的情況,具體如下:

package com.dpb.netty.websocket;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;


/**
 * @program: netty4demo
 * @description: 處理器
 * @author: 波波烤鴨
 * @create: 2019-12-30 22:39
 */
public class SocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        // 打印接收到的消息
        System.out.println("服務端接受到的消息:" + textWebSocketFrame.text());
        // 返回消息給客戶端
        channelHandlerContext.writeAndFlush(new TextWebSocketFrame("服務器時間: " + LocalDateTime.now() + "  : " + textWebSocketFrame.text()));
    }

    /**
     * 客戶端連接的時候觸發
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // LongText() 唯一的  ShortText() 不唯一
        System.out.println("handlerAdded:" + ctx.channel().id().asLongText());
        System.out.println("handlerAdded:" + ctx.channel().id().asShortText());
    }

    /**
     * 客戶端斷開連接的時候觸發
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:" + ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("異常發生了...");
        ctx.close();
    }
}

2.2 服務端

  服務端代碼在原有的基礎上需要轉換相關的協議,具體如下:

package com.dpb.netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

import java.net.ServerSocket;

/**
 * @program: netty4demo
 * @description: 基於WebSocket協議的服務端
 * @author: 波波烤鴨
 * @create: 2019-12-30 22:31
 */
public class SocketServerDemo {

    public static void main(String[] args) throws Exception {
        // 1.創建對應的EventLoopGroup對象
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try{
            bootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // websocket 相關的配置
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //因爲基於http協議,使用http的編碼和解碼器
                            pipeline.addLast(new HttpServerCodec());
                            //是以塊方式寫,添加ChunkedWriteHandler處理器
                            pipeline.addLast(new ChunkedWriteHandler());

                            /*
                            說明
                                1. http數據在傳輸過程中是分段, HttpObjectAggregator ,就是可以將多個段聚合
                                2. 這就就是爲什麼,當瀏覽器發送大量數據時,就會發出多次http請求
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));
                            /*
                            說明
                                1. 對應websocket ,它的數據是以 幀(frame) 形式傳遞
                                2. 可以看到WebSocketFrame 下面有六個子類
                                3. 瀏覽器請求時 ws://localhost:8888/hello 表示請求的uri
                                4. WebSocketServerProtocolHandler 核心功能是將 http協議升級爲 ws協議 , 保持長連接
                                5. 是通過一個 狀態碼 101
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                            // 自定義handler,處理業務邏輯
                            pipeline.addLast(new SocketServerHandler());

                        }
                    });
            System.out.println("服務啓動了....");
            ChannelFuture future = bootstrap.bind(8888).sync();
            future.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

2.3 客戶端實現

  在客戶端中我們就簡單的實現一個HTML頁面,在其中發送websocket協議相關的請求,然後接受相關的消息,如下,注意註釋

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket案例測試</title>
</head>
<body>
<script>
    var socket;
    //判斷當前瀏覽器是否支持websocket
    if(window.WebSocket) {
        //go on
        socket = new WebSocket("ws://localhost:8888/hello");
        //相當於channelReado, ev 收到服務器端回送的消息
        socket.onmessage = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data;
        }

        //相當於連接開啓(感知到連接開啓)
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = "連接開啓了.."
        }

        //相當於連接關閉(感知到連接關閉)
        socket.onclose = function (ev) {

            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + "連接關閉了.."
        }
    } else {
        alert("當前瀏覽器不支持websocket")
    }

    //發送消息到服務器
    function send(message) {
        if(!window.socket) { //先判斷socket是否創建好
            return;
        }
        if(socket.readyState == WebSocket.OPEN) {
            //通過socket 發送消息
            socket.send(message)
        } else {
            alert("連接沒有開啓");
        }
    }
</script>
<form onsubmit="return false">
    <textarea name="message" style="height: 300px; width: 300px"></textarea>
    <input type="button" value="發生消息" onclick="send(this.form.message.value)">
    <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
    <input type="button" value="清空內容" onclick="document.getElementById('responseText').value=''">
</form>
</body>
</html>

3.測試分析

  分別運行服務器和客戶端,訪問測試,如下:
在這裏插入圖片描述

在這裏插入圖片描述

運行效果:
在這裏插入圖片描述

同時在第一次運行的時候打開調試環境:

在這裏插入圖片描述

好了,本文我們就介紹到此,感興趣的可以自己運行下

歡迎關注點贊O(∩_∩)O哈哈~

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