16-优于select的epoll

参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨

测试环境:Ubuntu 10.10

GCC版本:4.4.5

 

一、回顾基于select的I/O复用技术

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

* 调用select函数后针对所有文件描述符的循环语句

* 每次调用select函数时都需要向该函数传递监视对象信息(更耗时:调用select函数时向操作系统传递监视对象信息)

2.select的优点——兼容性好(大部分操作系统都支持select函数)

3.select适用条件——服务器端接入者少程序应具有兼容性

 

二、基于epoll的I/O复用技术

1.epoll函数具有如下优点:

* 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句

* 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息

 

2.epoll相关函数及结构体

* epoll_create函数(由操作系统管理)——创建保存epoll文件描述符的空间

头文件:#include <sys/epoll.h>
函数功能:创建保存epoll文件描述符的空间
返回值:成功时返回epoll文件描述符,失败时返回-1
函数原型:int epoll_create(int size);
参数:
size——Linux2.6.8之后的内核将完全忽略传入epoll_create函数的size参数,因为内核会根据情况调整epoll例程的大小

* epoll_event、epoll_data结构体

struct epoll_event
{
    __unit32_t events;    //epoll事件
    epoll_data_t data;    //用户数据
};

typedef union epoll_data
{        
    void* ptr;         //指定与fd相关的用户数据
    int fd;            //指定事件所从属的目标文件描述符
    __uint32_t u32;
    __uint64_t u64;
}epoll_data_t;

注意:fd和ptr不能同时使用(联合体),建议在ptr指定的数据中包含fd

* epoll_ctl函数——注册监视对象文件描述符

头文件:#include <sys/epoll.h>
函数功能:注册监视对象文件描述符
返回值:成功时返回0,失败时返回-1
函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
参数:
epfd——用于注册监视对象的epoll例称的文件描述符
op——用于指定监视对象的添加、删除或更改等操作
fd——需要注册的监视对象文件描述符
event——监视对象的事件类型

op指定的操作类型有如下3种:
* EPOLL_CTL_ADD:将文件描述符注册到epoll例程
* EPOLL_CTL_DEL:从epoll例程中删除文件描述符(epoll_ctl第四个参数为NULL,但Linux2.6.9之前不行)
* EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况


epoll_event结构体使用示例:
struct epoll_event event;
……
event.events = EPOLLIN;    //发生需要读取数据的情况(事件)时
event.data.fd = sockfd;
event_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
……

epoll_event成员events中可以保存的常量及所指的事件类型:
EPOLLIN:需要读取数据的情况
EPOLLOUT:输出缓冲为空,可以立即发送数据的情况
EPOLLPRI:收到OOB数据的情况
EPOLLRDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用
EPOLLERR:发生错误的情况
EPOLLET:以边缘触发的方式得到事件通知
EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向 
              epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件。

* epoll_wait函数——等待文件描述符发生变化

头文件:#include <sys/epoll.h>
函数功能:等待文件描述符发生变化
返回值:成功时返回发生事件的文件描述符,失败时返回-1
函数原型:int epoll_wait(int epfd, struct epoll_event* event, int maxevent, int timeout);
参数:
epfd——表示事件发生监视范围的epoll例程的文件描述符
events——保存发生事件的文件描述符集合的结构体地址值(需要动态分配内存)
maxevents——第二个参数中可以保存的最大事件数
timeout——以1/1000秒为单位的等待事件,传递-1时,一直等待直到发生事件

调用方式如下:
int event_cnt;
struct epoll_event* ep_events;
……
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE);    //EPOLL_SIZE是宏常量
……
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
……

 

3.基于epoll的回声服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE   100
#define EPOLL_SIZE 50

void errorHandling(const char* buf);

