從零開始netty學習筆記之netty簡單demo

zhumeilu發表於2017-12-14

服務端:

public class TimeServer {

    public void bind(int port) throws Exception{

        //配置服務端的NIO執行緒組
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());
            //繫結埠,同步等待成功
            ChannelFuture f= b.bind(port).sync();
            //等待伺服器監聽埠關閉
            f.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //優雅退出,釋放執行緒池資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{

        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if(args!=null&&args.length>0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){

            }
        }
        new TimeServer().bind(port);

    }
}
複製程式碼

首先建立了兩個NioEventLoopGroup例項。NioEventLoopGroup是個執行緒組,它包含了一組NIO執行緒,專門用於網路時間的處理,實際上它們就是Reactor執行緒組。這裡建立兩個的原因是一個用於服務端接受客戶端的連結,另一個用於進行SocketChannel的網路讀寫。 然後建立了ServerBootstrap物件,它是Netty用於啟動NIO服務端的輔助啟動類,目的是降低服務端開發的複雜度。接著呼叫ServerBootstrap的group方法,將兩個NIO執行緒組當做引數傳遞到ServerBootstrap中。 接著設定建立的Channel為NioServerChannel,它的功能對於與JDK NIO類庫中的ServerSocketChannel類。然後配置NIOServerSocketChannel的TCP引數,此處將它的backlog設定為1024,最後繫結IO事件的處理類ChildChannelHandler,它的作用類似於Reactor模式中的Handler類,主要用於處理網路IO事件,例如記錄日誌,對訊息進行編碼解碼等。 服務端啟動輔助類配置完成之後,呼叫它的bind方法繫結監聽埠,隨後,呼叫它的同步阻塞方法sync等待繫結操作完成。完成時候Netty返回一個ChannelFuture,它的功能類似於JDK的java.util.concurrent.Future,主要用於非同步操作的通知回撥。 使用f.channel().closeFuture.sync()方法進行阻塞,等待伺服器鏈路關閉之後main函式才退出。 呼叫NIO執行緒組的shutdownGracefully進行優雅退出,它會釋放跟shutdownGracefuly相關聯的資源。 服務端處理器

public class TimeServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req,"utf-8");
        System.out.println("伺服器收到命令:"+body);
        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString():"BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
複製程式碼

TimeServerHandler繼承自 ChannelHandlerAdapter,它用於對網路事件進行讀寫操作,通常我們只需關注channelRead和exceptionCaught方法。 將msg轉換 成Netty的ByteBuf物件。ByteBuf類似於JDK中的java.nio.ByteBuffer物件,不過它提供了更加強大和靈活的功能。通過ByteBuf的readableBytes方法可以獲取緩衝區可讀的位元組數,根據可讀的位元組數建立byte陣列,通過ByteBuf的readBytes方法將緩衝區中的位元組陣列複製到新建的byte陣列中,最後通過new String建構函式獲取請求訊息。這是對請求訊息進行判斷,如果是"QUERY TIME ORDER"則建立應答訊息,通過ChannelHandlerContext的write方法非同步傳送應答訊息個客戶端。 呼叫ChannelHandlerContext的flush方法,它的作用是將訊息傳送佇列中的訊息寫入到SockChannel中傳送給對方。從效能角度考慮,為了防止頻繁地喚醒Selector進行訊息傳送,Netty的write方法並不直接將訊息寫入SocketChannel中,呼叫write方法只是把待傳送的訊息放到傳送緩衝陣列中,再通過呼叫flush方法,將傳送緩衝區中的訊息全部寫入到SocketChannel中。

客戶端

public class TimeClient {

    public void connect(int port,String host)throws Exception{

        //配置客戶端NIO執行緒組
        EventLoopGroup group = new NioEventLoopGroup();
        try{

            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioServerSocketChannel.class).option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            socketChannel.pipeline().addLast(new TimeClientHandler());
                        }
                    });
            //發起非同步連線操作
            ChannelFuture f= b.connect(host,port).sync();
            //等待非同步連線操作
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if(args!=null&&args.length>0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){

            }
        }
        new TimeClient().connect(port,"127.0.0.1");
    }
}
複製程式碼

首先建立客戶端處理IO讀寫的NioEventLoopGroup執行緒組,然後繼續建立客戶端輔助啟動類Bootstrap,隨後需要對其進行配置。與服務端不同的是,它的Channel需要設定為NioSocketChannel,然後為其新增Handler。此處為了簡單直接建立匿名內部類,實現initChannel方法,其作用是當建立NioSocketChannel成功之後,在進行初始化時,將它的ChannelHandler設定到ChannelPipeline中,用於處理網路IO事件。 客戶端啟動輔助類設定完成之後,呼叫connect方法發起非同步連線,然後呼叫同步方法等待連線成功。 最後當客戶端連線關閉之後,客戶端主函式退出,退出之前的釋放NIO執行緒組的字眼。

客戶端處理器

public class TimeClientHandler extends ChannelHandlerAdapter {

    private final ByteBuf firstMessage;

    public TimeClientHandler() {
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

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

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String (req,"utf-8");
        System.out.println("訊息:"+body);
    }
}
複製程式碼

當客戶端和服務端TCP鏈路建立成功之後,Netty的NIO執行緒會呼叫channelActive方法,傳送查詢時間的指定給服務端,呼叫ChannelHandlerContext的writeAndFlush方法將請求訊息傳送給服務端。 當服務端返回應答訊息時,channelRead被呼叫,從Netty的ByteBuf中讀取並列印應答訊息 當發生異常時,列印異常日誌,釋放客戶端資源。

相關文章