手写muduo笔记
网络IO的两个阶段:数据准备和数据读写
数据准备:根据系统IO操作的就绪状态,int size=recv(sockfd,buf,1024,0);
size>0有数据到来,字节数
size=-1 && errno=EAGAIN 内部错误
size==0 网络对端关闭连接
阻塞:调用IO方法的线程一直阻塞,直到sockfd有数据到来
非阻塞:没有数据到来,直接返回
数据读写:根据应用程序和内核的交互方式,指的是IO的同步和异步,
同步:把recv接受的数据(在TCP数据缓冲区中,往buf里搬,应用程序自己花时间搬)
异步:sockfd帮忙把数据放到buf里(操作系统搬的),当sigio通知应用程序时,buf的数据已经准备好了
在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO
同步:A操作等待B操作做完事情,得到返回值,继续处理
异步:A操作告诉B操作它感兴趣的事件以及通知方式,A操作继续执行自己的业务逻辑,等到B监听到相应事件发生后,B会通知A,A开始相应的数据处理逻辑
Linux上的五种IO模型
阻塞
非阻塞
IO复用 进程阻塞于select/poll/epoll等待套接字变为可读,用一个线程调用IO复用接口,可以监听很多很多个socket套接字,其他和非阻塞差不多
信号驱动(signal-driven)调用进程注册SIGIO的信号处理程序,系统来调用sigaction来告诉数据是否就绪,相当于异步,异步也是信号,但是是操作系统把数据接受好然后搬到buf再通知应用程序
也就是相当于上面的数据就绪是异步过程,下面还是需要应用程序自己从tcp 数据缓冲区中将数据读到buffer中。与非阻塞IO的区别在于它提供了消息通知机制,不需要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率
异步
异步非阻塞IO是最典型的,整个过程,从数据就绪到数据拷贝,都不耗费应用进程的时间,只要是事先通过airead来告诉内核,应用程序的buffer,通知信号,以及对哪个socket感兴趣
好的网络服务器的设计
赞同libev作者的观点:one loop per thread is usually a good model 。在一个线程里有一个事件循环
好的服务器一般都是IO复用来操作非阻塞的socket再加上线程池
reactor模型
比较出名的muduo网络库,lib-event都有基于事件驱动的reactor模型
reactor模型重要组件:Event事件,Reactor反应堆,Demultiplex事件分发器,Evanthandler事件处理器
Demultiplex事件分发器:基于IO多路复用的事件分发器
首先把事件注册到反应堆上,请求反应堆来监听我所感兴趣的事件,并且在事件发生的时候调用预制的回调Handler。启动反应堆后,反应堆的后端就会驱动这个事件分发器的启动,开启epoll_wait,整个服务器处于阻塞状态,来等待新用户的连接或者是已连接用户的读写事件 ,如果多路复用监听到有新事件发生,就会给反应堆返回,因为事件发生了,我们在reactor模型里面就要调用事件对应的处理器,reactor就会找到event对应的handler,是用一个map表来存储
reactor z主要就是存储了事件以及事件对应的处理器
Demultiplex事件分发器 可以有多进程,一个专门用来监听新用户的连接,另外可以用来监听已连接用户的读写事件,如果再有耗时的IO事件,比如说传输文件,再专门开一个事件。
epoll
select 和 poll的缺点
select的缺点:
1.单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差
2.内核/用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销
3.select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件
4.select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程
相比select模型,pool使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在
epoll的实现机制和select/poll机制完全不同,它们的缺点在epoll上不复存在
设想如下:有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是否活跃。如何实现这样的高并发?
在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完成后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接
epoll的设计和实现select完全不同。epoll通过再Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+数,磁盘IO消耗低,效率很高)。把原先的select/poll调用成一下三个部分
1.调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)(红黑树,双向链表)
2.调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3.调用epoll_wait 收集发生的事件的fd资源
如此以来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候像这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
LT模式
内核数据没被读完,就会一直上报数据
ET模式
内核数据只上报一次
muduo采用的是LT
不会丢失数据或者消息:应用没有读取完数据,内核是会不断上报的
低延迟处理:每次读数据只需要一次系统调用,照顾多个连接的公平性,不会引文某个连接上的数据量过大而影响其他连接处理消息
跨平台处理:像select一样可以跨平台使用