欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TCP/IP网络编程学习笔记(9)

程序员文章站 2022-06-30 18:20:16
...
  1. 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例程中)。
  2. epoll_event结构体有两个作用:1). 返回当前发生事件的文件描述符集合(使用epoll_wait()函数);2). 用于注册指定文件描述符指定的事件(使用epoll_ctl()函数);

  3. epoll与select的相似之处:

    epoll_create() <==> fd_set结构体
    epoll_ctl() <==> FD_SET(), FD_CLR()
    epoll_wait() <==> select()

  4. 注意,通过epoll_create()创建的文件描述符也是系统的,所以用完后必须close()

  5. 相比于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()查看就绪列表是否为空,如果为空,就阻塞进程,如果不为空,就唤醒进程。
  6. epoll模型中,条件触发是指只要输入缓冲中有数据,就会一直通知该事件。也就是说如果输入缓冲中有50个字节数据时,系统会通知该事件(注册到发生变化的文件描述符),如果服务器只读取了20字节后还剩30字节的话,仍会注册该事件。只要输入缓冲中有数据,就将以事件的方式再次注册。而边缘触发不一样,它在输入缓冲中收到数据时只注册1次该事件,即使输入缓冲中还留有数据,也不会再进行注册。epoll默认是条件触发,可通过epoll_ctl并传入EPOLLET来使用边缘触发的方式;

  7. select模型是以条件触发的方式工作的,只要输入缓冲中还剩有数据,肯定会注册该事件;

  8. 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()之前;

  9. 相比于水平触发,边缘触发的一大有点是:可以分离接收数据和处理数据的时间点。当服务器端收到数据时,服务器端可以选择读取和处理这些数据的时间点。如果是水平触发,则当输入缓冲区有数据时,如果不立刻读取掉,则每次调用epoll_wait()都会重复触发相同的事件,而且事件数也会累加,这样就可能加重服务器负担;