网络编程之Epoll - C

一、前言

        Epoll 是 Linux I/O 多路复用的管理机制,作为现在Linux 平台高性能网络IO必要的组件,是linux成为主流服务器系统的关键。

        I/O多路复用(I/O multiplexing)是一种处理多个I/O事件的机制,允许单个线程同时监听多个I/O通道的数据流。常用的I/O多路复用技术包括select、poll和epoll。它们都允许一个线程同时监视多个文件描述符,当其中任何一个文件描述符的状态发生变化时,线程就会被唤醒,从而实现并发处理多个I/O事件的能力。

二、接口函数

struct epoll_event 结构体

        对于struct event 结构体,一般使用两个成员:

        events:用户设置需要检测的事件,以及内核需要返回的事件也保存在这里

        data.fd:i/o对应的描述符fd

typedef union epoll_data {
    void* ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t events;      /* Epoll events */
    epoll_data_t data;    /* User data variable */
};

epoll_create 函数

        调用epoll_create创建一个epoll对象实例,并返回指向对象实例的文件描述符epfd。

#include <sys/epoll.h>
int epoll_create(int size);

参数size: 无效参数,在linux2.6.8之前,就绪i/o队列使用的是数组,所以参数size指的是能够同时就绪的i/o数量是多少;在linux2.6.8之后,就绪队列使用的是链表,没有了i/o数量的限制,所以参数size就失去了作用,只要传入大于0的数就行。

epoll_ctl 函数

        epoll_ctl用来对文件描述符进行添加、修改和删除操作。

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • int epfd:指向epoll对象实例的文件描述符epfd
  • int op:操作的选项有三种,

        EPOLL_ADD:添加fd以及需要检测的事件状态。

        EPOLL_DEL:删除fd。

        EPOLL_MOD:修改fd对应的要检测的事件状态,一般recv之后改事件为EPOLLOUT检测读事件,send之后改事件为EPOLLIN检测写事件。

  • int fd:i/o描述符。
  • struct epoll_event *event:携带事件信息,del的时候可设置为NULL。

epoll_wait 函数

        调用epoll_wait获取所有就绪的i/o事件以及就绪的I/o数量。

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
                                                 //返回:所有就绪的i/o数量
  • int epfd:指向epoll对象实例的文件描述符epfd
  • struct epoll_event *events:用来存放i/o事件信息的结构体数组
  • int maxevents:events 结构体数组的对象数量,即能存放的事件数
  • int timeout:指示内核等待的时间,-1为永久等待,立即返回,大于0则等待指示的时间数

三、特点分析

epoll特点       

        epoll 在内核中分有 fd总集 和 fd就绪队列,每次调用epoll_wait 获取 fd就绪队列的fd状态信息即可,不需要进行全部 fd状态检测,也不用将 fd集合从用户空间拷贝到内核空间。

对比 select、poll

四、服务器代码

        使用代码请自行设置ip地址和端口,这是一个回射服务器,客户端发什么就回什么。

使用epoll的一般过程

  • 调用epoll_create创建epoll文件表,返回指向文件表的epfd
epfd = epoll_create(1);
  • 创建struct epoll_events ev 设置读事件,将listenfd加入epoll文件表中
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
  • 创建struct epoll_events events[] 用来接收就绪i/o事件
struct epoll_event events[1024] = {0};
  • 通过循环的方式调用epoll_wait获取就绪i/o事件
while(1) {

        struct epoll_event events[1024] = {0};
        int nready = epoll_wait(epfd, events, 1024, -1);
}
  • 通过EPOLLIN 和EPOLLOUT对读、写事件进行判断
if (events[i].events & EPOLLIN) {}
if (events[i].events & EPOLLOUT) {}

完整代码

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>

#define SER_PORT    3000

#define BUFFER_SIZE     1024

int epfd = 0;

//接收和发送数据信息
struct conn {
    int fd;

    char rbuffer[BUFFER_SIZE];
    int rlength;

    char wbuffer[BUFFER_SIZE];
    int wlength;
};

struct conn conn_list[1024] ={0};

void set_event(int fd, int events)
{
    struct epoll_event ev;

    ev.events = events;
    ev.data.fd = fd;

    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}

int init_server(int port)
{

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in seraddr;
    memset(seraddr, 0, sizeof(struct sockaddr_in));
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(port);

    if (-1 == bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr))) {
        printf("bind failed\n");
    }

    return sockfd;
}


int main()
{
    int sockfd = init_server(SER_PORT);

    listen(sockfd,10);

    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(struct sockaddr_in);

    epfd = epoll_create(1);

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    while(1) {

        struct epoll_event events[1024] = {0};
        int nready = epoll_wait(epfd, events, 1024, -1);

        int i = 0;
        for (i=0; i<nready; i++) {

            int confd = events[i].data.fd;

            if (sockfd == confd) {
                int clientfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);

                ev.events = EPOLLIN;
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

                conn_list[clientfd].fd = clientfd;

            } else {
                int count = 0;

                if (events[i].events & EPOLLIN) {
                    count = recv(confd, conn_list[confd].rbuffer, BUFFER_SIZE, 0);
                    if (count == 0) {
                        printf("client disconnect:%d\n", confd);

                        close(confd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, confd, NULL);

                        continue;
                    }
                    
                    conn_list[confd].rlength = count;
                    conn_list[confd].wlength = count;
                    memcpy(conn_list[confd].wbuffer, conn_list[confd].rbuffer, count);
                    printf("recv:%s fd:%d count:%d\n", conn_list[confd].rbuffer, confd, count);

                    //将事件置为检测读事件
                    set_event(confd, EPOLLOUT);
                }

                if (events[i].events & EPOLLOUT) {
                    printf("send wbuffer:%s\n", conn_list[confd].wbuffer);
                    int ret = send(confd, conn_list[confd].wbuffer, conn_list[confd].wlength, 0);
                    printf("send:%d  fd:%d\n", ret, confd);

                    //将事件置为检测写事件
                    set_event(confd, EPOLLIN);
                }
            }
        }
    }


    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值