开源网络库boost.asio,libevent,mongoose学习记录以及多线程模式的实现

IO操做

IO操做包括两个部分:html

      等待数据准备好:对于一个套接口上的操做,这一步骤关系到数据从网络到达,并将其复制到内核的某个缓冲区。linux

      将数据从内核缓冲区复制到进程缓冲区。nginx

同步IO和异步IO

同步IO致使请求进程阻塞,直到IO操做完成;c++

异步IO不致使请求进程阻塞。git

IO多路复用(select,poll,epoll)

 使用c++进行网络开发,socket几乎是一切技术的基石。最简单的socket套接字用法就是listen后调用accept阻塞等待客户端的链接,每当有一个链接到来的时候,建立子套接字对链接进行处理,由于网络传输中最影响性能的是IO的读写,这样的话若是短期内有多个链接请求,socket只能一个一个的去处理。前一个IO进行读写的时候,由于进程阻塞,accept是无法准备接收下一个链接的。web

               这种状况有个简单的解决方式,就是accept返回后,在新的线程中进行数据收发,这样主线程里面的accept能够继续接收下一个客户端链接请求。这种方式能够同时处理多个IO,可是会产生建立销毁进程的开销,特别是在短任务的状况下开销会更大。服务器

              IO多路复用能够在单个线程内监听多个socket链接请求,此模型用到select和poll函数,这两个函数也会使进程阻塞,select先阻塞,有活动套接字才返回,可是和阻塞I/O不一样的是,这两个函数能够同时阻塞多个I/O操做,并且能够同时对多个读操做,多个写操做的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视全部select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,咱们就能够调用recvfrom处理数据正由于阻塞I/O只能阻塞一个I/O操做,而I/O复用模型可以阻塞多个I/O操做,因此才叫作多路复用。网络

              select,poll,epoll都是IO多路复用的机制。I/O多路复用就经过一种机制,能够监视多个描述符,一旦某个描述符就绪(通常是读就绪或者写就绪),可以通知程序进行相应的读写操做。但select,poll,epoll本质上都是同步I/O由于他们都须要在读写事件就绪后本身负责进行读写,也就是说这个读写过程是阻塞的。

多线程

select

select系统调用的目的是:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。poll和select应该被归类为这样的系统 调用:它们能够阻塞地同时探测一组链接请求,直至某一个设备触发了事件或者超过了指定的等待时间——也就是说它们的职责不是作IO,而是帮助调用者寻找当前就绪的设备。 并发

IO多路复用模型是创建在内核提供的多路分离函数select基础之上的,使用select函数能够避免同步非阻塞IO模型中轮询等待的问题。

select的优势:能够在一个线程上同时监听多个链接请求。

select的几大缺点:

(1)每次调用select,都须要把fd集合(文件描述符)从用户态拷贝到内核态,这个开销在fd不少时会很大

(2)同时每次调用select都须要在内核遍历传递进来的全部fd,这个开销在fd不少时也很大

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

poll:
  poll的实现和select很是类似,只是描述fd集合的方式不一样,poll使用pollfd结构而不是select的fd_set结构,poll支持的文件描述符数量没有限制,其余的都差很少。

epoll:
  epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此以前,咱们先看一下epoll和select和poll的调用接口上的不一样,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

  对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把全部的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每一个fd在整个过程当中只会拷贝一次。

  对于第二个缺点,epoll的解决方案不像select或poll同样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每一个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工做实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select中的实现是相似的)。

  对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目在linux上能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。

select,poll实现须要本身不断轮询全部fd集合,直到设备就绪,期间可能要睡眠和唤醒屡次交替。而epoll其实也须要调用epoll_wait不断轮询就绪链表,期间也可能屡次睡眠和唤醒交替,可是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,可是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就好了,这节省了大量的CPU时间。这就是回调机制带来的性能提高。

select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,而且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,并且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并非设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省很多的开销。

 

异步IO(iocp,epoll)
        epoll属于IO多路复用,它只是模拟实现了异步IO的功能。  “真正”的异步IO须要操做系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用便可。

          IOCP全称 IO完成端口。它是一种WIN32的网络I/O模型,既包括了网络链接部分,也负责了部分的I/O操做功能,用于方便咱们控制有并发性的网络I/O操做。它有以下特色:
