Netty詳解:Springboot整合Netty

小知識:21天效應
行爲心理學中,人們把一個人的新習慣或新理念的形成並得以鞏固至少需要21天的現象,稱之爲21天效應。也就是說,一個人的動作或想法,如果重複21天就會變成一個習慣性的動作或想法。

步驟

1 先寫好基本的Netty客戶端和Netty服務的代碼。參考文章【netty初識】
2.搭建好基本的Springboot項目。
3.將Netty服務端代碼的啓動代碼和關閉代碼分離,服務端加上@Component註解,交由Spring管理實例。
4.Springboot啓動時,將Netty服務給啓動;同時Springboot停止時,將Netty服務銷燬。

實現

Netty服務端

主要工作:
將Netty服務端代碼的啓動代碼和關閉代碼分離,服務端加上@Component註解,交由Spring管理實例

 

/**
 * 服務端
 * 1.創建一個ServerBootstrap的實例引導和綁定服務器。
 * 2.創建並分配一個NioEventLoopGroup實例以進行事件的處理,比如接受連接以及讀寫數據。
 * 3.指定服務器綁定的本地的InetSocketAddress。
 * 4.使用一個EchoServerHandler的實例初始化每一個新的Channel。
 * 5.調用ServerBootstrap.bind()方法以綁定服務器。
 */
@Slf4j
@Component
public class EchoServer {

    /**
     * NioEventLoop並不是一個純粹的I/O線程,它除了負責I/O的讀寫之外
     * 創建了兩個NioEventLoopGroup,
     * 它們實際是兩個獨立的Reactor線程池。
     * 一個用於接收客戶端的TCP連接,
     * 另一個用於處理I/O相關的讀寫操作,或者執行系統Task、定時任務Task等。
     */
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
    private Channel channel;
    /**
     * 啓動服務
     * @param hostname
     * @param port
     * @return
     * @throws Exception
     */
    public ChannelFuture  start(String hostname,int port) throws Exception {

        final EchoServerHandler serverHandler = new EchoServerHandler();
        ChannelFuture f = null;
        try {
            //ServerBootstrap負責初始化netty服務器,並且開始監聽端口的socket請求
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(hostname,port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
//                            爲監聽客戶端read/write事件的Channel添加用戶自定義的ChannelHandler
                            socketChannel.pipeline().addLast(serverHandler);
                        }
                    });

            f = b.bind().sync();
            channel = f.channel();
            log.info("======EchoServer啓動成功!!!=========");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (f != null && f.isSuccess()) {
                log.info("Netty server listening " + hostname + " on port " + port + " and ready for connections...");
            } else {
                log.error("Netty server start up Error!");
            }
        }
        return f;
    }

    /**
     * 停止服務
     */
    public void destroy() {
        log.info("Shutdown Netty Server...");
        if(channel != null) { channel.close();}
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        log.info("Shutdown Netty Server Success!");
    }
}

服務端業務處理handler

服務端的生命週期以及接收客戶端的信息收發和處理。這裏不建議使用阻塞的操作,容易影響netty的性能。

 

/***
 * 服務端自定義業務處理handler
 */
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 對每一個傳入的消息都要調用;
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf in = (ByteBuf) msg;
        System.out.println("server received: "+in.toString(CharsetUtil.UTF_8));

        ctx.write(in);
    }


    /**
     * 通知ChannelInboundHandler最後一次對channelRead()的調用時當前批量讀取中的的最後一條消息。
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 在讀取操作期間,有異常拋出時會調用。
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Springboot啓動服務端代碼

CommandLineRunner #run()
這裏主要是通過CommandLineRunner 接口的run方法,實現在項目啓動後執行的功能,SpringBoot提供的一種簡單的實現方案就是添加一個model並實現CommandLineRunner接口,實現功能的代碼放在實現的run方法中。

addShutdownHook()
而 Runtime.getRuntime().addShutdownHook(shutdownHook); 這個方法的意思就是在jvm中增加一個關閉的鉤子,當jvm關閉的時候,會執行系統中已經設置的所有通過方法addShutdownHook添加的鉤子,當系統執行完這些鉤子後,jvm纔會關閉。所以這些鉤子可以在jvm關閉的時候進行內存清理、對象銷燬等操作。

詳細代碼如下:

 

@SpringBootApplication
public class SpringNettyApplication implements CommandLineRunner {

    @Value("${netty.port}")
    private int port;

    @Value("${netty.url}")
    private String url;

    @Autowired
    private EchoServer echoServer;

    public static void main(String[] args) {
        SpringApplication.run(SpringNettyApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        ChannelFuture future = echoServer.start(url,port);
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                echoServer.destroy();
            }
        });
        //服務端管道關閉的監聽器並同步阻塞,直到channel關閉,線程纔會往下執行,結束進程
        future.channel().closeFuture().syncUninterruptibly();
    }
}

Netty客戶端

它在本文中的作用主要是爲了測試服務端是否正常運行,通過客戶端連接Netty的服務端,看到消息是否正常通信。

 

/**
 * 客戶端
 * 1.爲初始化客戶端,創建一個Bootstrap實例
 * 2.爲進行事件處理分配了一個NioEventLoopGroup實例,其中事件處理包括創建新的連接以及處理入站和出站數據;
 * 3.當連接被建立時,一個EchoClientHandler實例會被安裝到(該Channel的一個ChannelPipeline中;
 * 4.在一切都設置完成後,調用Bootstrap.connect()方法連接到遠程節點。
 */
public class EchoClient {

    private final String host;
    private final int port;


    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }


    /**
     * 運行流程:
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        new EchoClient("127.0.0.1",10010).start();
    }

    private void start() throws Exception {

        /**
         * Netty用於接收客戶端請求的線程池職責如下。
         * (1)接收客戶端TCP連接,初始化Channel參數;
         * (2)將鏈路狀態變更事件通知給ChannelPipeline
         */
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //綁定端口
            ChannelFuture f = b.connect().sync();

            f.channel().closeFuture().sync();
        } catch (Exception e) {
            group.shutdownGracefully().sync();
        }
    }
}

Netty客戶端業務處理類

主要是監控Netty客戶端的生命週期以及接收服務端的消息,往服務端發送消息等。

 


/**
 * 客戶端處理類
 */
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /**
     * 在到服務器的連接已經建立之後將被調用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks !", CharsetUtil.UTF_8));
    }

    /**
     * 當從服務器接收到一個消息時被調用
     * @param channelHandlerContext
     * @param byteBuf
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("Client received: "+ byteBuf.toString(CharsetUtil.UTF_8));
    }

    /**
     * 在處理過程中引發異常時被調用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

總結

從整體來看,只不過是將Netty服務端從main函數啓動方式改爲交給Spring來管理啓動和銷燬的工作,也就說以後你有個什麼代碼要交給Spring管理的也是可以這樣子處理。

最後

如果對 Java、大數據感興趣請長按二維碼關注一波,我會努力帶給你們價值。覺得對你哪怕有一丁點幫助的請幫忙點個贊或者轉發哦。
關注公衆號【愛編碼】,小編會一直更新文章的哦。



作者:xbmchina
鏈接:https://www.jianshu.com/p/2a2562f85241
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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