NIO
核心概念
Netty框架就是对NIO编程的封装,那么就需要知道NIO底层的几个核心概念:
- Channel(通道) :传递内容的渠道(socket的集合)
- Selector(多路复用器):前台小姐姐,用来关注那些channel注册的感兴趣的事件
- SelectionKey :用来标识某个事件
- Buffer(缓冲区) :存放数据
简言概之:根据不同的SelectionKey来区分不同事件,channel先去selector里注册想要什么事件,来了相应事件Selector则去通知不同的channel。
那么NIO如何去感知事件是否就绪呢?
我们先需要知道在Linux下,通过什么来标识一个文件
文件描述符:File descriptor,简称fd。 当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件,其fd本质上就是一个非负整数
select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout)
不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
epoll(重点)
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,可以看到epoll做了更细致的分解,包含了三个方法,使用上更加灵活。
epoll底层结构由rdllist 就绪列表(双向链表)和等待队列构成,并且其存储socket的数据结构为红黑树。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
int epoll_create(int size)
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
函数是对指定描述符fd执行op操作。
epfd:是epoll_create()的返回值。
op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
epoll_event:是告诉内核需要监听什么事,有具体的宏可以使用,比如EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);EPOLLOUT:表示对应的文件描述符可以写;
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
select、poll、epoll的比较
1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。
Netty
Netty相对于NIO模型,做了进一步升级:
-
首先是主从Reactor模型即Netty搞了两个线程池BossGroup和WorkGroup,其底层有NioEventLoop组成,而NioEventLoop由selector和 TaskQueue(LinkedBlockingQueue)组成。BossGroup里的NioEventLoop的selector只感兴趣连接请求,而其他读写请求由WorkGroup下的NioEventLoop的selector去感知。
-
Netty除了ServerSocketChannel(负责连接请求的channel)还有其他负责读写请求的channel(双向链表),入站事件和出站事件互相不影响,每个channel里由pipeline组成,而pipeline由一系列Handler组成
-
Netty实现了异步操作,底层使用了Future框架,即异步去进行channel的初始化操作,等客户端有请求来,再去回调initChannel()方法,加上自己想要添加的Handler。
-
Netty支持自定义的序列化协议 (可拓展性强的体现)protostuf
-
Netty使用了ByteBuf,即使用直接内存来缓存数据
-
Netty使用了无锁串行化设计思想,即一个线程搞定事件的分发,减少了线程切换的cpu损耗
下面为Netty模型图: