it_worker365

   ::  ::  ::  ::  :: 管理

linux内核将所有外部设备看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个文件描述符fd,对一个socket的读写也会有相应的文件描述符,文件描述符就是一个数字,指向了内核中的一个结构体。

I/O模型:

  • 阻塞

    在进程空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误才返回,期间一直等待
  • 非阻塞

    recvfrom从应用层到内核的时候,如果该缓冲区没有数据的话,就直接返回一个错误,一般对非阻塞模型进行轮训检查这个状态,看内核是不是有数据到来
  • i/o复用,减少系统创建进程线程的开销

    本质上是同步io,因为他们都需要在读写时间就绪后自己负责进行读写,读写过程是是阻塞的。而异步不需要自己负责读写
    select/poll 进程通过将一个或者多个fd传递给select/poll系统调用,阻塞在select操作上,这样select/poll可以检测(顺序扫描fd是否就绪)到多个fd是否处于就绪状态。
    epoll 使用基于事件驱动的方式代替顺序扫描,性能更高,当有fd就绪时,立即回调函数rollback
  • 信号驱动

    数据就绪,为该进程生成一个信号,通过信号回调通知应用程序读取数据,并通知主循环处理数据。--由内核通知何时可以开始一个io操作
  • 异步

          告知内核启动了某个操作,并让内核在整个操作完成之后(包括数据从内核复制到用户自己的缓冲区)通知应用。--由内核通知io操作何时已经完成

select/poll/epoll详解

select:

select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

poll:

管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。没有最大连接数的限制,原因是它是基于链表来存储的,poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

epoll:

epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

优点

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

看到这里有一个例子

https://blog.csdn.net/davidsguo008/article/details/73556811

有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的

在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。

epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分:

1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

3)调用epoll_wait收集发生的事件的连接

如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

posted on 2018-07-28 08:20  it_worker365  阅读(107)  评论(0编辑  收藏  举报