1. 程式人生 > 其它 >02- 你的第一款Netty應用程式

02- 你的第一款Netty應用程式

02- 你的第一款Netty應用程式

本章主要內容:

  • 設定開發環境(Java環境、Maven環境)

  • 編寫Echo伺服器和客戶端(簡單的請求響應)

  • 構建並測試應用

設定開發環境和測試在這裡不多做概述,主要將如何應用Netty構建一個簡單的請求響應客戶端與服務端。

同樣的這裡也不對Netty的各個元件展開說明,後面在每一章節都有詳細的解釋,這一章的目的就是讓你搭建一個簡單的程式,讓它能夠跑起來。


2.2 Netty客戶端/服務端概述

Echo 客戶端和伺服器之間的互動是非常簡單的;在客戶端建立一個連線之後,它會向伺服器傳送一個或多個訊息,反過來,伺服器又會將每個訊息回送給客戶端。雖然它本身看起來好像作用不大,但它充分地體現了客戶端/伺服器系統中典型的請求-響應互動模式


2.3 編寫Echo伺服器

所有的Netty伺服器都需要以下兩部分。

  • 至少一個ChannelHandler——該元件實現了伺服器對客戶端接收的資料處理,即它的業務邏輯

  • 引導——這是配置伺服器的啟動程式碼。至少,它會將伺服器繫結到它要監聽連線請求的埠上。

程式碼示例:

EchoServerHandler:
@Sharable  // 這是一個重點,標識一個ChannelHandler可以被多個Channel安全地共享,後面會展開闡述
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    
public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf in = (ButeBuf) msg; // 將訊息記錄到控制檯 System.out.println("Server received:" + in.toString(CharsetUtil.UTF-8)); // 將接收到的訊息寫給傳送者,而不沖刷出站訊息 ctx.write(in); } @Override public void channelReadComplete(ChannelHandlerContext ctx) {
// 將未決訊息沖刷到遠端節點,並且關閉該Channel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 列印異常棧跟蹤、關閉該Channel cause.printStackTrace(); ctx.close(); } }
EchoServer:
public class EchoServer {
    private final int port;
    
    public EchoServer(int port) {
        this.port = port;
    }
    
    // 校驗埠號
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage:" + EchoServer.class.getSimpleName() + "<port>");
            return;
        }
        // 設定埠值(如果埠值引數的格式不正確,則丟擲一個NumberFormatException)
        int port = Integer.parseInt(args[0]);
        // 呼叫伺服器的start()方法
        new EchoServer(port).start();
    }
    
    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        // 建立EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 建立ServerBootstrap
            ServerBootstrap b = new ServerBootStrap();
            b.group(group)
                .channel(NioServerScoketChannel.class)    // 指定所使用的NIO傳輸Channel
                .localAddress(new InetSocketAddress(port))    // 使用指定的埠設定套接字地址
                .childHandler(new ChannelInitializer<SocketChannel>() { // 新增一個EchoServerHandler到子Channel的ChannelPipeline
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        // EchoServerHandler被標註為@Shareable,所以我們總是可以使用同樣的例項
                        ch.pipeline().addLast(serverHandler);
                    }
                });
            // 非同步地繫結伺服器,呼叫sync()方法阻塞等待直到繫結完成
            ChannelFuture f = b.bind().sync();
            // 獲取Channel的CloseFuture,並且阻塞當前執行緒直到它完成
            } finally {
            // 關閉EventLoopGroup,釋放所有資源
            group.shutdownGracefully().sync();
        }           
    }
}

2.4 編寫Echo客戶端

Echo客戶端將會:

  1. 連線到伺服器

  2. 傳送一個或多個訊息

  3. 對於每個訊息,等待並接收從伺服器發回的相同的訊息

  4. 關閉連線

程式碼示例:

客戶端的ChannelHandler:
@Sharable    // 標記該類的例項可以被多個Channel共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 當被通知 Channel是活躍的時候,傳送一條訊息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!"), CharsetUtil.UTF_8);
    }
    
    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
        // 記錄已接收訊息的轉儲
        System.out.println("Client receive:" + in.toString(CharsetUtil.UTF_8));
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throws cause) {
        // 發生異常時,記錄錯誤並關閉Channel
        cause.printStackTrace();
        ctx.close();
    }
}
EchoClient:
public class EchoClient {
    private final String host;
    private final int port;
    
    public EchoClient(String host, int port) {
        this.port = port;
        this.host = host;
    }
    
    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                .channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress(host, port))
                .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new EchoClientHandler());
                }
            });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            // 關閉執行緒池並釋放所有資源
            group.shutdownGracefully().sync();
        }
    }
}
 
public static void main(String[] args) throws Exception { 
    if (args.length != 2) {
        System.err.println("Usage:" + EchoClient.class.getSimpleName() + "<host><port>");
        return;
    }
    
    String host = args[0];
    int port = Integet.parseInt(args[1]);
    new EchoClient(host, port).start();
}

本章小結

在這一章你編寫好了一個簡單的Netty客戶端與伺服器,能夠簡單執行請求響應的程式。但是你一定留有很多疑問,但是在下面的章節中會詳細解釋Netty中每個元件的作用,並分析器底層的原始碼,慢慢揭開它的神祕面紗。