网络编程(21)—— 使用epoll进行IO复用

一 引言

         之前在《 网络编程(16)—— IO复用技术之select》介绍了用于IO复用的select函数,其基本原理就是将被监视的读、写、或者异常的所有文件操作符分别放置到一个位数组fd_set类型的变量中,然后调用select对上述变量进行检查并进入阻塞状态,若集合中某个文件操作符存在待读数据、待传输数据、或者异常时,select就会结束阻塞状态,该发生状态变化的文件操作符在集合中被保留,我们可以通过宏来检查某个文件操作符是否发生了状态的改变。

        利用select进行IO复用,适合比较少连接或者有跨平台需求的服务器的开发,因为有两处制约其在处理多连接时的性能:
        1、每次都需要将包含文件操作符的集合向操作系统进行传递(文件操作符是操作系统级别的资源),由操作系统对集合中的文件操作符进行监视。

        2、需要遍历每个文件操作符(有时候甚至可能从0开始遍历标准输出和标准输入的文件操作符等等)。

二 epoll的使用步骤

        基于上述select本身的不足之处,可使用epoll函数代替select进行IO复用服务器的开发。 epoll的使用过程如下:

2.1 创建epoll文件描述符

        利用epoll_create()函数在操作系统中创建一个epoll文件描述符,该描述符指向一片内存空间,用来存储需要进行监视的文件操作符。如下,为epoll_create()函数的函数原型,其接受一个int类型的参数,用来指定开辟的空间的大小,函数返回epoll的文件描述符。
      #include<sys/epoll.h>
      int epoll_create(int size);

2.2 在epoll中注册文件描述符

         利用epoll_ctl()函数在上述开辟的空间中注册需要监视的文件操作符,其函数原型如下,
      int epoll_ctl(int epfd,int op,int fd,struct epoll_event &event);
        epfd — epoll例程的文件描述符,就是用epoll_create()返回的文件描述符。

        op — 注册选项,用来控制文件描述符的注册或者删除,EPOLL_CTL_ADD,表示注册一个文件描述符;EPOLL_CTL_DEL,表示删除一个文件描述符;

        fd —- 需要进行注册的文件描述符,socket或者标准io的文件描述符等等

        event — 需要监视的事件,是需要读取、还是写入等等。该参数的类型是一个epoll_event类型的结构体,它的定义形式如下:

struct epoll_event
{
     __unit32_t events;
     epoll_data_t data;
}
events是一个整数类型的数值,用来设置要监视的事件的类型,如设置成EPOLLIN是要监视有数据待读取的事件,设置成EPOLLOUT是要监视有数据待发送的事件。而我们在data的fd成员中注册我们的socket文件描述符。

epoll_ctl的使用过程可以用以下代码来概括:

struct epoll_event event;
event.events=EPOLLOUT;
event.data.fd=serv_sock;
epoll_ctl(epfd,EPOLL_CTL_ADD,serv_sock,&event);

2.3 利用epoll_wait进行文件描述符发生信号的等待

        epoll_wait()函数的功能和select类似,用来定位发生状态变化的文件描述符的原型如下:

int epoll_wait(int epfd,struct epoll_event *events,
                    int maxevents,int timeout);

       epfd — epoll文件描述符
       events — 表示被激活的事件,该结构体里面包含发生事件对应的文件描述符
       maxevents — 表示第二个参数中可以保存的最大的事件数
       timeout — 设置等待超时,单位是毫秒,设置成-1时,epoll_wait()会一直阻塞,直到有事件发生。

       函数返回发生事件的文件操作符的个数。

       以下代码是利用epoll实现的一个实现io复用的socket服务端,稍后对关键处进行解析。

示例代码:

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


void error_handling(char* message);


int main(int argc,char* argv[])
{
    int serv_sock,clnt_sock;
    struct sockaddr_in serv_addr,clnt_addr;
    int clnt_addr_sz;
    int epoll_fd;
    struct epoll_event event;
    struct epoll_event* pevents;
    int fd_num,i;
    int str_len;
    char buf[BUF_SIZE];
    if(argc!=2)
    {
        printf("Uasge %s <port>\n",argv[0]);
        exit(1);
    }


    //创建socket
    serv_sock=socket(AF_INET,SOCK_STREAM,0);
    //准备地址
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));
    //绑定
    if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
    {
        error_handling("bind error");
    }
    //监听
    if(listen(serv_sock,5)==-1)
    {
        error_handling("listen error");
    }
    //创建epoll描述符,以及用来接收发生变化事件的数组,空间占用50个字节
    epoll_fd=epoll_create(50);
    pevents=malloc(sizeof(struct epoll_event)*50);
    //利用epoll_ctl注册socket描述符
    event.events=EPOLLIN;
    event.data.fd=serv_sock;
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,serv_sock,&event);
    while(1)
    {
        //等待socket状态的变化,返回发生状态变化的文件描述符数
        fd_num=epoll_wait(epoll_fd,pevents,50,-1);
        puts("wait succeed");
        for(i=0;i<fd_num;i++)
        {
            if(serv_sock==pevents[i].data.fd)
            {
                clnt_addr_sz=sizeof(clnt_addr);
                clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);
                puts("accept succeed");
                //把客户端添加到监视事件中
                event.events=EPOLLIN;
                event.data.fd=clnt_sock;
                epoll_ctl(epoll_fd,EPOLL_CTL_ADD,clnt_sock,&event);
            }
            else
            {
                str_len=read(pevents[i].data.fd,buf,BUF_SIZE);
                if(str_len==0)
                {
                    epoll_ctl(epoll_fd,EPOLL_CTL_DEL,pevents[i].data.fd,NULL);
                    close(pevents[i].data.fd);
                }
                else
                {
                    printf("client:%s\n",buf);
                    write(pevents[i].data.fd,buf,str_len);
                }
            }
        }




    }
    close(epoll_fd);
    close(serv_sock);
    return 0;
}


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

第18、19、20行分别定义了epoll的操作符epoll_fd、以及用于监视socket描述符的事件event、用于存放被触发的事件的数组pevents。

第48行,我们创建了epoll描述符。

第49行,为事件数组malloc一段内存。

第51、52行,在事件中注册socket描述符,以及定义事件类型。

第53行,领用epoll_ctl实现epoll操作符和socket的关联

第57行,利用epoll_wait等待socket文件描述符发生状态的变化,该函数返回发生状态变化的socket文件描述符的个数,同时会将变化的event事件存放到

数组pevent中,这样我们就能在59行遍历这里数组,来处理对应的文件描述符。

第61行是一个if分支,主要判断文件描述符是服务器的socket还是客户端的socket。很显然,是服务端的socket的话,说明有新的客户端连接,我们就要

在62到70这段代码中接收连接并将客户端socket的监视放到epoll文件描述符中。

第72行开始是对客户端socket的一个处理,读取客户端发送的数据并进行返回,另外在客户端断开连接时,利用第76行的epoll_ctl从epool的文件描述符中

删除对该客户端socket的监控。

最后,第89和90行,分别关闭epoll的文件描述符和客户端的socket。

        epoll的整个使用过程就,先介绍到这,如想要验证该功能,可以随便从之前的专栏文章中挑选一个回声客户端,也可以从Giyhub上下载本文的源代码。

里面包含一个回声客户端和服务端。



Github位置:

https://github.com/HymanLiuTS/NetDevelopment

克隆本项目:

git clone git@github.com:HymanLiuTS/NetDevelopment.git

获取本文源代码:

git checkout NL21


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值