TCP/IP网络编程学习笔记(9)
-
epoll模型的实现过程:
- 使用
epoll_create()
在内核中创建一个epoll例程
; - 将要监视的socket文件描述符存放到一个
epoll_event
结构体中,并设定要监视此文件描述符的哪种事件(如EPOLLIN
事件,表示有数据进来时就唤醒进程); - 将上述设置好的
epoll_event
结构体通过epoll_ctl()
函数注册到第一步创建的epoll例程
中; - 在堆空间创建一个
epoll_event
动态数组,用于返回当前被触发的文件描述符相关信息; - 使用
epoll_wait()
,并传入上述创建的epoll_event
动态数组,等待注册的文件描述符发生了相应的事件; - 循环遍历
epoll_event
动态数组(遍历个数是epoll_wait()
的返回值),匹配到对应的文件描述符,做对应的事情(比如匹配到是server的socket,则就可以通过accept()
来建立与客户端的连接,并将客户端的socket文件描述符再注册到epoll例程
中)。
- 使用
-
epoll_event
结构体有两个作用:1). 返回当前发生事件的文件描述符集合(使用epoll_wait()
函数);2). 用于注册指定文件描述符指定的事件(使用epoll_ctl()
函数); -
epoll与select的相似之处:
epoll_create()
<==>fd_set结构体
epoll_ctl()
<==>FD_SET()
,FD_CLR()
epoll_wait()
<==>select()
-
注意,通过
epoll_create()
创建的文件描述符也是系统的,所以用完后必须close()
; -
相比于select,epoll高效的原因是:
- select每次调用
select()
时都要传入fds数组,而epoll在内核维护了一个等待队列,每次调用epoll_wait()
时不需要传入等待队列进去,只在执行epoll_ctl()
时才更新等待队列。降低了内核与用户层的数据迁移; -
select()
返回后每次都要遍历所有被监视的文件描述符,而epoll_wait()
返回时已经将有事件发生的文件描述符放到了一个数组中,所以用户层只需要遍历有事件发生的文件描述符即可,大大降低了遍历范围; - select是将当前进程添加到每个被监视的socket的等待队列里面,等事件发生时,就会从所有socket的等待队列里面移除当前进程,并将当前进程放到工作队列里面,从而唤醒当前进程来处理。epoll并不直接处理进程,epoll通过
epoll_create()
创建的epoll例程中会有一个就绪列表,当某个socket有事件发生时,内核会将此socket的引用存入到就绪列表里面。而epoll_wait()
查看就绪列表是否为空,如果为空,就阻塞进程,如果不为空,就唤醒进程。
- select每次调用
-
epoll模型中,条件触发是指只要输入缓冲中有数据,就会一直通知该事件。也就是说如果输入缓冲中有50个字节数据时,系统会通知该事件(注册到发生变化的文件描述符),如果服务器只读取了20字节后还剩30字节的话,仍会注册该事件。只要输入缓冲中有数据,就将以事件的方式再次注册。而边缘触发不一样,它在输入缓冲中收到数据时只注册1次该事件,即使输入缓冲中还留有数据,也不会再进行注册。epoll默认是条件触发,可通过
epoll_ctl
并传入EPOLLET
来使用边缘触发的方式; -
select模型是以条件触发的方式工作的,只要输入缓冲中还剩有数据,肯定会注册该事件;
-
epoll如果要使用边缘触发,则必须:
- 通过
errno
验证变量的错误原因。如果read函数-1且errno = EAGAIN
时,则表示缓冲区数据读取完了; - 必须将相应的套接字改为非租塞IO模式。通过如下方式修改:
int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag | O_NONBLOCK);
因为边缘触发方式只触发一次,所以必须要读取完数据才行。然而,由于不知道read能够几次读取完数据,所以要一直读取,直到缓冲区里面没有数据了,read函数就会返回-1,并且此时设置
errno = EAGAIN
。为啥要使用非阻塞呢,因为如果是阻塞的话,那么当缓冲区里面读取完了,read就会阻塞了,不会返回-1。注意,设置文件描述符为非阻塞的语句要放在epoll_ctl()
之前; - 通过
-
相比于水平触发,边缘触发的一大有点是:可以分离接收数据和处理数据的时间点。当服务器端收到数据时,服务器端可以选择读取和处理这些数据的时间点。如果是水平触发,则当输入缓冲区有数据时,如果不立刻读取掉,则每次调用
epoll_wait()
都会重复触发相同的事件,而且事件数也会累加,这样就可能加重服务器负担;
推荐阅读
-
python网络编程学习笔记(10):webpy框架
-
python网络编程学习笔记(六):Web客户端访问
-
python网络编程学习笔记(八):XML生成与解析(DOM、ElementTree)
-
python网络编程学习笔记(五):socket的一些补充
-
python网络编程学习笔记(七):HTML和XHTML解析(HTMLParser、BeautifulSoup)
-
python网络编程学习笔记(四):域名系统
-
python网络编程学习笔记(三):socket网络服务器
-
python网络编程学习笔记(一)
-
网络编程基础之TCP编程学习(一)
-
Python学习笔记【第十五篇】:Python网络编程三ftp案例练习--断点续传