Netty的入门日记-Hello Netty

105 阅读4分钟

netty的简介

  • API使用简单
  • 功能强大
  • 定制能力强
  • 性能高
  • 社区活跃
  • 有成熟的商业应用

netty为什么采用NIO?

因为NIO具有异步、高性能的特点。

Hello Netty

示例程序分为服务端与客户端。每一个端都包括了三个主要类,分别为业务处理类(Handler)、channel初始化类(Initializer)、启动类(Run)

服务端启动类

public class Server {
    public static void main(String[] args) throws InterruptedException {
        //Configure the server
        //创建两个EventLoopGroup对象
        //创建boss线程组 用于服务端接受客户端的连接
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        /// 创建 worker 线程组 用于进行 SocketChannel 的数据读写
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ServerInitializer())
            ;
            ChannelFuture f = b.bind(8080);
            f.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }
}

服务端初始化器

public class ServerInitializer extends ChannelInitializer {
    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();
    private static final ServerHandler SERVER_HANDLER = new ServerHandler();
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);
        pipeline.addLast(SERVER_HANDLER);
    }
}

服务端业务处理类

public class ServerHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
        ctx.write("It is " + new Date() + " now.\r\n");
        ctx.flush();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
        String response = null;
        boolean close = false;
        if (request.isEmpty()) {
            response = "Please type something.\r\n";
        }else if ("bye".equals(request)){
            response = "Have a good day!\r\n";
            close = true;
        }else {
            response = "Did you say '" + request + "'?\r\n";
        }
        ChannelFuture future = ctx.writeAndFlush(response);
        if (close){
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

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

客户端启动类

public class Client {
    public static void main(String[] args) throws InterruptedException, IOException {
        NioEventLoopGroup loopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(loopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());
            Channel ch = bootstrap.connect("127.0.0.1", 8080).sync().channel();
            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }

                // Sends the received line to the server.
                lastWriteFuture = ch.writeAndFlush(line + "\r\n");

                // If user typed the 'bye' command, wait until the server closes
                // the connection.
                if ("bye".equals(line.toLowerCase())) {
                    ch.closeFuture().sync();
                    break;
                }
                // Wait until all messages are flushed before closing the channel.
                if (lastWriteFuture != null) {
                    lastWriteFuture.sync();
                }
            }
        }finally {
            loopGroup.shutdownGracefully();
        }
    }
}

客户端初始化器

public class ClientInitializer extends ChannelInitializer<SocketChannel> {
    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();

    private static final ClientHandler CLIENT_HANDLER = new ClientHandler();
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);

        pipeline.addLast(CLIENT_HANDLER);
    }
}

客户端业务处理类

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.err.println(msg);
    }

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

基本组件的说明

channel

channel为NIO的最基本组件,表示着每一个客户端的连接。它所处的状态以及产生的事件,就是我们在每一个完成客户端连接生命周期中的不同阶段的处理。

生命周期:

  • unregistered: channel已经创建,但为分配EventLoop
  • registered: channel分配到EventLoop
  • active: 连接成功
  • inactive: 未连接

channelPipeline

pipeline就是包含了这些个handler,并按照一定的顺序去依次执行。存储这些handler的是双向链表,且是入站与出站两个工作流混在一起。

重要方法:

  • addFirst 添加到开头
  • addBefore 添加当前位置之后
  • addAfter 添加当前位置之前
  • addLast 添加到末尾
  • remove 移除一个handler
  • replace 替换一个handler
  • get 获取一个handler
  • context 返回上下文
  • names 返回所有handler的名称

channelHandler

在netty中就如上述所说,每一次连接的不同阶段都会产生对应的事件和状态的改变,它们可以分为两个工作流,一个为入站一个为出站。其中入站操作就包括:连接被激活、连接失效、数据读取、用户相应事件和错误事件等等。而出站操作就包括:打开或关闭连接、讲数据冲刷到套接字中等等。当然,netty官方提供了很多的预制handler可以调用。

重要回调方法

  • handlerAdded 被添加到pipeline调用
  • handlerRemoved 被删除到pipeline调用
  • exceptionCaught 执行出错调用
  • channelRead 读事件回调

channelFuture

Netty中所有的I/O操作都是异步的,我们知道“异步的意思就是不需要主动等待结果的返回,而是通过其他手段比如,状态通知,回调函数等”,那就是说至少我们需要一种获得异步执行结果的手段。JDK预置了interface java.util.concurrent.Future,Future提供了一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以Netty提供了它自己的实现ChannelFuture,用于在执行异步操作的时候使用。一般来说,每个Netty的出站I/O操作都将返回一个ChannelFuture。