《TCP/IP网络编程》第17章 优于select的epoll

本章所有示例代码>>gtihub

17.1 epoll理解及应用

1.       基于select的I/O复用技术速度慢的原因

    两点不合理:

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

    “每次调用select函数时向操作系统传递监视对象信息。”

    应用程序向操作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决,因此将成为性能上的致命弱点。(有些函数不需要操作系统的帮助就能完成功能,而有些则必须借助于操作系统)

    select函数与文件描述符有关,更准确地说,是监视套接字变化的函数。而套接字是由操作系统管理的,所以select函数绝对需要借助于操作系统才能完成功能。

    “仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。”(前提是操作系统支持这种处理方式,Linux的支持方式是epoll,Windows的支持方式是IOCP)

2.       select也有优点

    大多数操作系统都支持select函数,只要满足如下两个条件,可使用select函数:

  • 服务器接入者少;
  • 程序应具有兼容性;

3.       实现epoll时必要的函数和结构体

    epoll函数优点:

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

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

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

    select方式中为了保存监视对象的文件描述符,直接声明了fd_set变量。但epoll方式下由操作系统负责保存监视对象文件描述符,因此需要向操作系统请求创建保存文件描述符的空间,此时使用的函数就是epoll_create

    select方式中,为了添加和删除监视对象文件描述符,需要FD_SETFD_CLR函数。但在epoll方式中,通过epoll_ctl函数请求操作系统完成。

    select方式下调用select函数等待文件描述符的变化,而epoll中调用epoll_wait函数。

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

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
};
typedef union epoll_data
{
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
}epoll_data_t;

    声明足够大的epoll_event结构体数组后,传递给epoll_wait函数时,发生变化的文件描述符信息将被填入该数组。

4.       epoll_create

#include<sys/epoll.h>
int epoll_create(int size); //成功时返回epoll文件描述符,失败-1  

    -size:   epoll实例的大小,仅供操作系统参考,Linux2.6.8内核版本后将完全忽略该参数;

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

5.       epoll_ctl

    生成epoll例程后,应在其内部注册监视对象文件描述符,此时使用epoll_ctl函数。

 #include<sys/epoll.h>
 int epoll_ctl(intepfd, int op, int fd, struct epoll_event *event);
 //成功时返回0,失败时返回-1

    -epfd:   用于注册监视对象的epoll例程;

    -op:      用于指定监视对象的添加、删除和更改等操作;

    -fd:       需要注册的监视对象文件描述符;

    -event: 监视对象的事件类

调用:

    epoll_ctl(A, EPOLL_CTL_ADD, B, C);

    “epoll例程A中注册文件描述符B,主要目的是监视参数C中的事件。”

    epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);

    “从epoll例程A中删除文件描述符B。”

  • EPOLL_CTL_ADD       :   将文件描述符注册到epoll例程;
  • EPOLL_CTL_DEL:    从epoll例程中删除文件描述符;
  • EPOLL_CTL_MOD:  更改注册的文件描述符的关注事件发生情况;

    epoll_event结构体用于保存发生事件的文件描述符集合。但也可以在epoll例程中注册文件描述符时,用于注册关注的事件。

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

  • EPOLLIN:                  需要读取数据的情况;
  • EPOLLOUT:               输出缓冲为空,可以立即发送数据的情况;
  • EPOLLPRI:                 收到OOB数据的情况;
  • EPOLLRDHUP:          断开连接或半关闭的情况,这在边缘触发方式下非常有用;
  • EPOLLERR:                发生错误的情况;
  • EPOLLLET:                 以边缘触发的方式得到事件通知;
  • EPOLLONESHOT:      发生一次事件后,相应文件描述符不再收到事件通知;

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

6.       epoll_wait

#include <sys/epoll.h>
int epoll_wait(intepfd, struct epoll_event *events, int maxevents, int timeout); //成功时返回发生事件的文件描述符数,失败时返回-1

    -epfd:             表示事件发生监视范围的epoll例程的文件描述符;

    -events:          保存发生事件的文件描述符集合的结构体地址值;

    -maxevents:    第二个参数中可以保存的最大事件数;

    -timeout:        以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件

