selector
在BIO中 serverSocket.accept() 会阻塞,而在NIO中使用selector来监视发生io事件的ServerSocket (accept事件) 以及SocketChannel (read connect write)。当有selector 中注册的SocketChannel 或者ServerSocketChannel 发生相应的事件后,selector 会通过监视相应的socket 通过select操作得到发生某种事件的SelectionKey集合。之后我们便通过SelectionKey 来获得是哪个SocketChannel发生了什么事件,比如对于发生accept事件的必然是ServerSocketChannel。
1 逻辑上我们理解了Selector的作用就是监视socket 并通过select操作获取事件就绪的Socket.那么底层是怎么知道哪些事件就绪呢?
epoll 是一种io多路复用技术,可以高效的处理数以百万计的socket句柄。使用epoll_create创建epoll文件描述符,epoll_ctl 为监测的文件描述符注册相应事件,将需要监控的socket放入内核的红黑树上,epoll_wait 获取就绪的socket文件描述符个数,这是通过事件驱动机制,当socket对应的事件发生时,会通过回调函数将socket加入就绪链表。
select 实现多路复用的方式是,将需要监听的文件描述符集合传入内核,然后内核遍历判断情况,如果就绪就标志为可读或者可写状态。将整个文件描述符集合返回。用户空间在遍历获取可读或者可写的文件描述符。所以select实现io多路复用的方式是需要两次遍历,一次是在内核空间一次是再用户空间。同时会有数据的拷贝。
poll实现io多路复用的方式是类似的,只不过将bitMap换做链表,突破了监控的socket文件描述符的个数。
综上所述 epoll相比于select或者poll ,优势是每次监控不用重复将所有监控数据从用户空间和内核空间来回拷贝,而是在epoll_ctl一次在内核中形成要监控的socket数据结构(红黑树),epoll_wait返回给用户空间的也是直接就绪的链表数据(socket句柄)。
epoll支持两种模式的就绪事件触发。水平 和边缘。水平即数据就绪后 只要还有数据就会一直触发就绪事件让应用程序读取数据。边缘触发则会只在数据就绪后触发一次,需要应用程序一次性把数据读完。
epoll 是解决 C10K 问题的利器,通过两个方面解决了 select/poll 的问题。epoll
在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删查一般时间复杂度是
O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket
集合,减少了内核和用户空间大量的数据拷贝和内存分配。epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的
Socket 集合传递给应用程序,不需要像 select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket
),大大提高了检测的效率。作者:小林coding
链接:https://www.zhihu.com/question/32163005/answer/1802684879 来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
netty基础知识
1、channel 是什么?
channel代表一个连接,可以方便的进行read\write\bind\connect等操作。该channel关联的ChannelPipeLine可以处理所有的io事件和绑定在这个channel上的请求。
2、Channel与EventLoop
一个channel只能注册到一个eventloop中
一个eventloop可以处理多个channel
3、Channel 和ChannelPipeLine
channel包含ChannelPipeLine,ChannelPipeLine 是由channelHandlerConext组成的双向链表。headContext tailContext,每一个context关联着一个ChannelHandler。
4、NIO Server端 创建能接收连接的步骤?对应netty中是怎么做的?
创建ServerSocketChannel->创建Selector->设置SSC为非阻塞configureBlocking(false)->ssc.register(selector,0,attach) 注册->设置感兴趣事件->bind()绑定监听端口
对应netty ,其使用Reactor主从多线程模型,即Bossgroup和workerGroup 俩类,boss负责接收连接,一般一个线程就ok,worker负责每一个channel上发生的事件的处理,比如读、写等,比较耗时的任务可以使用java自带的线程池或者netty默认线程池执行。
在ServerBootStrap 调用bind方法时,会进行一些列的init register bind等操作。init主要是创建NSSC并为其指定回调方法,该回调方法是一个初始化channel的方法只执行一次,该回调函数主要是为NioServerSocketChannel新增了一个handler(定义当发生accept事件后如何处理 即一个acceptor处理器)。register主要是为ServerSocketChannel注册到selector中。bind操作是将NioServerSocketChannel绑定端口,首先是doBind将socketAddress 绑定到,之后fireChannelActive事件 ,HeadContext接收到active事件 调用channelActive 将NioServerSocketChannel对应的SelectionKey 设置感兴趣的事件SelectionKey.op_accept;
5综述
Netty 首先应该明白的是其采用的一般是主从reactor多线程模型。另外应该对Netty中的各个重要类应该有自己的理解
NioEventLoopGroup :一个NioEventLoopEventGroup包含多个NioEventLoop。
NioEventLoop:是只有一个线程的线程池,用来执行io任务 普通任务 或者定时任务。 主要属性有selector、Thread、任务队列等
Selector:是一个监听器,可以监听数以万计的连接是否有就绪事件到来。
SelectionKey 在ServerSocketChannel.register(selector,0,attach)之后会返回一个SelectionKey,通过该selectionKey可以获取原始的表示连接的channnel,以及其感兴趣的事件是否发生。
ChannelHandlerContext:使得ChannelHandler与ChannelPipeLine得以交互,同时使得可以和其他的handler也可以交互,比如可以通知下一个handler来处理事件等
ChannelPipeLine :是一个流水线 ,该流水线上有不同的入站ChannelInboundHandler或者ChannelOutboundHandler出站来进行IO操作。
ChannelHandler:每一个Handler依附一个Channel,如果一个Handler是每隔Channel都需要的比如日志、解码等操作,可以在初始化完成Channel的时候为其增加一个handler.注意一个handler可以有多个ChannelHandlerContext.
* public class DataServerInitializer extends ChannelInitializer<Channel>; {
* {@code @Override}
* public void initChannel(Channel channel) {
* channel.pipeline().addLast("handler", new DataServerHandler());
* }
* }