浅谈Java的IO与Netty

一、Java的IO((Input/Output))模型

传统IO和Java NIO最大的区别是传统的IO是面向流,NIO是面向Buffer
Socket之间建立链接及通信的过程!实际上就是对TCP/IP连接与通信过程的抽象:
1.服务端Socket会bind到指定的端口上,Listen客户端的”插入”
2.客户端Socket会Connect到服务端
3.当服务端Accept到客户端连接后
4.就可以进行发送与接收消息了
5.通信完成后即可Close

1、BIO(阻塞)

1.socketServer的accept方法是阻塞的;
2.获得连接的顺序是和客户端请求到达服务器的先后顺序相关;
3.适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;
4.适合需要管理同时打开不太多的连接,这些连接会发送大量的数据。

BIO模型

客户端代码
    //Bind,Connect
    Socket client = new Socket("127.0.0.1",7777);
    //读写
    PrintWriter pw = new PrintWriter(client.getOutputStream());
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    pw.write(br.readLine());
    //Close
    pw.close();
    br.close();

服务端代码
    Socket socket;
    //Bind,Listen
    ServerSocket ss = new ServerSocket(7777);
    while (true) {
    //Accept
    socket = ss.accept();
    //一般新建一个线程执行读写
    BufferedReader br = new BufferedReader(
       new InputStreamReader(socket .getInputStream()));
       System.out.println("you input is : " + br.readLine());
    }

2、NIO(非阻塞)

1.基于事件驱动,当有连接请求,会将此连接注册到多路复用器上(selector);
2.在多路复用器上可以注册监听事件,比如监听accept、read;
3.通过监听,当真正有请求数据时,才来处理数据;
4.会不停的轮询是否有就绪的事件,所以处理顺序和连接请求先后顺序无关,与请求数据到来的先后顺序有关;
5.优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
6.适合需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据。

NIO模型

IO复用模型
NIO客户端
//连接
    //获取socket通道
    SocketChannel channel = SocketChannel.open();
    channel.configureBlocking(false);
    //获得通道管理器
    selector=Selector.open();
    channel.connect(new InetSocketAddress(serverIp, port));
    //为该通道注册SelectionKey.OP_CONNECT事件
    channel.register(selector, SelectionKey.OP_CONNECT);
//监听
    while(true){
        //选择注册过的io操作的事件(第一次为SelectionKey.OP_CONNECT)
        selector.select();
        while(SelectionKey key : selector.selectedKeys()){
            if(key.isConnectable()){
                SocketChannel channel=(SocketChannel)key.channel();
                if(channel.isConnectionPending()){
                    channel.finishConnect();//如果正在连接,则完成连接
                }
                channel.register(selector, SelectionKey.OP_READ);
            }else if(key.isReadable()){ //有可读数据事件。
                SocketChannel channel = (SocketChannel)key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(10);
                channel.read(buffer);
                byte[] data = buffer.array();
                String message = new String(data);
                System.out.println("recevie message from server:, size:" + buffer.position() + " msg: " + message);
            }
        }
    }

服务端
//连接
    //获取一个ServerSocket通道
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    serverChannel.configureBlocking(false);
    serverChannel.socket().bind(new InetSocketAddress(port));
    //获取通道管理器
    selector = Selector.open();
    //将通道管理器与通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);

//监听
    while(true){
        //当有注册的事件到达时,方法返回,否则阻塞。
        selector.select();
        for(SelectionKey key : selector.selectedKeys()){
            if(key.isAcceptable()){
                ServerSocketChannel server = (ServerSocketChannel)key.channel();
                SocketChannel channel = server.accept();
                channel.write(ByteBuffer.wrap(
                new String("send message to client").getBytes()));
                //在与客户端连接成功后,为客户端通道注册SelectionKey.OP_READ事件。
                channel.register(selector, SelectionKey.OP_READ);
            }else if(key.isReadable()){ //有可读数据事件
                SocketChannel channel = (SocketChannel)key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(10);
                int read = channel.read(buffer);
                byte[] data = buffer.array();
                String message = new String(data);
                System.out.println("receive message from client, size:" + buffer.position() + " msg: " + message);
            }
        }
    }

二、Netty的IO

1、Netty特性

谁在用Netty

1. 框架,GRPC、Dubbo、Spring WebFlux、Spring Cloud Gateway
2. 大数据,Spark、Hadoop、Flink
3. 消息队列,RocketMQ、ActiveMQ
4. 搜索引擎,Elasticsearch
5. 分布式协调器,Zookeeper
6. 数据库,Cassandra、Neo4j
7. 负载均衡,Ribbon

2、Reactor 线程模型

Reactor 模式是 Dispatcher 模式,即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程)。就是将消息放到了一个队列中,通过异步线程池对其进行消费!

Reactor单线程模型

Reactor多线程模型

Reactor主从模型

3、Netty 线程模型

Netty的线程模型是基于主从Reactor多线程做了改进。 MainReactor 负责客户端的连接请求,SubReactor 负责相应通道的 IO 读写请求。具体逻辑处理的任务写入队列,等 worker threads 进行处理。

Netty 线程模型

 4、Netty重要组件

Netty重要组件
Netty各组件关系

一、Channel

