《Nio系列五》- Netty實現(xiàn)時間查詢服務

前幾節(jié)中,講解了Bio、Nio、Aio實現(xiàn)時間查詢服務的細節(jié),比對其優(yōu)缺點進行了對比。從本節(jié)開始,Nio系列將講解Netty的相關知識,為了比較使用Netty實現(xiàn)服務與直接使用JDK提供的IO操作實現(xiàn)服務的區(qū)別,本節(jié)仍然以實現(xiàn)時間查詢服務為例,講解Netty的使用細節(jié)。

Netty實現(xiàn)服務端

Netty屏蔽了IO底層的操作,即便對IO操作不熟悉,沒有專業(yè)的知識基礎,也可以寫出穩(wěn)定可用的服務。使用Netty編程,基本就是按照特定的步驟進行自定義的設值,啟動服務即可。Netty服務端實現(xiàn)邏輯如下:

  1. 創(chuàng)建兩個線程池 EventLoopGroup,一個用于處理客戶端的鏈接操作,如果服務只監(jiān)聽了一個端口的話,建議改線程池的大小設置為1,另一個線程池用于處理IO的讀寫操作。
  2. 配置ServerBootstrap, 該類是服務端配置輔助類,主要配置服務端的一些設置參數(shù)。例如設置使用的線程池、使用的channel類型、連接上限、初始化邏輯操作等等。
  3. 使用serverBootstrap綁定端口,使用同步方法綁定端口,綁定成功返回ChannelFuture對象,到此服務端就啟動了
  4. 等待future執(zhí)行完,主要就是為了不讓邏輯執(zhí)行完成后退出

實現(xiàn)代碼如下:

public class NettyServer {

    private int port;

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

    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ChannelInitializer<SocketChannel>(){
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ch.pipeline().addLast(new TimerServerHandler());
                    }
                });

        try {
            //同步綁定
            ChannelFuture future = serverBootstrap.bind(new InetSocketAddress(port)).sync();
            //等待關閉
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {

        NettyServer server = new NettyServer(8080);
        server.start();
    }
}

重點講解一下childHandler()方法,該方法中接收一個ChannelInitializer對象,ChannelInitializer是一個抽象類,我們需要繼承該類實現(xiàn)抽象方法initchannel(),在該方法中往pipeline中添加自己的處理邏輯。數(shù)據(jù)的編解碼、數(shù)據(jù)的接收讀取都可以在這里進行擴展。處理邏輯類的實現(xiàn)如下:

public class TimerServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String message = new String(bytes, "utf-8");
        System.out.println("獲取到請求:" + message);
        String response = null;
        if("時間".equals(message)){
            response = (new Date()).toString();
        }else{
            response = "不支持該請求";
        }
        byte[] responseBytes = response.getBytes();
        ByteBuf responseBuf = Unpooled.buffer(responseBytes.length);
        responseBuf.writeBytes(responseBytes);
        System.out.println("返回響應:" + response);
        ctx.write(responseBuf);
    }

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

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

邏輯處理類,需要繼承ChannelHandlerAdapter,其中重要介紹一下channelRead()方法,該方法在接收到數(shù)據(jù)時自動調用(會存在半包問題,此處不考慮),ctx是上下文參數(shù),可以通過ctx讀寫數(shù)據(jù),msg是讀取到的數(shù)據(jù),這個數(shù)據(jù)可以是Buffer,可以是String,可以是任意對象,關鍵是看有沒有在initChannel方法中進行編解碼配置。讀取到數(shù)據(jù)中,解析數(shù)據(jù),然后進行響應。到此服務端代碼就寫完了。

Netty實現(xiàn)客戶端

客戶端的實現(xiàn)與服務端實現(xiàn)類似,不同之處在于:

  1. 輔助配置類為Bootstrap
  2. 只需要配置一個線程池即可,因為可以斷不需要處理請求連接操作

實現(xiàn)代碼如下:

public class NettyClient {

    private int port;
    private String hostname;

    public NettyClient(int port, String hostname) {
        this.port = port;
        this.hostname = hostname;
    }

    public void start(){

        EventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new TimeClientHandler());
                    }
                });
        try {
            ChannelFuture future = bootstrap.connect(new InetSocketAddress(hostname, port)).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) {

        NettyClient client = new NettyClient(8080, "127.0.0.1");
        client.start();
    }
}

重點看一下客戶端的處理邏輯類TimeClientHandler的實現(xiàn),代碼如下:

public class TimeClientHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String message = new String(bytes, "utf-8");
        System.out.println("收到響應:" + message);

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        String message = "時間";
        byte[] bytes = message.getBytes();
        ByteBuf byteBuf = Unpooled.buffer(bytes.length);
        byteBuf.writeBytes(bytes);
        System.out.println("發(fā)送請求:" + message);
        ctx.writeAndFlush(byteBuf);
    }

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

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

channelActive()在連接服務器成功時調用,連接成功后發(fā)送請求即可。channelRead()方法在當有響應數(shù)據(jù)時調用(接收數(shù)據(jù)隊列中有數(shù)據(jù)了就會調用,當然也會受到initChannel()方法中配置的影響),到此客戶端也實現(xiàn)完成。

總結

經(jīng)過對Netty的學習,我們發(fā)現(xiàn)在實現(xiàn)邏輯中,我們不在和Channel、socket打交道了,而是而需要和Buffer打交道即可(如果配置了編解碼Buffer也不用打交道,直接處理對象即可)。Netty屏蔽了IO處理的細節(jié),簡化了編程的復雜性。

下一節(jié),將講解Netty對半包/粘包問題的處理。

歡迎掃描下方二維碼,關注公眾號,我們可以進行技術交流,共同成長

qrcode_for_gh_5580beb3cba1_430.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容