netty實戰筆記 第二章 第一個Netty程序

2.1 編寫Echo服務器

所有的Netty服務器都需要一下兩個部分:

  • 至少一個ChannelHandler
    該組件實現了服務器對客戶端接收的數據的處理,即他的業務邏輯。
  • 引導
    這是配置服務器的啓動代碼. 至少,他會將服務器綁定到它要監聽連接請求的端口上.

2.1.1 ChannelHandler 業務處理邏輯

接口ChannelInboundHandler
用來響應入站事件的方法。使用的時候只需要繼承ChannelInboundHandlerAdapter類就行了。它提供了ChannelInboundHandler的默認實現。
主要的方法:

  • channelRead() 對於每個傳入的消息都會調用
  • channelReadComplete() 通知ChannelInboundHandler最後一次對channelRead()的調用時當前批量讀取中的最後一個條消息。
  • exceptionCaught() 在讀取操作期間,有異常拋出時會調用。
package com.moyang.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * nettydemo1 -- 這個組件實現了服務器的業務邏輯,決定了連接創建後和接收到信息後該如何處理
 * <p>
 * 這種使用 ChannelHandler 的方式體現了關注點分離的設計原則,並簡化業務邏輯的迭代開發的要求
 *
 * @author 墨陽
 * @date 2018-11-09
 */
// 標識ChannelHandler的實例之間可以在Channel裏面共享
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("server has received: " + buf.toString(CharsetUtil.UTF_8));
        // 將接受到的消息發送給發送者,注意,這裏並沒有flush。
        ctx.write(buf);
    }


    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//        沖刷所有待審消息到遠程節點。並關閉通道
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 打印異常棧跟蹤
        cause.printStackTrace();
        // 關閉Channel
        ctx.close();
    }
}

2.2.2 引導服務器

引導服務器主要涉及一下內容:

  • 綁定到服務器上,進行監聽,並接受傳入連接請求的端口。
  • 配置channel以將有關的入站消息通知給EchoServerHandler實例。
package com.moyang.echo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

/**
 * nettydemo1
 * <p>
 * 1.監聽和接收進來的連接請求
 * 2.配置 Channel 來通知一個關於入站消息的 EchoServerHandler 實例
 *
 * @author 墨陽
 * @date 2018-11-09
 */
public class EchoServer {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8000;
        new EchoServer(port).start();
    }

    public void start() throws InterruptedException {

        // 創建EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // 創建ServerBootStrap
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(group)
                    // 爲NIO指定使用 NioServerSocketChannel 這種信道類型
                    .channel(NioServerSocketChannel.class)
                    // 使用指定的端口設置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    // 添加EchoServerHandler到子Channel的ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {  //添加 EchoServerHandler 到 Channel 的 ChannelPipeline
                        //當一個新的連接被接受,一個新的子 Channel 將被創建, ChannelInitializer 會添加我們EchoServerHandler 的實例到 Channel 的 ChannelPipeline。
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // EchoServerHandler被標註爲@Shareable,所以我們可以總是使用同樣的實例。
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });

            // 異步的綁定服務;調用sync()方法阻塞等待直到綁定完成
            ChannelFuture f = bootstrap.bind().sync();  // 8

            System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress());

            //獲取channel的CloseFuture,調用sync()會導致阻塞當前線程直到它完成。
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉 EventLoopGroup,釋放所有資源。包括所有創建的線程
            group.shutdownGracefully().sync();
        }
    }
}

ChannelInitializer,當一個新的連接被接受的時候,一個新的子Channel將會被建立.ChannelInitializer將會把一個你的EchoServerHandler的實例添加到該ChannelChannelPipeline中。這個ChannelHandler就會收到有關入站消息的通知。

2.2 編寫Echo客戶端

Echo客戶端會完成的工作:

  • 連接到服務器
  • 發送一個或者多個消息
  • 對於每個消息,等待並接受從服務器發回來的消息
  • 關閉連接

2.2.1 通過ChannelHandler實現業務邏輯

客戶端將擁有一個用來處理數據庫的ChannelInboundHandler,客戶端使用SimpleChannelInboundHandler類來完成任務。主要的方法有:

  • channelActive() 在到服務器的連接已經建立之後被調用
  • channelRead0() 當從服務器接到一條消息的時候被調用
  • exceptionCaught() 在處理的過程中已發異常的時候被調用
package com.moyang.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

/**
 * nettydemo1
 *
 * @author 墨陽
 * @date 2018-11-09
 */
// 標記該實例可以被多個Channel共享
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /**
     * 建立連接後該 channelActive() 方法被調用一次.
     * 邏輯很簡單:一旦建立了連接,字節序列被髮送到服務器。
     * 該消息的內容並不重要;在這裏,我們使用了 Netty 編碼字符串 “Netty rocks!”
     * 通過覆蓋這種方法,我們確保東西被儘快寫入到服務器。
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("netty rocks!!! ", CharsetUtil.UTF_8));
    }

    /**
     * 這種方法會在接收到數據時被調用。
     * 注意,由服務器所發送的消息可以以塊的形式被接收。
     * 即,當服務器發送 5 個字節是不是保證所有的 5 個字節會立刻收到 - 即使是隻有 5 個字節,
     * channelRead0() 方法可被調用兩次,第一次用一個ByteBuf(Netty的字節容器)裝載3個字節和第二次一個 ByteBuf 裝載 2 個字節。
     * 唯一要保證的是,該字節將按照它們發送的順序分別被接收。 (注意,這是真實的,只有面向流的協議如TCP)。
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
        System.out.println("Client Received: " + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

思考

爲什麼客戶端使用的SimpleChannelInboundHandler而服務端使用的ChannelInboundHandler?

這個關係到:業務邏輯如何處理消息以及Netty如何管理資源.
在客戶端,當channelRead0()方法完成時,你已經有了傳入消息,並且已經處理完它了。當該方法返回時,SimpleChannelInboundHandler 負責釋放指向保存該消息的ByteBuf 的內存引用。在EchoServerHandler 中,你仍然需要將傳入消息回送給發送者,而write()操作是異步的,直到channelRead()方法返回後可能仍然沒有完成(如代碼清單2-1 所示)。爲此,EchoServerHandler擴展了ChannelInboundHandlerAdapter,其在這個時間點上不會釋放消息。消息在EchoServerHandler 的channelReadComplete()方法中,當writeAndFlush()方法被調用時被釋放

2.4.2 引導客戶端

package com.moyang.echo;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * nettydemo1
 *
 * @author 墨陽
 * @date 2018-11-09
 */
public class EchoClient {

    private final String host;

    private final int port;

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

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // 創建BootStrap
            Bootstrap bootstrap = new Bootstrap();
            // 指定EventLoopGroup以處理客戶端事件;
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    // 設置服務器的InetSocketAddress
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            // 連接到遠程節點,調用sync()阻塞直到連接完成
            ChannelFuture f = bootstrap.connect().sync();
            // 阻塞,直到Channel關閉
            f.channel().closeFuture().sync();
        } finally {
            // 關閉線程池,釋放所有資源
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String host = "127.0.0.1";
        int port = 8000;
        new EchoClient(host, port).start();
    }

}

注意:
可以在客戶端和服務端使用不同的傳輸。

最後

如果你覺得寫的還不錯,就關注下公衆號唄,關注後,有點小禮物回贈給你。
你可以獲得5000+電子書,java,springCloud,adroid,python等各種視頻教程,IT類經典書籍,各種軟件的安裝及破解教程。
希望一塊學習,一塊進步!
在這裏插入圖片描述

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