博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多路复用中的select和epoll对比分析
阅读量:2490 次
发布时间:2019-05-11

本文共 5011 字,大约阅读时间需要 16 分钟。

什么是多路复用?

简单的理解就是在单线程下可以实现同时监控多个socket文件是否有IO事件到达的能力。

为什么要用多路复用?

要理解这个问题首先要先来复习一个BIO和NIO这两种网络模型。

BIO

BIO称为同步阻塞模型,也就是一个线程只能监控一个socket,并且在有IO事件到达前不能做其他任何事情,线程会一直处于阻塞状态。


调用accpet后,如果没有客户端连接,代码就会一直阻塞在这一行,直到有连接到达。

Socket client = server.accept()

读取数据时也一样,即使客户端没有发送任何数据过来,服务端也只能阻塞在这一行,等待客户端的数据请求到达。

String str = reader.readLine()

BIO的模型在Java网络编程中就是一个线程对应一个客户端。

NIO

在BIO网络模型中要想同时处理多个客户端就只能开启多线程,但是毕竟线程的资源有限,当客户端连接较多时BIO就不能适用了,于是就有了NIO这种网络模型。

NIO称为同步非阻塞模型,在此模型下,连接、读/写等IO请求即使没有数据到达也不会阻塞调用了,而是会立刻返回调用者一个约定好的错误状态,调用者只需根据自己的业务逻辑处理即可。

while (true) {
SocketChannel accept = serverSocketChannel.accept(); if (accept == null) {
System.out.println("accept 没有阻塞,而是返回了null!"); } else {
//有连接到达,可以添加到一个保存了所有到达连接的集合中 socketChannelList.add(accept); } //遍历集合中所有连接进来的客户端 Iterator
iterator = socketChannelList.iterator(); while (iterator.hasNext()) {
SocketChannel sc = iterator.next(); int read = sc.read(byteBuffer); if (read > 0) {
//有数据到达 } else {
//没数据到达 } } }

NIO虽然可以实现非阻塞了,但是问题也很明显,服务端每次都需要遍历所有连接进来的客户端,挨个询问是否有数据到达(调用read函数),每一次的询问就会产生系统调用,造成用户态与内核态的切换,假如服务端维护着1000个客户端的连接,其中只有1个客户端有请求到达,那就意味着服务端的999次都是无效的请求。

现在NIO也无法解决同时处理大量客户端的问题,所以就出现了多路复用,它必然能够解决NIO中的无效系统调用的问题。

多路复用是如何解决NIO存在的问题?

现在我们已经知道NIO中主要问题就是可能会存在大量的无效系统调用,那么在多路复用模型中,思路很简单,就是由服务端告诉内核对哪些socket的哪些事件感兴趣,那么当有对应的事件到达时,内核就会主动通知调用者,这样就避免了无效的调用了。

select、poll、epoll对比

在linux中多路复用存在select、poll、epoll三种实现方式,其中epoll是现在用的最多的实现方式。

select

在这里插入图片描述

int       main(void)       {
fd_set rfds; struct timeval tv; int retval; /* Watch stdin (fd 0) to see when it has input. */ FD_ZERO(&rfds); FD_SET(0, &rfds); /* Wait up to five seconds. */ tv.tv_sec = 5; tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); /* Don’t rely on the value of tv now! */ if (retval == -1) perror("select()"); else if (retval) printf("Data is available now.\n"); /* FD_ISSET(0, &rfds) will be true. */ else printf("No data within five seconds.\n"); exit(EXIT_SUCCESS); }

在调用select时可以传入多个fds,并告知对这些fds的哪些事件关心,比如只关心读事件,那么一旦有读事件到达该方法就会返回,你只需遍历这些fds,获取数据即可。

可以看出有了select函数后,只有当有IO事件到达时,你才会去遍历fds,而在之前的NIO中无论是否有数据到达都必须遍历所有fds。

select缺点:

  • 如果fds很多时,每次都需要先把fds从用户空间复制到内核空间,事件到达时再从内核空间复制到用户空间,有一定的消耗
  • linux内核中,单个进程能够打开的fds数量有限。
  • 假设我们告诉select,关心的fds数量有1000个,那么只要有一个有事件到达,select就会返回,然而调用者还是不知道具体是哪一个fds有事件,所以还是需要从头到尾遍历一次。

poll

poll和select没有太大的区别,poll主要解决了select中fd数量限制的问题,其他select中存在的问题poll中依然存在。

epoll

epoll分为3个阶段:epoll_create、epoll_ctl、epoll_wait。

先用epoll_create创建eventpoll对象并返回对应的epollfd,再通过epoll_ctl把需要监控的fd添加到eventpoll对象中,最后调用epoll_wait等待数据。

#define MAX_EVENTS 10           struct epoll_event ev, events[MAX_EVENTS];           int listen_sock, conn_sock, nfds, epollfd;           /* Set up listening socket, 'listen_sock' (socket(),              bind(), listen()) */           epollfd = epoll_create(10);           if (epollfd == -1) {
perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listen_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) {
perror("epoll_pwait"); exit(EXIT_FAILURE); } for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock, (struct sockaddr *) &local, &addrlen); if (conn_sock == -1) {
perror("accept"); exit(EXIT_FAILURE); } setnonblocking(conn_sock); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
perror("epoll_ctl: conn_sock"); exit(EXIT_FAILURE); } } else {
do_use_fd(events[n].data.fd); } } }

就绪列表

select其中有一个问题就是在一批fds中不知道具体哪些是真正的有数据到达,只能一个个遍历,而在epoll中,当通过epoll_ctl函数添加或者删除socket时,除了使用红黑树的结构帮我们维护这些socket之外,还会向内核的中断程序注册一个回调函数,那么当fd中断时就会调用回调函数,把中断的fd放到就绪里链表中(一种双向链表的数据结构),当epoll_wait调用时,就可以直接通过这个就绪链表获取数据即可。

这里就同时解决了两个select中的问题:

  1. 不需要遍历所有fd,挨个询问具体哪个fd数据到达了。
  2. 只会把真正有事件到达的fds从内核空间拷贝到用户空间。

总结

在这里插入图片描述

转载地址:http://polrb.baihongyu.com/

你可能感兴趣的文章
uni-app 全局变量的几种实现方式
查看>>
echarts 为例讲解 uni-app 如何引用 npm 第三方库
查看>>
uni-app跨页面、跨组件通讯
查看>>
springmvc-helloworld(idea)
查看>>
JDK下载(百度网盘)
查看>>
idea用得溜,代码才能码得快
查看>>
一篇掌握python魔法方法详解
查看>>
数据结构和算法5-非线性-树
查看>>
数据结构和算法6-非线性-图
查看>>
数据结构和算法7-搜索
查看>>
数据结构和算法8-排序
查看>>
windows缺少dll解决办法
查看>>
JPA多条件动态查询
查看>>
JPA自定义sql
查看>>
BigDecimal正确使用了吗?
查看>>
joplin笔记
查看>>
JNDI+springmvc使用
查看>>
vue+springboot分页交互
查看>>
vue+springboot打包发布
查看>>
XSL 开发总结
查看>>