Socket 连接,负责基本的 IO 操作,传入或者传出的数据载体,可以被打开或者关闭,连接或者断开连接。
提供了以下操作:
1.当前网络连接的通道的状态(例如是否打开?是否已连接?)
2.网络连接的配置参数 (例如接收缓冲区大小)
2.提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。
  调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
4.支持关联 I/O 操作与对应的处理程序。
常用的 Channel 类型:
1.NioSocketChannel,异步的客户端 TCP Socket 连接。
2.NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
3.NioDatagramChannel,异步的 UDP 连接。
4.NioSctpChannel,异步的客户端 Sctp 连接。
5.NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

二、EventLoop

netty针对网络编程时创建的多线程进行了封装和优化,构建了自己的线程模型。
EventLoop用于处理IO事件,多线程模型、并发:
1.一个EventLoopGroup包含一个或者多个EventLoop;
2.一个EventLoop在它的生命周期内只和一个Thread绑定;
3.所有有EventLoop处理的I/O事件都将在它专有的Thread上被处理;
4.一个Channel在它的生命周期内只注册于一个EventLoop;
5.一个EventLoop可能会被分配给一个货多个Channel;
NioEventLoopGroup
NioEventLoopGroup,主要管理 EventLoop 的生命周期,可理解为一个线程池,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

三、ChannelHandler和ChannelPipeline

ChannelHandler其实就是用于负责处理接收和发送数据的的业务逻辑,Netty中可以注册多个handler,以链式的方式进行处理,根据继承接口的不同,实现的顺序也不同。
1、ChannelInboundHandler:对接收的信息进行处理。一般用来执行解码、读取客户端数据、进行业务处理等。如ByteToMessageDecoder;
2、ChannelOutboundHandler:对发送的信息进行处理,一般用来进行编码、发送报文到客户端。如MessageToByteEncoder;
ChannelPipeline
保存 ChannelHandler 的 List,为ChannelHandler链提供了容器,用于处理 Channel 的入栈事件和出栈操作。
ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

四、ByteBuf

强大高效的字节容器,提供了更加丰富的API用于字节的操作,同时保持了卓越的功能性和灵活性;传统的 I/O 是面向字节流或字符流的,以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 不能随意改变读取指针的位置。NIO引入了 Channel 和 Buffer 的概念。
在 NIO 中,只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。基于 Buffer 操作不像传统 IO 的顺序操作,NIO 中可以随意地读取任意位置的数据。

5、代码

1.客户端
/**
 * 定义Netty客户端
 */
public class NettyClient {
    public static void main(String[] args) throws Exception {
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //创建客户端启动对象 Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //设置相关参数
            bootstrap.group(group) // 设置线程组
                    .channel(NioSocketChannel.class) // 用NioSocketChannel作为客户端的通道实现
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //加入处理器
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("netty 客户端启动......");
            //启动客户端去连接服务器端
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();
            //对通道关闭进行监听
            cf.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

/**
 * 定义 客户端NettyClientHandler
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端连接服务器完成就会触发该方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ByteBuf buf = Unpooled.copiedBuffer("HelloServer".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }

    //当通道有读取事件时会触发,即服务端发送数据给客户端
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("收到服务端的消息:" + buf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
2.服务端
/**
 * 定义Netty服务端
 */
public class NettyServer {

    public static void main(String[] args) throws Exception {

        // 创建两个线程组bossGroup和workerGroup, 含有的子线程NioEventLoop的个数默认为cpu核数的两倍
        // bossGroup只是处理连接请求 ,真正的和客户端业务处理,会交给workerGroup完成
        EventLoopGroup bossGroup = new NioEventLoopGroup(3);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            // 创建服务器端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 使用链式编程来配置参数
            bootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    // 使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。
                    // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数,在 SocketChannel 建立起来之前执行
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //对workerGroup的SocketChannel设置处理器
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("netty 服务端启动......");
            // 绑定一个端口并且同步, 生成了一个ChannelFuture异步对象,通过isDone()等方法可以判断异步事件的执行情况
            // 启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture cf = bootstrap.bind(9000).sync();
            // 给cf注册监听器,监听我们关心的事件
            /*cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口9000成功");
                    } else {
                        System.out.println("监听端口9000失败");
                    }
                }
            });*/
            // 等待服务端监听端口关闭,closeFuture是异步操作
            // 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object的wait()方法
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}


/**
 * 定义服务端NettyServerHandler
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当客户端连接服务器完成就会触发该方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("客户端连接通道建立完成");
    }

    /**
     * 读取客户端发送的数据
     * @param ctx 上下文对象, 含有通道channel,管道pipeline
     * @param msg 就是客户端发送的数据
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        //Channel channel = ctx.channel();
        //ChannelPipeline pipeline = ctx.pipeline(); //本质是一个双向链接, 出站入站
        //将 msg 转成一个 ByteBuf,类似NIO 的 ByteBuffer
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("收到客户端的消息:" + buf.toString(CharsetUtil.UTF_8));
    }

    /**
     * 数据读取完毕处理方法
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ByteBuf buf = Unpooled.copiedBuffer("HelloClient".getBytes(CharsetUtil.UTF_8));
        ctx.writeAndFlush(buf);
    }

    /**
     * 处理异常, 一般是需要关闭通道
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.close();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值