Netty入门

98 阅读5分钟

Netty相比于Socket的优缺点

  • Socket的缺点
    • Socket的Api繁多,学习成本高
    • 要想编写出高质量的NIO程序需要熟悉多线程编程
  • Netty的优点
    • Api简单,学习成本低
    • 功能强大,内置多种协议
    • 性能高
    • 社区活跃,会及时的更新处理漏洞

客户端代码

package com.example.netty_demo.client;

import com.example.netty_demo.handler.MyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class MyClient {

    public static void main(String[] args) throws Exception {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //创建bootstrap对象,配置参数
            Bootstrap bootstrap = new Bootstrap();
            //设置线程组
            bootstrap.group(eventExecutors)
                //设置客户端的通道实现类型    
                .channel(NioSocketChannel.class)
                //使用匿名内部类初始化通道
                .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //添加客户端通道的处理器
                            ch.pipeline().addLast(new MyClientHandler());
                        }
                    });
            System.out.println("客户端准备就绪,随时可以起飞~");
            //连接服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            //对通道关闭进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            //关闭线程组
            eventExecutors.shutdownGracefully();
        }
    }
}

服务端代码

package com.example.netty_demo.server;

import com.example.netty_demo.handler.MyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;

public class MyServer {
    public static void main(String[] args) throws Exception {
        //创建两个线程组 boosGroup、workerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务端的启动对象,设置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            //设置两个线程组boosGroup和workerGroup
            bootstrap.group(bossGroup, workerGroup)
                //设置服务端通道实现类型    
                .channel(NioServerSocketChannel.class)
                //设置线程队列得到连接个数    
                .option(ChannelOption.SO_BACKLOG, 128)
                //设置保持活动连接状态    
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                //使用匿名内部类的形式初始化通道对象    
                .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //给pipeline管道设置处理器
                            socketChannel.pipeline().addLast(new MyServerHandler());
                        }
                    });//给workerGroup的EventLoop对应的管道设置处理器
            System.out.println("java技术爱好者的服务端已经准备就绪...");
            //绑定端口号,启动服务端
            ChannelFuture channelFuture = bootstrap.bind(6666).sync();
            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

bootStrap和ServerBootStrap

分别是为客户端和服务端启动类提供一个工厂类

使用BootStrap和ServerBootStrap创建启动器的步骤

  1. 设置EventLoopGroup线程组
  2. 设置channel通道类型
  3. 设置option参数
  4. 设置handler流水线
  5. 进行端口绑定
  6. 启动
  7. 等待通道关闭
  8. 优雅关闭

group()

服务端必须要两个线程组,使用时可以设置线程,默认线程数是Cpu核数的两倍。

  • bossGroup:负责监听客户端的连接,专门负责与客户端进行连接,并把连接注册到WorkerGroup的Selector里。
  • WorkerGroup:workerGroup负责处理每个连接发生的读写事件。

channel

用于设置通道类型

  • NioSocketChannel:异步非阻塞客户端TCP Socket连接
  • NioServerSocketChannel:异步非阻塞服务端TCP Socket连接
  • OioSocketChannel:同步阻塞
  • OioServerSocketChannel:...
  • NioSctpChannel:异步客户端sctp连接
  • NioSctpServerChannel: ...

option和childOption

  1. option设置的是服务端接收到的连接,也就是bossGroup进程
    • ServerSocketChannel参数,option的参数
      • SO_BACKLOG Socket参数:服务端默认接收连接的队列的长度,如果连接已满,则拒绝客户端的连接。默认值:windows200 其他128。
  2. childOption设置的是客户端的连接,workerGroup进程
    • SocketChannel参数,childOption的参数
      • SO_RCVBUF Socket参数:TCP数据接收缓冲区大小。
      • TCP_NODELAY TCP参数:立即发送数据,默认为true
      • SO_KEEPALIVE Socket参数:连接保持活性,默认false,当开启时,TCP会主动探测空闲连接的有效性。

设置流水线

  • channelHandler是具体处理请求的处理器,实际上每一个channel都有一个处理器的流水线。
  • bootStrap中childHandler方法需要初始化管道实例化channelInitializer这时候需要重写initChannel()初始化管道的方法
  • 处理器Handler有两种方法
    • channelInboundHandlerAdapter(入站处理器)
      • ChannelActive 连接建立事件
      • ChannelRead 读取事件
      • ChannelReadComplete 读完成事件
    • channelOutboundHandler(出站处理器)
      • bind 绑定端口
      • connect 连接服务端
      • read 读事件
      • write 写事件
      • flush 刷新事件
      • close 关闭channel

优雅的关闭EventLoopGroup

  • 调用.shutdowmGracefully()方法

channel

  • 为用户提供
    • 当前的状态
      • 获取状态的方法
        • isOpen: 通道是否打开
        • isRegister:通道是否注册到EventLoop
        • isActive:是否通道处于活动并已连接
        • isWritable:当I/O线程立即执行请求的写入操作时
    • channel配置参数(例如接收缓冲区的大小)
      • 获取配置参数的方法
        •   ChannelConfig config = channel.config();//获取配置参数
            //获取ChannelOption.SO_BACKLOG参数,
            Integer soBackLogConfig = config.getOption(ChannelOption.SO_BACKLOG);
            //因为我启动器配置的是128,所以我这里获取的soBackLogConfig=128
          
    • channel支持的IO操作(读,写,连接,绑定),以及处理与channel相关联的所有IO事件和请求的channelpipeline
      • 读,写等操作
        • write,read,flush
        •  //获取ChannelPipeline对象
           ChannelPipeline pipeline = ctx.channel().pipeline();
           //往pipeline中添加ChannelHandler处理器,装配流水线
           pipeline.addLast(new MyServerHandler());
          

Selector

用于监听事件,管理注册到Selector中的channel,实现多路复用器。

pipeline和channelPipeline

一个channel中只能有一个channelPipeLine,这个channelPipeLine在channel创建时被创建,channelPipeline包含了一个channelHandler的列表,且所有channelHandler都会注册到channelPipeLine中。

ChannelHandlerContext

image.png

eventLoopGroup

每个Group包含一个或多个EventLoop,而每个EventLoop中维护一个Selector实例。

轮询机制的实现原理

  1. 变量 ++;
  2. 返回相应变量作为下标的结果。

Netty怎么实现一个端口支持多种协议

  1. 协议探测(使用ByteToMessageDecoder):根据数据的前几个字节(至少4个字节(HTTP和WebSocket的前几个字节相同))判断出消息所属的协议类型。
  2. 修改pipeline:移除当前的处理器链,添加相应协议的处理器链到pipeline中
  3. 消息传递:将已读取的数据传递给新的处理器