1:它是一个WIN32内核对象,因此没法运行于linux。
2:它本身负责维护了工做线程池,同时也负责了I/O通道的内存池。
3:它本身实现了线程的管理以及I/O请求通知,最小化的作到了线程的上下文切换。
4:它本身实现了线程的优化调度,提升了CPU和内存缓冲的使用率。

           真正意义上的异步IO严格的来讲只有IOCP,可是epoll也模拟实现了异步IO的功能。

        epoll 由于采用 mmap的机制, 使得 内核socket buffer和 用户空间的 buffer共享, 从而省去了 socket data copy, 这也意味着, 当epoll 回调上层的 callback函数来处理 socket 数据时, 数据已经从内核层 "自动" 到了用户空间, 虽然和 用poll 同样, 用户层的代码还必需要调用 read/write, 但这个函数内部实现所触发的深度不一样了。

        poll 时, poll通知用户空间的Appliation时, 数据还在内核空间, 因此Appliation调用 read API 时, 内部会作 copy socket data from kenel space to user space。

       而用 epoll 时, epoll 通知用户空间的Appliation时, 数据已经在用户空间, 因此 Appliation调用 read API 时, 只是读取用户空间的 buffer, 没有 kernal space和 user space的switch了。

IOCP和Epoll之间的异同。
异:
1:IOCP是WINDOWS系统下使用。Epoll是Linux系统下使用。
2:IOCP是IO操做完毕以后,经过Get函数得到一个完成的事件通知。
Epoll是当你但愿进行一个IO操做时,向Epoll查询是否可读或者可写,若处于可读或可写状态后,Epoll会经过epoll_wait进行通知。
3:IOCP封装了异步的消息事件的通知机制,同时封装了部分IO操做。但Epoll仅仅封装了一个异步事件的通知机制,并不负责IO读写操做,可是由于mmap机制,epoll其实已经省去了IO操做的第二部分(将数据从内核缓冲区复制到进程缓冲区)。
4: 基于上面的描述,咱们能够知道Epoll不负责IO操做,因此它只告诉你当前可读可写了,而且将协议读写缓冲填充,由用户去读写控制,此时咱们能够作出额 外的许多操做。IOCP则直接将IO通道里的读写操做都作完了才通知用户,当IO通道里发生了堵塞等情况咱们是没法控制的。

同:
1:它们都是异步的事件驱动的网络模型。
2:它们均可以向底层进行指针数据传递,当返回事件时,除可通知事件类型外,还能够通知事件相关数据(通知到来时IO已经彻底完成)。

还有一个概念,边缘触发和水平触发,可了解也可不了解。

开源网络库boost.asio,libevent,mongoose学习记录以及多线程模式的实现

 

Libevent
libevent是一个轻量级的基于事件驱动的高性能的开源网络库,而且支持多个平台,对多个平台的I/O复用技术进行了封装。在linux下面集成了poll,epoll;在window下面集成了select,旧版本没有集成IOCP,因此在window上面 libevent的性能并不优秀,新版本的libevent也集成了IOCP,可是只做为网络开发库的话,libevent的综合评价仍是不如boost.asio。

对于网络通讯Libevent和boost.asio功能相近,可是asio综合性能更好,并且集成到了boost里面,只须要引入头文件便可使用。因此须要开发高性能web服务的时候,推荐使用asio,在这里就再也不臃述libevent。

(如对libevent有兴趣可参考https://www.cnblogs.com/nearmeng/p/4043548.html)

Boost.asio

Boost.Asio是利用当代C++的先进方法,跨平台,异步I/O模型的C++网络库,Windows下使用IOCP,Linux下使用epoll。下面是一个asio使用多线程异步IO的网络服务器demo。

 

通常来讲,高性能web服务器的io和业务处理都是分离的,服务器开销主要在io上。由于asio已经实现了异步io,因此若是只是做为转发服务器,只使用一个线程处理便可(多线程有线程切换的开销)。好比nginx就是使用单线程异步io的服务器。

可是若是io和业务处理没有分离,好比上例中处理post的时候 sleep了5秒(假设业务处理占用了5秒的时间),若是使用单线程那就会阻塞客户端新的请求(其实请求不会阻塞,只是asio的事件回调函数被阻塞了)。在这种状况下就须要使用如上例的多线程处理。

关于boost.asio介绍不错的两个连接:

https://blog.csdn.net/somestill/article/details/52159948

https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter1.html

相关文章
相关标签/搜索