int main(int argc, char* argv[])
{
    int servSock, clientSock;
    struct sockaddr_in servAddr, clientAddr;
    socklen_t clientAddrSize;
    int strLen, i;
    char buf[BUF_SIZE];

    struct epoll_event* epollEvents;
    struct epoll_event event;
    int epfd, eventCount;

    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error");

    epfd = epoll_create(EPOLL_SIZE);
    epollEvents = (struct epoll_event*)malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = servSock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, servSock, &event);

    while(1)
    {
        eventCount = epoll_wait(epfd, epollEvents, EPOLL_SIZE, -1);

        if(-1 == eventCount)
        {
            puts("epoll_wait() error");
            break;
        }

        for(i = 0; i < eventCount; i++)
        {
            if(epollEvents[i].data.fd == servSock)
            {
                clientAddrSize = sizeof(clientAddr);
                clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
                if(-1 == clientSock)
                    errorHandling("clientSock() error");

                event.events = EPOLLIN;
                event.data.fd = clientSock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientSock, &event);
                printf("connected client: %d\n", clientSock);
            }
            else
            {
                strLen = read(epollEvents[i].data.fd, buf, BUF_SIZE);
                if(0 == strLen) //close request!
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, epollEvents[i].data.fd, NULL);
                    close(epollEvents[i].data.fd);
                    printf("closed client:%d\n", epollEvents[i].data.fd);
                }
                else
                {
                    write(epollEvents[i].data.fd, buf, strLen); //echo!
                }
            }
        }
    }

    close(servSock);
    close(epfd);

    return 0;
}

void errorHandling(const char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
void errorHandling(const char* message);
 
int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    int clntDataLen = -1;
    struct sockaddr_in servAddr;
 
    if(argc != 3)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }
 
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");
 
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));
 
    if(connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("connect() error");
    else
        puts("Connected............");
 
 
    while(1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
 
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
 
        write(sock, message, strlen(message));
        clntDataLen = read(sock, message, BUF_SIZE);
        message[clntDataLen] = 0;
        printf("Message from server: %s\n", message);
    }
 
    close(sock);
 
    return 0;
}
 
void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

测试:

服务端:gcc echoEpollServer.c -o echoEpollServer.out

connected client: 5
connected client: 6
closed client:6
closed client:5

客户端1:gcc echoClient.c -o echoClient.out

Connected............
Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): hello~
Message from server: hello~

Input message(Q to quit): Q

客户端2:gcc echoClient.c -o echoClient.out

Connected............
Input message(Q to quit): abc
Message from server: abc

Input message(Q to quit): Q

 

三、条件触发和边缘触发

1. 条件触发和边缘触发区别

* 条件触发——epoll默认工作模式。当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。如果不处理,下一次调用epoll_wait时还会再次向应用程序通告此事件

* 边缘触发——当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,否则后续的epoll_wait调用将不再向应用程序通知这一事件

 

2. 证明:epoll默认触发方式是条件触发

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE   4
#define EPOLL_SIZE 50

void errorHandling(const char* buf);

int main(int argc, char* argv[])
{
    int servSock, clientSock;
    struct sockaddr_in servAddr, clientAddr;
    socklen_t clientAddrSize;
    int strLen, i;
    char buf[BUF_SIZE];

    struct epoll_event* epollEvents;
    struct epoll_event event;
    int epfd, eventCount;

    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error");

    epfd = epoll_create(EPOLL_SIZE);
    epollEvents = (struct epoll_event*)malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = servSock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, servSock, &event);

    while(1)
    {
        eventCount = epoll_wait(epfd, epollEvents, EPOLL_SIZE, -1);

        printf("epoll_wait finish!\n");

        if(-1 == eventCount)
        {
            puts("epoll_wait() error");
            break;
        }

        for(i = 0; i < eventCount; i++)
        {
            if(epollEvents[i].data.fd == servSock)
            {
                clientAddrSize = sizeof(clientAddr);
                clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
                if(-1 == clientSock)
                    errorHandling("clientSock() error");

                event.events = EPOLLIN;
                event.data.fd = clientSock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientSock, &event);
                printf("connected client: %d\n", clientSock);
            }
            else
            {
                strLen = read(epollEvents[i].data.fd, buf, BUF_SIZE);
                if(0 == strLen) //close request!
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, epollEvents[i].data.fd, NULL);
                    close(epollEvents[i].data.fd);
                    printf("closed client:%d\n", epollEvents[i].data.fd);
                }
                else
                {
                    write(epollEvents[i].data.fd, buf, strLen); //echo!
                }
            }
        }
    }

    close(servSock);
    close(epfd);

    return 0;
}

void errorHandling(const char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
void errorHandling(const char* message);
 
int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    int clntDataLen = -1;
    struct sockaddr_in servAddr;
 
    if(argc != 3)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }
 
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");
 
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));
 
    if(connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("connect() error");
    else
        puts("Connected............");
 
 
    while(1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
 
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
 
        write(sock, message, strlen(message));
        clntDataLen = read(sock, message, BUF_SIZE);
        message[clntDataLen] = 0;
        printf("Message from server: %s\n", message);
    }
 
    close(sock);
 
    return 0;
}
 
