服务器要接受a、b、c、d、e几个客户端的数据;客户端越多,服务器维持的连接也要越多。随着n多个客户端的,如何知道客户端发送到服务器端的数据?调用receive函数之前,如何知道有数据来的?把所有的io所有的socket放在一起,中间有数据来了,立马可以检测得到叫做io多路复用,select、poll、epoll。
作用:io放在一起,加在epoll这种io多路复用里面检测到什么数据可以读,这时候就可以调用receive。
单个进程可监视的fd数量被限制(32位1024,64位2048)
对socket是线性扫描,即轮询,效率较低:浪费cpu时间
内核需要将消息传递到用户空间,都需要内核拷贝动作。需要维护一个用来存放大量fd的数据结构,使得用户空间和内核空间在传递该结构时复制开销大。
1.每次调用select,都需把fd集合从用户态拷贝到内核态,fd很多时开销就很大
2.同时每次调用select都需在内核遍历传递进来的所有fd,fd很多时开销就很大
3.select支持的文件描述符fd数量太小了,默认最大支持1024个
4.主动轮询效率很低
poll:
poll没有最大文件描述符数量的限制。其余也与select相似,基于轮训
epoll:
1.事件驱动,修改主动轮询为被动通知
2.LT,默认的模式(水平触发) 只要该fd还有数据可读,每次 epoll_wait 都会返回它的事件,提醒用户程序去操作,
ET是“高速”模式(边缘触发)
没有最大连接数限制、效率提升、内存拷贝、epoll内核空间和用户空间共享内存
总结
4 总结
select,poll,epoll都是IO多路复用机制,即可以监视多个描述符,一旦某个描述符就绪(读或写就绪),能够通知程序进行相应读写操作。 但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
accept的listen的实现
udp并发的实现
qq早期版本用udp?为什么用udp?
tcp面向连接,服务器上面通过select只能用来检测fd,一个进程里面fd不能太多。1024个
selsect要copy到内核,然后再copy出来。
select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds 遍历循环
timeout 多长时间轮训-降低
selsect中的fd不能太多,nfds穿的不是数量,而是传的最大值。
tcp并发量不高,udp高吗?不是因为这样
udp并发可以模拟tcp,可以采用不用管理fd可以采用一个连接
早期只有select
现在有了epoll可以用tcp,io数量增加。
云主机默认选择linux,跟epoll关系很大;epoll检测是否有数据
udp服务器分配fd,再发一个数据,这样可以避免每一个链接里面的脏数据。
异步io,
为什么et模式下,socket文件描述符要设置成非阻塞的?
阻塞IO:当你去读一个阻塞的文件描述符时,如果在该文件描述符上没有数据可读,那么它会一直阻塞(通俗一点就是一直卡在调用函数那里),直到有数据可读。当你去写一个阻塞的文件描述符时,如果在该文件描述符上没有空间(通常是缓冲区)可写,那么它会一直阻塞,直到有空间可写。以上的读和写我们统一指在某个文件描述符进行的操作,不单单指真正的读数据,写数据,还包括接收连接accept(),发起连接connect()等操作…
非阻塞IO:当你去读写一个非阻塞的文件描述符时,不管可不可以读写,它都会立即返回,返回成功说明读写操作完成了,返回失败会设置相应errno状态码,根据这个errno可以进一步执行其他处理。它不会像阻塞IO那样,卡在那里不动!!!
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
所以ET所以循环处理,保证能将数据读取完毕,即同时要保证非阻塞IO,不然最后会被阻塞。