慕課網_《Netty入門之WebSocket初體驗》學習總結

時間:2018年04月11日星期三
說明:本文部份內容均來自慕課網。@慕課網:https://www.imooc.com
教學源碼:https://github.com/zccodere/s...
學習源碼:https://github.com/zccodere/s...javascript

第一章:課程介紹

1-1 課程介紹

什麼是Nettyhtml

  • 高性能、事件驅動、異步非阻塞的IO Java開源框架
  • 基於NIO的客戶端,服務端編程框架
  • 很是可靠的穩定性和伸縮性

Netty使用場景java

  • 高性能領域:遊戲、大數據、分佈式計算
  • 多線程併發領域:多線程模型、主從多線程模型
  • 異步通訊領域:異步非阻塞,主動獲取或經過通知機制來獲得結果

課程提綱git

  • IO通訊:BIO、僞異步IO、NIO、AIO通訊
  • Netty入門:原生NIO的缺陷、Netty的優點
  • WebSocket入門:什麼是WebSocket、如何創建鏈接、生命週期及關閉
  • Netty實現WebSocket通訊案例

課程要求github

  • 有必定的Java基礎
  • 有必定的IO編程基礎
  • 瞭解Java的BIO、僞異步IO、NIO和AIO

第二章:IO通訊

2-1 IO通訊

BIO通訊web

  • 一個線程負責鏈接
  • 一請求一應答
  • 缺少彈性伸縮能力

BIO通訊模型apache

clipboard.png

僞異步IO通訊編程

  • 線程池負責鏈接
  • M請求N應答
  • 線程池阻塞

僞異步IO通訊模型bootstrap

clipboard.png

NIO通訊瀏覽器

  • 緩衝區Buffer
  • 通道Channel
  • 多路複用器Selector

AIO通訊

  • 鏈接註冊讀寫事件和回調函數
  • 讀寫方法異步
  • 主動通知程序

四種IO對比

clipboard.png

第三章:Netty入門

3-1 Netty入門

原生NIO的缺陷

  • 類庫和API繁雜
  • 入門門檻高
  • 工做量和難度大
  • JDK NIO存在BUG

Netty的優點

  • API使用簡單,定製能力強,能夠經過ChannelHandler對框架進行靈活的擴展
  • 入門門檻低,功能強大,預製了多種編解碼功能,支持多種主流協議
  • 性能高,經過與其餘的業界主流的NIO框架對比,Netty的綜合性能最優
  • Netty比較成熟穩定,Netty修復了JDK NIO全部發現的BUG

第四章:WebSocket入門

4-1 WebSocket入門

什麼是WebSocket

  • H5協議規範:H5提出的協議規範
  • 握手機制:使客戶端與服務器可以創建相似TCP的鏈接,方便通訊
  • 解決客戶端與服務端實時通訊而產生的技術:基於TCP的協議

WebSocket的優勢

  • 節省通訊開銷:之前使用輪詢,開銷較大
  • 服務器主動傳送數據給客戶端:任意時刻,相互傳送數據
  • 實時通訊:能夠彼此相互推送信息

WebSocket創建鏈接

  • 客戶端發起握手請求
  • 服務器響應請求
  • 鏈接創建

WebSocket生命週期

  • 打開事件:發生新的鏈接時調用、在端點上創建鏈接時且在任何事件以前
  • 消息事件:接收WebSocket對話中,另外一端發送的消息
  • 錯誤事件:在進行鏈接或端點發生錯誤時產生
  • 關閉事件:表示WebSocket端點的鏈接關閉

WebSocket關閉鏈接

  • 服務器關閉底層TCP鏈接
  • 客戶端發起TCP Close

第五章:通訊案例

5-1 通訊案例

基於Netty實現WebSocket通訊案例

功能介紹

  • Netty開發服務端
  • Html實現客戶端
  • 實現服務端與客戶端的實時交互

代碼編寫

1.建立名爲netty-websocket的maven工程pom以下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.myimooc</groupId>
    <artifactId>netty-websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

2.編寫NettyConfig類

package com.myimooc.netty.websocket;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * <br>
 * 標題: Netty 全局配置類<br>
 * 描述: 存儲整個工程的全局配置<br>
 *
 * @author zc
 * @date 2018/04/11
 */
public class NettyConfig {

    /**
     * 存儲每個客戶端接入進來時的 Channel
     */
    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

}

3.編寫MyWebSocketHandler類

package com.myimooc.netty.websocket;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;

import java.util.Date;

