首页 > 系统相关 >TCP IP网络编程(13) Linux下epoll与多线程

TCP IP网络编程(13) Linux下epoll与多线程

时间:2023-01-03 08:33:15浏览次数:70  
标签:文件 13 函数 epoll 描述符 多线程 event select

优于select的epoll

1. epoll的理解与应用

  select服用方法由来已久,在《TCP/IP网络编程(6) 》中,介绍了如何使用select方法实现IO复用。但是利用该技术后,无论如何优化程序性能,也难于同时接入上百个客户端(同时也是基于硬件性能的不同)。这种select方式并不适合以web服务器端开发,因此需要了解Linux平台下的epoll。

1.1 基于select的IO复用技术速度慢的原因

  针对《TCP/IP网络编程(6) 》中基于select的IO复用代码,很容易分析其中的不合理之处:

  • 调用select()函数后常见的针对所有文件描述符的循环操作
  • 每次调用select()函数时都需要向该函数传递监视对象的信息

调用select()函数之后,并不是把发生变化的文件描述符集中到一起,而是通过观察作为监视对象的fd_set变量的变化,从而找出发生变化的文件描述符。这一操作无法避免针对所有监视对象的循环语句,而且,作为监视对象的fd_set变量会发生变化,所以在调用select()函数之前,应该复制并保存fd_set变量原有的信息,并在每次调用select()函数时传递新的监视对象信息(具体可见《TCP/IP网络编程(6) 》中详细代码)

那么是哪些因素阻碍了程序性能的进一步提高呢?

  1. 循环语句 ?
  2. 每次传递的对象监视信息 ?

实际上更大的障碍是每次调用select()函数时向操作系统传递的对象监视信息,应用程序向操作系统传递数据将对程序造成很大的负担,且无法通过优化应用程序代码解决,因此成为性能上的致命弱点

因为select()与文件描述符有关,更准确的说是监视套接字变化的函数,而套接字是由操作系统进行管理的,因此select()函数必须借助操作系统才能完成其功能。

select()函数这一缺点的改进方法:仅向操作系统传第一次监视对象,监视范围或者内容发生变化的时候,只通知发生变化的事项。这样就无需每次调用select()函数的时候,都要向操作系统传第一次监视对象信息。Linux操作系统通过epoll支持这种处理方式,Windows是通过IOCP进行支持。

select()函数的优点:

  • 具备更好的兼容性 (例如epoll尽在Linux下支持,但是select具备兼容性)
  • 服务器端接入的客户端数量很少,即可使用select()
1.2 epoll实现

epoll()函数具备如下优点:

  • 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
  • 调用epoll_wait()函数(对应于select()函数)的时候无需每次都传递监视对象信息

epoll()服务器端实现中需要的3个函数:

  1. epoll_create : 创建保存epoll文件描述符的空间
  2. epoll_ctl : 向空间注册并注销文件描述符
  3. epoll_wait : 与select()函数类似,等待文件描述符发生变化

select方式中需要声明fd_set变量保存监视对象文件描述符不同的是,epoll方式下由操作系统负责保存监视对象的文件描述符,因此首先需要向操作系统请求创建保存文件描述符的空间,此操作是通过epoll_create()函数完成的。

select函数中,添加和删除监视对象的文件描述符通过FD_SET,FD_CLR来完成,在epoll方式下,需要调用epoll_ctl()函数,请求操作系统完成。

调用epoll_wait()函数等待文件描述符变化,在select方式中,通过fd_set变量查看监视对象的状态变化(事件是否发生),而在epoll方式下,通过结构体epoll_event将发生变化的文件描述符单独集中到一起。

epoll_event结构体定义

struct epoll_event
{
    __uint32 events;
    epoll_data_t data;
}

typedef union epoll_data
{
    void* ptr;
    int fd;
    __uint32 u32;
    __uint64 u64;
} epoll_data_t;

仅需申明足够大的epoll_event结构体数组后,传递给epoll_wait()函数,发生变化的文件描述符信息将被填入该结构体数组,无需像select()函数那样针对所有文件描述符进行循环。

1.3 epoll相关函数介绍
1.3.1 epoll_create函数

  epoll_create()函数是从Linux 2.5.44版本的内核开始引入的,可通过如下命令查看Linux版本内核:

cat /proc/sys/kernel/osrelease
#include <sys/epoll.h>

int epoll_create(int size);

// size : epoll实例的大小  返回值: 文件描述符,与套接字相似

调用epoll_create()函数创建的文件描述符保存空间称之为epoll例程,参数size的值决定epoll例程的大小,但是size的值只是向操作系统提供建议,size的值并非最后用来决定epoll例程的大小,仅仅供操作系统参考。

注:Linux 2.6.8之后的内核将完全忽略```epoll_create()函数传入的size参数,而是完全由操作系统系统内核自行决定epoll例程大小。

epoll_create()函数创建的资源与套接字相同,也是由操作系统进行管理。因此,该函数也会返回文件描述符,该函数返回的文件描述符主要用于区分epoll例程,需要终止时,同样需要调用close()函数进行关闭。

1.3.2 epoll_ctl函数

生成epoll例程后,应在其内部注册监视对象文件描述符。

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数含义:

  • epfd   用于注册监视对象的epoll例程文件描述符
  • op   用于指定监视对象的添加,删除,或者修改,其值和含义如下所示
    1. EPOLL_CTL_ADD : 将文件描述符注册到epoll例程
    2. EPOLL_CTL_DEL : 从epoll例程中删除文件描述符, 此时event参数应该设为NULL
    3. EPOLL_CTL_MOD : 更改注册的文件描述符的关注事件发生情况
  • fd   需要注册的监视对象的文件描述符
  • event  监视对象的事件类型

epoll_ctl()函数中,第四个参数为epoll_event结构体,此处的作用是用于在epoll例程中注册文件描述符时,用于注册关注的事件。此外之前介绍过epoll_event结构体还可以用于保存发生事件的文件描述符集合。

示例代码: 将sockfd注册到epoll例程epfd当中,并在需要读取数据的情况下产生相应的事件

struct epoll_event event;

event.events = EPOLLIN;     // 发生需要读取数据的情况时
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

epoll_event的成员events可取的值及其含义如下所示:

  • EPOLLIN  需要读取数据的情况
  • EPOLLOUT  输出缓冲区为空,可以立刻发送数据的情况
  • EPOLLPRI  收到OOB数据的情况
  • EPOLLRDHUP   套接字在断开连接或者半关闭的情况,在边缘触发方式下非常有用
  • EPOLLERR  发生错误的情况
  • EPOLLET  以边缘触发的方式得到事件通知
  • EPOLLONESHOT  发生一次事件后,相应的文件描述符不再收到事件通知。因此需要向epoll_ctl()函数的第二个参数传递EPOLL_CTL_MOD,在此设置事件。

上述的多个参数可通过位或运算同时进行传递。

1.3.3 epoll_wait函数
#include <sys/epoll.h>
int epoll_wait();

标签:文件,13,函数,epoll,描述符,多线程,event,select
From: https://www.cnblogs.com/ncepubye/p/17020588.html

相关文章