Netty入門系列(1) --使用Netty搭建服務端和客戶端

引言

前面咱們介紹了網絡一些基本的概念,雖說這些很難吧,可是至少要作到理解吧。有了以前的基礎,咱們來正式揭開Netty這神祕的面紗就會簡單不少。編程

服務端

public class PrintServer {

    public void bind(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();                     //1
        EventLoopGroup workerGroup = new NioEventLoopGroup();                   //2
        try {
            ServerBootstrap b = new ServerBootstrap();                          //3
            b.group(bossGroup, workerGroup)                                     //4                                         
                    .channel(NioServerSocketChannel.class)                      //5
                    .option(ChannelOption.SO_BACKLOG, 1024)                     //6
                    .childHandler(new ChannelInitializer<SocketChannel>() {     //7
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new PrintServerHandler());
                        }
                    });

            ChannelFuture f = b.bind(port).sync();              //8
            
            f.channel().closeFuture().sync();                   //9
        } finally {
            // 優雅退出,釋放線程池資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int port = 8080;
        new TimeServer().bind(port);
    }
}

咱們來分析一下上面的這段代碼(下面的每一點對應上面的註釋)數組

1~2:首先咱們建立了兩個NioEventLoopGroup實例,它是一個由Netty封裝好的包含NIO的線程組。爲何建立兩個?我想通過前面的學習你們應該都清楚了。對,由於Netty的底層是IO多路複用,bossGroup 是用於接收客戶端的鏈接,原理就是一個實現的Selector的Reactor線程。而workerGroup用於進行SocketChannel的網絡讀寫。緩存

3:建立一個ServerBootstrap對象,能夠把它想象成Netty的入口,經過這類來啓動Netty,將所須要的參數傳遞到該類當中,大大下降了的開發難度。網絡

4:將兩個NioEventLoopGroup實例綁定到ServerBootstrap對象中。異步

5:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),這裏建立的是NIOserverSocketChannel,它的功能能夠理解爲當接受到客戶端的鏈接請求的時候,完成TCP三次握手,TCP物理鏈路創建成功。並將該「通道」與workerGroup線程組的某個線程相關聯。ide

6:設置參數,這裏設置的SO_BACKLOG,意思是客戶端鏈接等待隊列的長度爲1024.oop

7:創建鏈接後的具體Handler。就是咱們接受數據後的具體操做,例如:記錄日誌,對信息解碼編碼等。學習

8:綁定端口,同步等待成功編碼

9:等待服務端監聽端口關閉線程

綁定該服務端的Handler

public class PrintServerHandler extends ChannelHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    ByteBuf buf = (ByteBuf) msg;                                        //1
    byte[] req = new byte[buf.readableBytes()]; 
    buf.readBytes(req); //將緩存區的字節數組複製到新建的req數組中
    String body = new String(req, "UTF-8");
    System.out.println(body);
    String response= "打印成功";
    ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());                      
    ctx.write(resp);                                                    //2
    }   

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


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

PrintServerHandler 繼承 ChannelHandlerAdapter ,在這裏它的功能爲 打印客戶端發來的數據而且返回客戶端打印成功。

咱們只須要實現channelRead,exceptionCaught,前一個爲接受消息具體邏輯的實現,後一個爲發生異常後的具體邏輯實現。

1:咱們能夠看到,接受的消息被封裝爲了Object ,咱們將其轉換爲ByteBuf ,前一章的講解中也說明了該類的做用。咱們須要讀取的數據就在該緩存類中。

2~3:咱們將寫好的數據封裝到ByteBuf中,而後經過write方法寫回到客戶端,這裏的3調用flush方法的做用爲,防止頻繁的發送數據,write方法並不直接將數據寫入SocketChannel中,而是把待發送的數據放到發送緩存數組中,再調用flush方法發送數據。

客戶端

public class PrintClient {

    public void connect(int port, String host) throws Exception {
    EventLoopGroup group = new NioEventLoopGroup();                 //1
    try {
        Bootstrap b = new Bootstrap();                              //2
         b.group(group)                                             //3
            .channel(NioSocketChannel.class)                        //4
            .option(ChannelOption.TCP_NODELAY, true)                //5
            .handler(new ChannelInitializer<SocketChannel>() {      //6
            @Override
            public void initChannel(SocketChannel ch)               
                throws Exception {
                ch.pipeline().addLast(new PrintClientHandler());
            }
            });

        ChannelFuture f = b.connect(host, port).sync();             //7
        f.channel().closeFuture().sync();                           //8
    } finally {
        // 優雅退出,釋放NIO線程組
        group.shutdownGracefully();
    }
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
    int port = 8080;
    new TimeClient().connect(port, "127.0.0.1");
    }
}

咱們繼續來分析一下上面的這段代碼(下面的每一點對應上面的註釋)

1:區別於服務端,咱們在客戶端只建立了一個NioEventLoopGroup實例,由於客戶端你並不須要使用I/O多路複用模型,須要有一個Reactor來接受請求。只須要單純的讀寫數據便可

2:區別於服務端,咱們在客戶端只須要建立一個Bootstrap對象,它是客戶端輔助啓動類,功能相似於ServerBootstrap。

3:將NioEventLoopGroup實例綁定到Bootstrap對象中。

4:建立Channel(典型的channel有NioSocketChannel,NioServerSocketChannel,OioSocketChannel,OioServerSocketChannel,EpollSocketChannel,EpollServerSocketChannel),區別與服務端,這裏建立的是NIOSocketChannel.

5:設置參數,這裏設置的TCP_NODELAY爲true,意思是關閉延遲發送,一有消息就當即發送,默認爲false。

6:創建鏈接後的具體Handler。注意這裏區別與服務端,使用的是handler()而不是childHandler()。handler和childHandler的區別在於,handler是接受或發送以前的執行器;childHandler爲創建鏈接以後的執行器。

7:發起異步鏈接操做

8:當代客戶端鏈路關閉

綁定該客戶端的Handler

public class PrintClientHandler extends ChannelHandlerAdapter {

    private static final Logger logger = Logger
        .getLogger(TimeClientHandler.class.getName());

    private final ByteBuf firstMessage;

    /**
     * Creates a client-side handler.
     */
    public TimeClientHandler() {
    byte[] req = "你好服務端".getBytes();
    firstMessage = Unpooled.buffer(req.length);                                 //1
    firstMessage.writeBytes(req);

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    ctx.writeAndFlush(firstMessage);                                            //2             
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)              //3
        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);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {   //4
    // 釋放資源
    System.out.println("Unexpected exception from downstream : "
        + cause.getMessage());
    ctx.close();
    }
}

PrintClientHandler 繼承 ChannelHandlerAdapter ,在這裏它的功能爲 發送數據並打印服務端發來的數據。

咱們只須要實現channelActive,channelRead,exceptionCaught,第一個爲創建鏈接後當即執行,後兩個與一個爲接受消息具體邏輯的實現,另外一個爲發生異常後的具體邏輯實現。

1:將發送的信息封裝到ByteBuf中。

2:發送消息。

3:接受客戶端的消息並打印

4:發生異常時,打印異常信息,釋放客戶端資源

總結

這是一個入門程序,對應前面所講的I/O多路複用模型以及NIO的特性,能頗有效的理解該模式的編程方式。若是這幾段代碼看着很費勁,那麼能夠看看以前博主的Netty基礎系列。

若是博主哪裏說得有問題,但願你們提出來,一塊兒進步~

相關文章
相關標籤/搜索