/**
 * <br>
 * 標題: 處理客戶端WebSocket請求的核心業務處理類<br>
 * 描述: 接收/處理/響應 客戶端websocket請求的核心業務處理類<br>
 *
 * @author zc
 * @date 2018/04/11
 */
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;
    private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket";

    /**
     * 服務端處理客戶端websocket請求的核心方法
     */
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            // 處理客戶端向服務端發起http握手請求的業務
            FullHttpRequest request = (FullHttpRequest) msg;
            this.handHttpRequest(ctx, request);
        } else if (msg instanceof WebSocketFrame) {
            // 處理websocket鏈接的業務
            WebSocketFrame frame = (WebSocketFrame) msg;
            this.handWebSocketFrame(ctx, frame);
        }
    }

    /**
     * 處理客戶端與服務端以前的websocket業務
     */
    private void handWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        if (frame instanceof CloseWebSocketFrame){
            // 若是是關閉websocket的指令
            handshaker.close(ctx.channel(),(CloseWebSocketFrame)frame.retain());
        }
        if (frame instanceof PingWebSocketFrame){
            // 若是是ping消息
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if (!(frame instanceof TextWebSocketFrame)){
            // 若是不是文本消息,則拋出異常
            System.out.println("目前暫不支持二進制消息");
            throw  new RuntimeException("【"+this.getClass().getName()+"】不支持二進制消息");
        }

        // 獲取客戶端向服務端發送的文本消息
        String request = ((TextWebSocketFrame) frame).text();
        System.out.println("服務端收到客戶端的消息=====>>>" + request);

        // 將客戶端發給服務端的消息返回給客戶端
        TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + "====>>>" + request);

        // 羣發,服務端向每一個鏈接上來的客戶端羣發消息
        NettyConfig.group.writeAndFlush(tws);
    }


    /**
     * 處理客戶端向服務端發起http握手請求的業務
     */
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        if (!request.getDecoderResult().isSuccess() || !("websocket").equals(request.headers().get("Upgrade"))) {
            // 不是websocket握手請求時
            this.sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
        handshaker = wsFactory.newHandshaker(request);

        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), request);
        }
    }

    /**
     * 服務端向客戶端響應消息
     */
    private void sendHttpResponse(ChannelHandlerContext ctc, FullHttpMessage request, DefaultFullHttpResponse response) {
        if (response.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
        }

        // 服務端向客戶端發送數據
        ChannelFuture future = ctc.channel().writeAndFlush(response);
        if (response.getStatus().code() != 200) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 工程出現異常時調用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 客戶端與服務端建立鏈接時調用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.add(ctx.channel());
        System.out.println("客戶端與服務端鏈接開啓...");
    }

    /**
     * 客戶端與服務端斷開鏈接時調用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyConfig.group.remove(ctx.channel());
        System.out.println("客戶端與服務端鏈接關閉...");
    }

    /**
     * 服務端接收客戶端發送過來的數據結束以後調用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

4.編寫MyWebSocketChannelHandler類

package com.myimooc.netty.websocket;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;


/**
 * <br>
 * 標題: 初始化鏈接時的各個組件<br>
 * 描述: 初始化鏈接時的各個組件<br>
 *
 * @author zc
 * @date 2018/04/11
 */
public class MyWebSocketChannelHandler extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        // 將請求和應答消息解碼爲HTTP消息
        ch.pipeline().addLast("http-codec",new HttpServerCodec());
        // 將HTTP消息的多個部分合成一條完整的HTTP消息
        ch.pipeline().addLast("aggregator",new HttpObjectAggregator(65536));
        // 向客戶端發送HTML5文件
        ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
        ch.pipeline().addLast("handler",new MyWebSocketHandler());
    }
}

5.編寫AppStart類

package com.myimooc.netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

/**
 * <br>
 * 標題: 程序入口<br>
 * 描述: 啓動應用<br>
 *
 * @author zc
 * @date 2018/04/11
 */
public class AppStart {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childHandler(new MyWebSocketChannelHandler());
            System.out.println("服務端開啓等待客戶端鏈接...");

            Channel channel = serverBootstrap.bind(8888).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 優雅的退出程序
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

}

6.編寫websocket.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="content-type" content="text/html"/>
    <title>WebSocket客戶端</title>

    <script type="text/javascript">
        var socket;
        if (!window.WebSocket) {
            window.WebSocket = window.MozWebSocket;
        }
        if (window.WebSocket) {
            socket = new WebSocket("ws://localhost:8888/websocket");
            socket.onmessage = function (event) {
                var ta = document.getElementById("responseContent");
                ta.value += event.data + "\r\n";
            };
            socket.onopen = function (event) {
                var ta = document.getElementById("responseContent");
                ta.value = "您當前的瀏覽器支持 WebSocket,請進行後續操做\r\n";
            };
            socket.onclose = function (event) {
                var ta = document.getElementById("responseContent");
                ta.value = "";
                ta.value = "WebSocket 鏈接已近關閉\r\n";
            };
        } else {
            alert("您的瀏覽器不支持 WebSocket");
        }

        function send(msg) {
            if (!window.WebSocket) {
                return;
            }
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(msg);
            } else {
                alert("WebSocket鏈接沒有創建成功");
            }
        }

    </script>

</head>
<body>

<form onSubmit="return false;">
    <input type="text" name="msg" value=""/>
    <br/>
    <br/>
    <input type="button" value="發送WebSocket請求消息" onclick="send(this.form.msg.value)"/>
    <hr color="red"/>
    <h2>客戶端接收到服務端返回的應答消息</h2>
    <textarea id="responseContent" style="width: 1024px;height: 300px"></textarea>
</form>

</body>
</html>

第六章:課程總結

6-1 課程總結

課程總結

  • 課程介紹
  • IO通訊:四種IO通訊
  • Netty入門:原生NIO的缺點,Netty的優勢
  • WebSocket入門:WebSocket的優勢,如何使用
  • 通訊案例:Netty實現WebSocket通訊案例
相關文章
相關標籤/搜索