1. 程式人生 > >第一個Netty程式(編寫Hello Netty伺服器)

第一個Netty程式(編寫Hello Netty伺服器)

具體步驟

  • 構建一對主從執行緒組
  • 定義伺服器啟動類
  • 為伺服器設定channel
  • 設定處理從執行緒池的助手類初始化器(我們會有一個channel,這個channel會有一堆相應的助手類handler對它進行處理,如編解碼處理、讀寫資料等。這些操作都是需要歸類在助手類的初始化器裡面,簡單來說它就是一個類,在這個類裡要新增很多的助手類。)
  • 監聽啟動和關閉伺服器

設定channel初始化器

每一個channel由多個handler共同組成管道(pipeline)。
在下圖,左側有一個channel,右側是一個pipeline。當這個channel註冊完之後,就會有一個pipeline,其實就是一個初始化器。在pipeline裡面,我們需要為它設定很多handler,這些handler我們可以稱之為是一個個的助手類,這些助手類就會針對channel去做一些處理。比如現在有個管道通了,就會分別去註冊handlerA、hanlderB、handlerC。當客戶端和服務端進行互動的時候,相應的助手類會針對我們的請求去做相應的處理。也可以把pipeline當成是一個大的攔截器,然後裡面有很多小的攔截器handler,當請求過來的時候,會一層一層的去攔截。
在這裡插入圖片描述

程式碼實現

1、構建伺服器(HelloServer.java)

/**
 * 建立人:taofut
 * 建立時間:2018-12-18 21:50
 * 實現客戶端傳送一個請求,伺服器會返回hell netty
 */
public class HelloServer {

    public static void main(String[] args) throws InterruptedException {

        //定義一對執行緒組
        //主執行緒組,用於接收客戶端的請求,但是不做任何處理,跟老闆一樣,不做事
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        //從執行緒組,等待老闆執行緒組安排任務,然後幹活
        EventLoopGroup workGroup=new NioEventLoopGroup();

        try {
            //netty伺服器的建立 ServerBootstrap是一個啟動類
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(bossGroup,workGroup)            //設定主從執行緒組
                    .channel(NioServerSocketChannel.class)  //設定nio的雙向通道
                    .childHandler(new HelloServerInitializer());                    //子處理器,用於處理workGroup

            //啟動server,並且設定埠號為8088,同時啟動方式為同步
            ChannelFuture channelFuture=bootstrap.bind(8088).sync();

            //監聽關閉的channel,設定為同步方式
            channelFuture.channel().closeFuture().sync();
        } finally {
            //關閉主執行緒
            bossGroup.shutdownGracefully();
            //關閉從執行緒
            workGroup.shutdownGracefully();
        }
    }
}

2、建立channel初始化器(HelloServerInitializer.java)

/**
 * 建立人:taofut
 * 建立時間:2018-12-19 20:22
 * 描述:初始化器,channel註冊後,會執行裡面相應的初始化方法
 */
public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //通過SocketChannel獲得對應的管道
        ChannelPipeline pipeline=socketChannel.pipeline();

        //通過管道,新增handler
        //HttpServerCodec是由netty官方提供的助手類,可以理解為攔截器
        //當請求到服務端,我們需要做解碼,響應到客戶端做編碼
        pipeline.addLast("HttpServerCodec",new HttpServerCodec());

        //新增自定義的助手類,返回 "hello netty"
        pipeline.addLast("customHandler",new CustomHandler());
    }

}

3、新增自定義助手類handler(CustomHandler.java)

/**
 * 建立人:taofut
 * 建立時間:2018-12-19 20:48
 * 描述:建立自定義助手類
 */
//SimpleChannelInboundHandler:對於請求來講,相當於入站、入境
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject)
            throws Exception {
        //獲取channel
        Channel channel=channelHandlerContext.channel();

        if(httpObject instanceof HttpRequest){
            //顯示客戶端的遠端地址
            System.out.println(channel.remoteAddress());

            //定義傳送的資料訊息
            ByteBuf content= Unpooled.copiedBuffer("hello netty", CharsetUtil.UTF_8);

            //構建一個http response
            FullHttpResponse response=
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                            HttpResponseStatus.OK,
                            content);

            //為響應增加資料型別和長度
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            //把響應刷到客戶端
            channelHandlerContext.writeAndFlush(response);
        }

    }
}

執行測試

啟動HelloServer.java裡的main方法,然後在瀏覽器輸入地址:http://localhost:8088/, “hello netty”被打印出來。
在這裡插入圖片描述

Channel生命週期

將以上的CustomHandler.java類裡面的channel對應的方法一一實現。

/**
 * 建立人:taofut
 * 建立時間:2018-12-19 20:48
 * 描述:建立自定義助手類
 */
//SimpleChannelInboundHandler:對於請求來講,相當於入站、入境
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject)
            throws Exception {
        //獲取channel
        Channel channel=channelHandlerContext.channel();

        if(httpObject instanceof HttpRequest){
            //顯示客戶端的遠端地址
            System.out.println(channel.remoteAddress());

            //定義傳送的資料訊息
            ByteBuf content= Unpooled.copiedBuffer("hello netty", CharsetUtil.UTF_8);

            //構建一個http response
            FullHttpResponse response=
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                            HttpResponseStatus.OK,
                            content);

            //為響應增加資料型別和長度
            response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());

            //把響應刷到客戶端
            channelHandlerContext.writeAndFlush(response);
        }

    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel註冊...】");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel移除...】");
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel活躍...】");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel不活躍...】");
        super.channelInactive(ctx);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel資料讀取完畢...】");
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("【使用者事件觸發...】");
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【channel可寫更改...】");
        super.channelWritabilityChanged(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("【捕獲異常...】");
        super.exceptionCaught(ctx, cause);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【助手類新增...】");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("【助手類移除...】");
        super.handlerRemoved(ctx);
    }
}

然後,我們繼續訪問伺服器,觀察控制檯的列印結果。這裡,我使用了linux命令代替瀏覽器發起請求。(瀏覽器訪問會發起額外的不相關請求,為了保證結果清晰,才使用linux命令代替瀏覽器。)
輸入命令:curl 192.168.3.101(本機IP):8088
控制檯結果:

【助手類新增…】
【channel註冊…】
【channel活躍…】
/192.168.3.101:53147
【channel資料讀取完畢…】
【channel資料讀取完畢…】
【channel不活躍…】
【channel移除…】
【助手類移除…】