基於Netty的WebSocket

WebSocket


Http是無狀態的,瀏覽器每次請求,都是創建一個新連接,傳輸完畢即斷開。雙方並不能感知對方的狀態。
而WebSocket是長連接,一次TCP握手,即可建立持久性的連接,並且雙方能感知到對方的狀態。

核心代碼:

// 因爲基於http,所以要添加http編碼和解碼器
pipeline.addLast(new HttpServerCodec());
// 以塊的方式寫,添加ChunkedWriteHandler
pipeline.addLast(new ChunkedWriteHandler());
// http數據傳輸過程中是分段的
// HttpObjectAggregator將多個段聚合起來
pipeline.addLast(new HttpObjectAggregator(4096));
// WebSocket以幀的方式傳遞(frame)
// 指定webSocketPath 對應瀏覽器地址ws://localhost/hello
// 將http協議升級爲WebSocket協議,保持長連接
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
  • HttpServerCodec :Http編碼和解碼器

  • ChunkedWriteHandler :以塊的方式寫數據

  • HttpObjectAggregator(4096) :將多個段聚合起來,最大長度4096

  • WebSocketServerProtocolHandler將http協議升級爲WebSocket協議,通過狀態碼101切換的。101(switch protocols)。構造方法參數是ws的路徑

  • WebSocket傳輸數據以frame(幀)的形式傳遞:

WebSocketFrame
WebSocketFrame及其子類


附代碼

Server

		NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try{

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .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(4096));
                            // WebSocket以幀的方式傳遞(frame)
                            // 指定webSocketPath 對應瀏覽器地址ws://localhost/hello
                            // 將http協議升級爲WebSocket協議,保持長連接
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                            // 自定義的WebSocket處理器
                            pipeline.addLast(new WebSocketHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

ServerHandler:

// 泛型表示一個文本幀
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println(msg.text());
        ctx.writeAndFlush(new TextWebSocketFrame("服務器時間 : "+Instant.now() + msg.text()));
    }

    // web連接即觸發
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handler add 被觸發 " + ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handler remove被調用" + ctx.channel().id().asShortText());
    }

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

html頁面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello,WebSocket</title>
</head>
<body>
<script>
    var socket;
    if (window.WebSocket){
        socket = new WebSocket("ws://localhost:7000/hello")
        socket.onmessage = function (ev) {
            var text = document.getElementById('repText')
            text.value = text.value +"\n" + ev.data
        }

        socket.onopen = function (ev) {
            var text = document.getElementById('repText')
            text.value = "連接開啓"
        }

        socket.onclose = function (ev) {
            var text = document.getElementById('repText')
            text.value = text.value +"\n" +"連接關閉"
        }
        
        function send(text) {
            if (!window.socket){
                return
            }
            if (socket.readyState == WebSocket.OPEN){
                socket.send(text)
            }else{
                alert("連接未開啓")
            }
        }

    }else {
        alert("瀏覽器不辭職WebSocket")
    }
</script>
    <form onsubmit="return false">
        <textarea name="msg" style="width: 300px;height: 300px"></textarea>
        <input type="button" value="發送" onclick="send(this.form.msg.value)">
        <textarea id="repText" style="width: 300px;height: 300px"></textarea>
        <input type="button" value="清空" onclick="document.getElementById('repText').value=''">
    </form>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章