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


    IO操作

    IO操作包括两个部分:

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

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

    同步IO和异步IO

    同步IO导致请求进程阻塞,直到IO操作完成;

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

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

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

                   这种情况有个简单的解决方式,就是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

  • 相关阅读:
    PCB 规则引擎之脚本语言JavaScript应用评测
    PCB 挺有意思的基数排序----C#代码实现
    PCB NOSQL MongoDb MI流程指示数据存储结构
    PCB javascript解析Gerber274X格式实现方法
    PCB javascript解析钻孔(Excellon)格式实现方法
    PCB 围绕CAM自动化,打造PCB规则引擎
    PCB Genesis拼SET画工艺边 实现方法(一)
    约瑟夫环的三种解法
    Microsoft edge真香!
    商城规格参数
  • 原文地址:https://www.cnblogs.com/guxuanqing/p/12416130.html
Copyright © 2020-2023  润新知