【netty】(2)---搭建一個簡單服務器


netty(2)---搭建一個簡單服務器

效果:當用戶訪問:localhost:8088 后 服務器返回 “hello netty”;

一、服務端線程模型

下面的做法是服務端監聽線程和 IO 線程分離,類似於 Reactor 的多線程模型,它的工作原理圖如下(盜的圖):

這里netty版本是4.1.25

    <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
    </dependency>

二、主程序類

/**
 * @Description: 實現客戶端發送一個請求,服務器會返回 hello netty
 */
public class HelloServer {

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

        // 定義一對線程組
        // 主線程組, 用於接受客戶端的連接,但是不做任何處理,跟老板一樣,不做事
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 從線程組, 老板線程組會把任務丟給他,讓手下線程組去做任務
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            // netty服務器的創建, 輔助工具類,用於服務器通道的一系列配置
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)           //綁定兩個線程組
                           .channel(NioServerSocketChannel.class)   //指定NIO的模式
                           .childHandler(new HelloServerInitializer()); // 子處理器,用於處理workerGroup
            
            // 啟動server,並且設置8088為啟動的端口號,同時啟動方式為同步
            ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
            
            // 監聽關閉的channel,設置位同步方式
            channelFuture.channel().closeFuture().sync();
        } finally {
            //退出線程組
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

上面這段代碼展示了服務端的一個基本步驟:

(1)、 初始化用於Acceptor的主"線程池"以及用於I/O工作的從"線程池";
(2)、 初始化ServerBootstrap實例, 此實例是netty服務端應用開發的入口;
(3)、 通過ServerBootstrap的group方法,設置(1)中初始化的主從"線程池";
(4)、 指定通道channel的類型,由於是服務端,故而是NioServerSocketChannel;
(5)、 設置ServerSocketChannel的處理器
(6)、 設置子通道也就是SocketChannel的處理器, 其內部是實際業務開發的"主戰場"
(8)、 配置子通道也就是SocketChannel的選項
(9)、 綁定並偵聽某個端口

三、子處理器 HelloServerInitializer類

/**
 * @Description: 初始化器,channel注冊后,會執行里面的相應的初始化方法
 */
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        // 通過SocketChannel去獲得對應的管道
        ChannelPipeline pipeline = channel.pipeline();
        
        // 通過管道,添加handler
        // HttpServerCodec是由netty自己提供的助手類,可以理解為攔截器
        // 當請求到服務端,我們需要做解碼,響應到客戶端做編碼
        pipeline.addLast("HttpServerCodec", new HttpServerCodec());
        
        // 添加自定義的助手類,返回 "hello netty~"
        pipeline.addLast("customHandler", new CustomHandler());
    }

}

子處理器也可以通過內部方法來實現的。

b.group(group).channel(NioServerSocketChannel.class)
              .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    //這里進行方法實現.......
                    socketChannel.pipeline().addLast(serverHandler);
                }
            });


四、自定義助手類 CustomHandler類

/**
 * 創建自定義助手類
 */
// SimpleChannelInboundHandler: 對於請求來講,其實相當於[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) 
            throws Exception {
        // 獲取channel
        Channel channel = ctx.channel();
        
        if (msg 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());
            
            // 把響應刷到客戶端
            ctx.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("channeld讀取完畢。。。");
        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);
    }

}

## 五、啟動主類 進行測試

后台完整輸出


六、客戶端線程模型

相比於服務端,客戶端的線程模型簡單一些,它的工作原理如下:

代碼如下

  EventLoopGroup group = new NioEventLoopGroup();
         try {
             Bootstrap b = new Bootstrap();
             b.group(group) // 注冊線程池
              .channel(NioSocketChannel.class) // 使用NioSocketChannel來作為連接用的channel類
              .remoteAddress(new InetSocketAddress(this.host, this.port)) // 綁定連接端口和host信息
              .handler(new ChannelInitializer<SocketChannel>() { // 綁定連接初始化器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                                     //這里放入自定義助手類
                                     ch.pipeline().addLast(new EchoClientHandler());
                                 }
                             });
         
             ChannelFuture cf = b.connect().sync(); // 異步連接服務器
             cf.channel().closeFuture().sync(); // 異步等待關閉連接channel
 
         } finally {
             group.shutdownGracefully().sync(); // 釋放線程池資源
         }
     }

客戶端的開發步驟和服務端都差不多:

(1)、 初始化用於連接及I/O工作的"線程池";
(2)、 初始化`Bootstrap`實例, 此實例是netty客戶端應用開發的入口;
(3)、 通過Bootstrap的group方法,設置(1)中初始化的"線程池";
(4)、 指定通道channel的類型,由於是客戶端,故而是`NioSocketChannel`;
(5)、 設置SocketChannel的選項;
(6)、 設置SocketChannel的處理器, 其內部是實際業務開發的"主戰場";
(7)、 連接指定的服務地址;

相比於服務端很明顯的區別在於
(1),客戶端只需要創建一個 EventLoopGroup,因為它不需要獨立的線程去監聽客戶端連接,也沒必要通過一個單獨的客戶端線程去連接服務端。Netty 是異步事件驅動的 NIO 框架,它的連接和所有 IO 操作都是異步的,因此不需要創建單獨的連接線程。
(2)服務端引導類ServerBootstrap,而客戶端引導是Bootstrap進行開發的,不過它們都需要通過group屬性指定EventLoopGroup, 因為是開發NIO程序,所以我們選擇NioEventLoopGroup。



如果一個人充滿快樂,正面的思想,那么好的人事物就會和他共鳴,而且被他吸引過來。同樣,一個人老帶悲傷,倒霉的事情也會跟過來。
                                                      ——在自己心情低落的時候,告誡自己不要把負能量帶給別人。(大校11)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM