Netty入門程式

MuXinu發表於2024-03-23
本章使用Netty開發一個入門程式,使用ServerBootstrap開發時間服務TimeServer,使用Bootstrap開發客戶端TimeClient請求TimeServer獲取時間。
開發 TimeServer之前,先回顧一下使用 NIO 進行服務端開發的步驟。
(1)建立 ServerSocketChannel,配置它為非阻塞模式;
(2)繫結監聽,配置 TCP 引數,例如 backlog 大小;
(3)建立一個獨立的 IO執行緒,用於輪詢多路複用器 Seleetor;
(4)建立 Selector,將之前建立的 ServerSocketChannel 註冊到 Selector 上, 監聽SelectionKey.ACCEPT;
(5)啟動IO執行緒,在迴圈體中執行 Selector.select()方法,輪詢就緒的 Channel;
(6)當輪詢到了處於就緒狀態的 Channel 時,需要對其進行判浙,如果是 OP_ACCEPT狀態,說明是新的客戶端接入,則呼叫ServerSocketChannel.accept()方法接受新的客戶端;
(7)設定新接入的客戶端鏈路 SocketChannel 為非阻塞模式,配置其他的一些 TCP 引數;
(8)將 SocketChannel 註冊到 Seleetor,監聽 OP READ 操作位;
(9)如果輪詢的 Channel 為 OP READ,則說明 SocketChannel 中有新的就緒的資料包需要讀取,則構造 Byte Buffer 物件,讀取資料包;
(10)如果輪詢的 Channel 為 OP WRITE,說明還有資料沒有傳送完成,需要繼續傳送。
詳見 JAVA非阻塞IO、非同步IO(NIO、AIO)

一、Netty入門程式

1.1 Netty開發服務端程式碼的流程:

1.建立EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
2.初始化ServerBootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
3.配置ServerBootstrap
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 128);
bootstrap.childHandler()
4.繫結埠並啟動服務
try {
    ChannelFuture future = bootstrap.bind(PORT).sync();
    future.channel().closeFuture().sync();
} catch (InterruptedException e) {
    ...
} finally {
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}
在這個過程中,bind方法會同步阻塞,直到服務端繫結成功。當關閉future的channel時,表示服務端已關閉,這時可以安全地關閉EventLoopGroup,釋放資源。

1.1.1 服務端完整程式碼

TimeServer
package Netty.Server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Created by Idea 14 whih "netty"
 *
 * @Auhor: karl.zhao
 * @Email: karl.zhao@qq.com
 * @Date: 2015-11-28
 * @Time: 15:54
 */
public class TimeServer {
    public void bind(int port)throws Exception{
        /* 配置服務端的NIO執行緒組 */
        // NioEventLoopGroup類 是個執行緒組,包含一組NIO執行緒,用於網路事件的處理
        // (實際上它就是Reactor執行緒組)。
        // 建立的2個執行緒組,1個是服務端接收客戶端的連線,另一個是進行SocketChannel的
        // 網路讀寫
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup WorkerGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap 類,是啟動NIO伺服器的輔助啟動類
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,WorkerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChildChannelHandler());

            // 繫結埠,同步等待成功
            ChannelFuture f= b.bind(port).sync();

            // 等待服務端監聽埠關閉
            f.channel().closeFuture().sync();
        }finally {
            // 釋放執行緒池資源
            bossGroup.shutdownGracefully();
            WorkerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
        @Override
        protected  void initChannel(SocketChannel arg0)throws Exception{
            arg0.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[]args)throws Exception{
        int port = 8080;
        if(args!=null && args.length>0){
            try {
                port = Integer.valueOf(args[0]);
            }
            catch (NumberFormatException ex){}
        }
        new TimeServer().bind(port);
    }
}

TimeServerHandler
package Netty.Server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.util.Date;

/**
 * Created by Idea 14 whih "netty"
 *
 * @Auhor: karl.zhao
 * @Email: karl.zhao@qq.com
 * @Date: 2015-11-28
 * @Time: 16:13
 */
public class TimeServerHandler extends ChannelHandlerAdapter {
    // 用於網路的讀寫操作
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            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("the time server order : " + body);

        String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(
                System.currentTimeMillis()).toString() : "BAD ORDER";
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();   // 它的作用是把訊息傳送佇列中的訊息寫入SocketChannel中傳送給對方
        // 為了防止頻繁的喚醒Selector進行訊息傳送,Netty的write方法,並不直接將訊息寫入SocketChannel中
        // 呼叫write方法只是把待傳送的訊息發到緩衝區中,再呼叫flush,將傳送緩衝區中的訊息
        // 全部寫到SocketChannel中。
    }

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

1.2 Netty開發客戶端程式碼的流程:

1.建立EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
2.初始化Bootstrap
Bootstrap bootstrap = new Bootstrap();
3.配置Bootstrap
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
bootstrap.handler()
4.連線伺服器
String host = "localhost";
int port = 8080;
ChannelFuture future = bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
System.out.println("Connected to the server successfully.");
} else {
System.err.println("Failed to connect to the server.");
future.cause().printStackTrace();
}
});
// 等待連線成功或失敗
future.syncUninterruptibly();
5.關閉連線和EventLoopGroup
Channel channel = future.channel();
...
channel.closeFuture().sync();
group.shutdownGracefully();

1.2.1 客戶端完整程式碼

TimeClient
package Netty.Client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * Created by Idea 14 whih "netty"
 *
 * @Auhor: karl.zhao
 * @Email: karl.zhao@qq.com
 * @Date: 2015-11-28
 * @Time: 16:56
 */
public class TimeClient {
    public void connect(String host, int port) throws Exception {
        // 配置服務端的NIO執行緒組
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // Bootstrap 類,是啟動NIO伺服器的輔助啟動類
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(new TimeClientHandler());
                        }
                    });

            // 發起非同步連線操作
            ChannelFuture f = b.connect(host, port).sync();

            // 等待客服端鏈路關閉
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException ex) {
            }
        }
        new TimeClient().connect("127.0.0.1", port);
    }
}

TimeClientHandler
package Netty.Client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

import java.util.logging.Logger;

/**
 * Created by Idea 14 whih "netty"
 *
 * @Auhor: karl.zhao
 * @Email: karl.zhao@qq.com
 * @Date: 2015-11-28
 * @Time: 16:58
 */
public class TimeClientHandler extends ChannelHandlerAdapter {

    // 寫日誌
    private static final Logger logger =
            Logger.getLogger(TimeClientHandler.class.getName());

    private final ByteBuf firstMessage;

    public TimeClientHandler() {
        byte[] req = "QUERY TIME ORDER".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            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("Now is : " + body);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 當客戶端和服務端建立tcp成功之後,Netty的NIO執行緒會呼叫channelActive
        // 傳送查詢時間的指令給服務端。
        // 呼叫ChannelHandlerContext的writeAndFlush方法,將請求訊息傳送給服務端
        // 當服務端應答時,channelRead方法被呼叫
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.warning("message from:" + cause.getMessage());
        ctx.close();
    }
}

1.3 執行結果

啟動TimeServer,再啟動TimeClient ,執行結果如下:
服務端:

客戶端:

摘自: 李林峰《netty權威指南》

相關文章