void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

测试:

服务器端:gcc server.c -o server.out,运行:./server.out 9190

epoll_wait finish!
connected client: 5
epoll_wait finish!
connected client: 6
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
epoll_wait finish!
closed client:5
epoll_wait finish!
epoll_wait finish!
closed client:6

客户端1:gcc client.c -o client.out,运行:./client.out 127.0.0.1 9190

Connected............
Input message(Q to quit): Hello Server~
Message from server: Hell
Input message(Q to quit): Q 

客户端2:运行:./client.out 127.0.0.1 9190

Connected............
Input message(Q to quit): hello~
Message from server: hello~

Input message(Q to quit): a
Message from server: a

Input message(Q to quit): Q

 

3.边缘触发

* 实现边缘触发的必知内容

a. 通过errno变量验证错误原因

b. 为了完成非阻塞I/O,更改套接字特性

头文件:#include <fcntl.h>
函数功能:改变已打开的文件描述符性质
返回值:成功时返回cmd参数相关值,失败时返回-1
函数原型:int fcntl(int filedes, int cmd,...);
参数:
filedes——属性更改目标的文件描述符
cmd——表示函数调用的目的

* 边缘触发示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

#define BUF_SIZE    4
#define EPOLL_SIZE  50

void setnonblockingmode(int fd);
void errorHangling(const char* message);

int main(int argc, char* argv[])
{
    int servSock, clientSock;
    struct sockaddr_in servAddr, clientAddr;
    socklen_t clientAddrSize;
    int strLen, i;
    char buf[BUF_SIZE];

    struct epoll_event* epollEvents;
    struct epoll_event event;
    int epfd, eventCount;

    if(2 != argc)
    {
        printf("Usage:%s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHangling("bind() error");

    if(-1 == listen(servSock, 5))
        errorHangling("listen() error");

    epfd = epoll_create(EPOLL_SIZE);
    epollEvents = (struct epoll_event*)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    setnonblockingmode(servSock);
    event.events = EPOLLIN;
    event.data.fd = servSock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, servSock, &event);

    while(1)
    {
        eventCount = epoll_wait(epfd, epollEvents, EPOLL_SIZE, -1);
        if(-1 == eventCount)
        {
            puts("epoll_wait() error");
            break;
        }

        puts("return epoll_wait");
        for(i = 0; i < eventCount; i++)
        {
            if(epollEvents[i].data.fd == servSock)
            {
                clientAddrSize = sizeof(clientSock);
                clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
                setnonblockingmode(clientSock);
                event.events = EPOLLIN | EPOLLET;
                event.data.fd = clientSock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientSock, &event);
                printf("connected client: %d\n", clientSock);
            }
            else
            {
                while(1)    //循环是为了从缓冲区读取全部数据
                {
                    strLen = read(epollEvents[i].data.fd, buf, BUF_SIZE);
                    if(0 == strLen) //close request!
                    {
                        epoll_ctl(epfd, EPOLL_CTL_DEL, epollEvents[i].data.fd, NULL);
                        close(epollEvents[i].data.fd);
                        printf("closed client: %d\n", epollEvents[i].data.fd);
                        break;
                    }
                    else if(strLen < 0)
                    {
                        if(errno == EAGAIN)
                            break;
                    }
                    else
                    {
                        write(epollEvents[i].data.fd, buf, strLen);
                    }
                }
            }
        }
    }

    close(servSock);
    close(epfd);

    return 0;
}

void setnonblockingmode(int fd)
{
    int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}

void errorHangling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端代码同上。

测试:

服务器端:gcc epollEPETserver.c -o epollEPETserver.out,运行:./epollEPETserver.out 9190

return epoll_wait
connected client: 5
return epoll_wait
return epoll_wait
return epoll_wait
return epoll_wait
closed client: 5

客户端:

Connected............
Input message(Q to quit): I like computer programming
Message from server: I like computer programming

Input message(Q to quit): Do you like computer programming?
Message from server: Do you like computer programming?

Input message(Q to quit): Good bye
Message from server: Good bye

Input message(Q to quit): Q

分析:客户端从连接到断开连接,总共发送5次数据,服务器端产生了5个事件。

 

边缘触发的优势:可以分离接收数据和处理数据的时间点!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值