int event_cnt;
struct epoll_event*ep_event;
……
ep_events =malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
……
event_cnt =epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
……

    调用函数后,返回发生事件的文件描述符数,同时在第二个参数指向的缓冲中保存发生事件的文件描述符集合。

17.2 条件触发(Level Trigger)和边缘触发(EdgeTrigger)

1.       条件触发和边缘触发的区别在于发生事件的时间点

    如,

    服务器端输入缓冲收到50字节的数据时,服务器端操作系统将通知该事件(注册到发生变化的文件描述符)。服务器端读取20字节后还剩30字节的情况下,仍然会注册事件。

    边缘触发中输入缓冲收到数据时仅注册1次该事件。即使输入缓冲中还留有数据,也不会再进行注册。

2.       掌握条件触发的事件特性

    epoll默认以条件触发方式工作,因此可以通过22-EchoEPLTServer示例验证

3.       边缘触发的服务器端实现中必知的两点

  • 通过errno变量验证错误原因;
  • 为了完成非阻塞(Non-blocking)I/O,更改套接字特性;

    为了在发生错误时提供额外的信息,Linux提供了如下全局变量:

#include <errno.h>
int errno;

    “read函数发现输出缓冲中没有数据可读时返回-1,同时在error中保存EAGAIN常量。”

    将套接字改为非阻塞方式的方法:

    Linux提供更改或读取文件属性的如下方法。

#include <fcntl.h>
int fcntl(intfiledes, int cmd, ……); // 成功返回cmd参数相关值,失败时返回-1

    -filedes:     属性/更改目标的文件描述符;

    -cmd:         表示函数调用的目的;

    fcntl具有可变参数的形式。如果向第二个参数传递F_GETFL,可以获得第一个参数所指的文件描述符属性;如果向第二个参数传递F_SETFL,可以更改文件描述符属性。

    若希望将文件(套接字)改为非阻塞模式,需要如下2条语句:

int flag = fcntl(fd,F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);

    通过第一条语句获取之前设置的属性信息,通过第二条语句在此属性基础上添加非阻塞O_NONBLOCK标志。

4.       实现边缘触发的回声服务器端

    边缘触发方式下,以非阻塞方式工作的read& write函数有可能引起服务器端的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read& write函数。

5.       条件触发和边缘触发孰优孰劣

    边缘触发可以做到如下这点:

    “可以分离接收数据和处理数据的时间点。”

    条件触发在输入缓冲收到数据的情况下,如果不读取(延迟处理),则每次调用epoll_wait函数时都会产生相应事件,而且事件数也会累加,服务器端不能承受。

    从实现模型的角度看,边缘触发更有可能带来高性能。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP/IP是一种网络通信协议,它是互联网的基础协议之一,TCP/IP协议族定义了一系列的协议,包括IPTCP、UDP、FTP、HTTP等等。在网络编程,我们主要是使用TCP和UDP协议来进行网络通信。 TCP协议是面向连接的协议,它提供可靠的数据传输,保证数据的有序性和完整性。在TCP协议,发送方和接收方必须先建立连接,然后再进行数据传输,数据传输完成后,双方再关闭连接。在TCP协议,数据传输是一种可靠的传输方式,但是会影响网络传输效率。 UDP协议是无连接的协议,它提供不可靠的数据传输,不保证数据的有序性和完整性。在UDP协议,发送方不需要和接收方建立连接,直接进行数据传输,数据传输完成后,双方也不需要关闭连接。在UDP协议,数据传输效率高,但是可能会导致数据丢失和重复传输。 在网络编程,我们可以使用socket API来进行TCP/IP网络编程。socket API是一组网络通信接口,可以轻松地实现网络通信。我们可以通过socket API来创建套接字,建立连接,发送和接收数据等操作。 TCP/IP网络编程技术基础包括以下内容: 1. socket编程基础:socket的创建,绑定和监听等操作。 2. TCP编程:TCP的连接建立和断开,数据的发送和接收等操作。 3. UDP编程:UDP的数据发送和接收等操作。 4. 多线程编程:使用多线程实现网络并发处理。 5. selectepoll编程:使用selectepoll提高网络并发处理效率。 6. HTTP编程:使用HTTP协议进行网络通信。 7. 网络安全编程:网络安全编程的基本原理和实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值