NIO

192 阅读3分钟

NIO处理

演进

C10K是什么 10K个并发IO问题

服务端经典的C10k问题(译) - 知乎 (zhihu.com)

(15条消息) NIO原理详解左耳听风的博客-CSDN博客nio原理

背景,假设10K个客户端建立连接,几种IO模型本质上是Linux操作系统支持的不同命令字系统调用,例如select,poll,epoll

BIO:

Socket命令字:创建并返回一个文件描述符 绑定 端口号并监听

单线程创建连接读取数据都要受到阻塞

引入问题:为什么不使用多线程?1.文件句柄受限制1024个 2.过多的线程消耗大量内存

NIO:非阻塞

操作系统支持socket可以通过setblock设置为非阻塞,需要应用程序自己去遍历每一个socket连接,判断是否有数据

解决阻塞问题,一个线程轮询遍历即可。

引入问题:10K个连接,应用程序不知道当前哪个连接有数据,每次需要程序自己去遍历全部连接

Select:多路复用

操作系统内核支持select 命令字,支持应用程序通过一次请求获取哪些fd当前有数据

解决问题:程序自己去遍历全部的连接

引入问题:程序每次需要重复传递大量数据,10K个fd,内核需要主动遍历10K个fd,遍历下沉。

列表有限 Array

Poll:

列表无限大 List

Epoll:(同步IO)基于事件响应,Redis,JDK1.5,Netty

操作系统支持epoll命令字,支持应用程序通过多路复用器知道哪些fd有数据,自己调用recieve方法操作数据。

epoll create : 监听

epoll ctl(modify,create,delete): 注册文件描述符到多路复用器,read\write 监听

epoll wait: 通知可读写的fd集合

对比

image-20220510233737841

Java

1.4 NIO select

1.5 NIO ePoll

MMAP

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间

堆外内存:对于Java程序,堆外程序访问不需要Java解释器翻译内存地址,访问更快

MMAP:开启共相内存,mapbuffer内存数据直接写到文件 例如Kafka

零拷贝:发生在数据读取阶段,新增系统调用sendFile(inFd,outFd,offset),输入文件描述符,输出socket文件描述符,系统打通IO通道,直接读取数据。

image-20220510230822068

NIO处理流程

image-20220506233536362

epoll使用以及原理

// 开启服务端channel用户获取连接
ServerSOcketChannel serverSocket = ServerSocketChannel.open
​
// 绑定端口
serverSocket.socket().bind(new Address(9000))
​
// 打开selector处理channel,调用epoll create
Selector selector = Selector.open()
​
// 注册到selector上,监听建立连接事件
// serverSocket.register(selector,SelectionKey.OP-ACCEPT)

Netty原理

server

server handler

处理收发消息的核心逻辑

public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
​
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }
​
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

server

        
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
    
            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
    
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();

\