1. 程式人生 > >Java Netty 學習(七) - 第一個Netty程式

Java Netty 學習(七) - 第一個Netty程式

介紹

Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、事件驅動的網路應用程式框架和工具,它簡化了程式設計師的工作,用以快速開發高效能、高可靠性的網路伺服器和客戶端程式。
本系列文章講慢慢一起走進學習Netty
本篇文章就以一個Hello Word程式開始。

例子

首先,Netty作為網路程式設計的框架,自然離不開Socket,同時,也包括Server端以及Client端。他們利用Socket進行資訊傳輸。
Socket可以理解為應用層與TCP/IP協議族通訊的中間軟體抽象層。
關於Socket可看文章:Socket
下面看demo

Client端

首先是一個啟動的類:

public class Client {
    private Integer port;
    private String address;
    public Client(Integer port, String address){
        this.port = port;
        this.address = address;
    }
    /**
     * 用於連線伺服器
     */
    public void start() throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();  
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new HelloClientHandler());
            }
        });
        System.out.println("begin to connect...");
        ChannelFuture future = b.connect(this.address, this.port);
        future.channel().closeFuture().sync();
    }
    public static void main(String[] args) throws Exception {
        Client client = new Client(8989, "127.0.0.1");
        client.start();
    }
}

再看看裡面的HelloClientHandler

public class HelloClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("Client received Message from server: " + msg.toString(CharsetUtil.UTF_8));  //讀取資料就輸出
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));   //活躍時候輸出
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();   //遇到錯誤就關閉
    }
}

對於上面的client端的程式碼,可以看到下面幾個概念:

  • EventLoopGroup:可以理解為執行緒池,裡面產生EventLoop來執行channel所有產生的事件
  • Bootstrap:啟動器,講Netty各個元件串起來使之執行
  • Pipline:可以理解為管道,或者流水線,裡面可以裝載多個ChannelHandler並讓他們執行
  • ChannelHandler:例如HelloClientHandler ,具體的channel所處理的工作

整個過程可以理解為這樣一個過程,分發快遞過程,快遞車把快遞送到目的地,此時有一個或者多個快遞員(EventLoop)進行快遞分發,分發到給不同的區域標籤(Pipline),此時,由於快遞是已經定義好的,再有快遞員發給不同的快遞手上(執行ChannelHandler所定一個的規則)
EventLoop 的作用是一個死迴圈,而這個迴圈中做3件事情:

  • 有條件的等待 NIO 事件
  • 處理 NIO 事件
  • 處理訊息佇列中的任務

下面看看服務端程式碼

Server端

首先也是一個啟動類:

public class Server {
    private Integer port;
    public Server(Integer port){
        this.port = port;
    }
    public void startServer() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(group)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("new channel");
                            ch.pipeline().addLast(new HelloServerHandler());
                        }
                    });
            System.out.println("begin to run server");
            ChannelFuture future = serverBootstrap.bind().sync();
            future.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }
    public static void main(String[] args) throws Exception {
        Server server = new Server(8989);
        server.startServer();
    }
}

再看HelloServerHandler,和前面的HelloClientHandler邏輯差不多:

public class HelloServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        System.out.println("Server received from client: " + in.toString(CharsetUtil.UTF_8));
        ctx.write(in);     //傳送出去,所以client端可以讀
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server active");
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(cause);
        ctx.close();
    }
}

就看啟動程式碼來看,服務端程式碼和客戶端程式碼還是很相似的

  • Bootstrap變為ServerBootstrap
  • NioSocketChannel變為NioServerSocketChannel
  • 而在Handler處理上,client端的handler變為了childHandler

整個例子的流程如下:

  1. 服務端啟動,監聽127.0.0.1:8989埠
  2. 客戶端連線埠,服務端監聽到,輸出new channel並把HelloServerHandler加入到pipline
  3. 此時,客戶端連線上後,觸發了channelActive事件,並把Netty rocks!發了出去
  4. 此時,服務端接受到了資料Netty rocks!,觸發了HelloServerHandler中事件,所以列印了Server received: Netty rocks!,並且傳送了出去
  5. 此時,由於客戶端接受訊息,觸發了channelRead0 所以最後列印了Client received: Netty rocks!

整個例子就完成了。

資料:

  1. Netty In Action