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集合
对比
Java
1.4 NIO select
1.5 NIO ePoll
MMAP
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间
堆外内存:对于Java程序,堆外程序访问不需要Java解释器翻译内存地址,访问更快
MMAP:开启共相内存,mapbuffer内存数据直接写到文件 例如Kafka
零拷贝:发生在数据读取阶段,新增系统调用sendFile(inFd,outFd,offset),输入文件描述符,输出socket文件描述符,系统打通IO通道,直接读取数据。
NIO处理流程
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();
\