epoll反应堆模型简单实现C/S模型----多路IO转接之高并发

本文介绍了如何使用C++实现epoll反应堆模型,通过对比普通epoll模型,详细阐述了反应堆模型的创建流程,强调了反应堆不仅要监听连接套接字的读事件,还需监听写事件。当服务器处理数据后,根据网络状况监听写事件,确保在适宜的时机进行数据回写,并循环监听读事件。文中包含错误封装函数的实现代码。
摘要由CSDN通过智能技术生成

epoll 反应堆模型:

epoll ET模式 + 非阻塞、轮询 + void *ptr。

普通epoll模型创建流程

原来: socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足 – read() — 小->大 – write回去 }

epoll反应堆模型创建流程

socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足
– read() — 小->大 – cfd从监听红黑树上摘下 – EPOLLOUT – 回调函数 – epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听写事件
– 等待 epoll_wait 返回 – 说明 cfd 可写 – write回去 – cfd从监听红黑树上摘下 – EPOLLIN
– epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听读事件 – epoll_wait 监听 }

总结: 也就是说反应堆做的事是**不但要监听 cfd 的读事件、还要监听cfd的写事件。
**

反应堆的理解:加入IO转接之后,有了事件,server才去处理,这里反应堆也是这样,由于网络环境复杂,服务器处理数据之后,可能并不能直接写回去,比如遇到网络繁忙或者对方缓冲区已经满了这种情况,就不能直接写回给客户端。反应堆就是在处理数据之后,监听写事件,能写会客户端了,才去做写回操作。写回之后,再改为监听读事件。如此循环。

实现代码:

#include "wrap.h"                                                                                                                                                                                                                                                                                                                           
#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <time.h>
#include <fcntl.h>

#define MAX_EVENTS 1024
#define SERV_PORT 9527
#define BUFLEN 1024

void revcdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);

//定义 my_events结构体
struct myevent_s
{
    int fd;             //要监听的文件描述符
    int events;         //对应的监听事件
    void *arg;          //泛型参数
    void (*call_back)(int fd, int events, void *arg);   //回调函数
    int status;         //是否在监听,1->在红黑树上监听, 0->不在
    char buf[BUFLEN];   //处理缓冲区的数组
    int len;            //从缓冲区读到的长度
    long last_active;   //记录每次加入红黑树 g_efd的时间, 
};

//定义epoll_create返回的文件描述符
int g_efd = 0;
struct myevent_s g_events[MAX_EVENTS+1];        //自定义结构体类型数组,最后一个指向listenfd

//将结构体 myevent_s 成员变量初始化
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
    ev->fd = fd; 
    ev->call_back = call_back;
    ev->events = 0;
    ev->arg = arg;
    ev->status = 0;
    //memset(ev->buf, 0, sizeof(ev->buf));
    //ev->len = 0;
    ev->last_active = time(NULL);         //调用eventset函数的时间
}

//向epoll监听的红黑树 g_efd中添加一个文件描述符
void eventadd(struct myevent_s *ev, int efd, int events)
{
    struct epoll_event tempEp = {0, {0}};

    int op; 
    tempEp.data.ptr = ev;                   //有事件加入时, ptr就=ev 可以事件调用的函数
    tempEp.events = ev->events = events;

    //如果不在红黑树中
	if (ev->status == 1) {
		op = EPOLL_CTL_MOD;
	}
	else {
		op = EPOLL_CTL_ADD;
		ev->status = 1;
	}
    if(epoll_ctl(efd, op, ev->fd, &tempEp) < 0)
        perr_exit("epoll_ctl faild\n");
    else
        printf("event add ok [fd=%d], op=%d, event[%0X]\n", ev->fd, op, events);
    
}
//从红黑树中删除节点
void eventdel(int efd, struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};

    if(ev->status == 0)
        return ;
    
    epv.data.ptr = ev;
    ev->status = 0;
    epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
    printf("del fd:%d  things:%d\n", ev->fd, ev->events);
}
//读事件 调用函数
void recvdata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;

    len = recv(fd, ev->buf, sizeof(ev->buf), 0);    //最后参数为0 可以把函数recv看作 read
    eventdel(g_efd, ev);        //将该节点从红黑树中删除

    if(len > 0)
    {
        ev->len = len;
        ev->buf[len] = '\0';
        printf("C[%d]:  len:%d   %s\n", fd, len, ev->buf);
        eventset(ev, fd, senddata, ev);  //将回调函数改为 senddata
        eventadd(ev, g_efd, EPOLLOUT);  //将 fd加入红黑树中,监听写事件
    }
    else if (len == 0) {
        close(ev->fd);
        /* ev-g_events 地址相减得到偏移元素位置 */
        printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
    } else {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
    }
}
//写事件 
void senddata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;
    for(int i = 0; i < ev->len; i++)
    {
        ev->buf[i] = toupper(ev->buf[i]);
    }
    len = send(fd, ev->buf, ev->len, 0);                    //直接将数据 回写给客户端。未作处理

    eventdel(g_efd, ev);                                //从红黑树g_efd中移除

    if (len > 0) {

        printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
        //将该fd的 回调函数改为 recvdata
        eventset(ev, fd, recvdata, ev);
        eventadd(ev, g_efd, EPOLLIN);                       //从新添加到红黑树上, 设为监听读事件

    } else {
        close(ev->fd);                                      //关闭链接
        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }

    return ;
}

//listenfd 的 accept函数
void acceptConn(int lfd, int events, void *arg)
{
    struct sockaddr_in clie_addr;
    bzero(&clie_addr, sizeof(clie_addr));
    socklen_t clie_len = sizeof(clie_addr);
    int cfd,i;

    if((cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_len)) == -1)
    {
        printf("%s: accept, %s\n", __func__, strerror(errno));
        return ;
    }

    do                                                                                                                                                                                                                                                                                                                                      
    {
        for(i = 0; i < MAX_EVENTS; i++)
            //从全局数组g_events中找到一个空闲元素
            if(g_events[i].status == 0)
                break;

        if(i == MAX_EVENTS)
        {
            printf("%s: max connnect limit[%d]\n", __func__, MAX_EVENTS);
            break;
        }

        //给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata
        eventset(&g_events[i], cfd, recvdata, &g_events[i]);
        eventadd(&g_events[i], g_efd, EPOLLIN);

    }while(0);
    printf("new connect [%s:%d][time:%ld], pos[%d]\n",
            inet_ntoa(clie_addr.sin_addr), ntohs(clie_addr.sin_port), g_events[i].last_active, i);
}
//创建socket, 初始化 listenfd
void initListenSocket(int efd, short port)
{
    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置非阻塞
    int flag = fcntl(lfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(lfd, F_SETFL, flag);

    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));

    Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    Listen(lfd, 128);

    //初始化my_events结构体
    eventset(&g_events[MAX_EVENTS], lfd, acceptConn, &g_events[MAX_EVENTS]);
    eventadd(&g_events[MAX_EVENTS], efd, EPOLLIN | EPOLLET);

}

int main()
{
    unsigned short port = SERV_PORT;

    g_efd = epoll_create(MAX_EVENTS + 1);   //创建红黑树,返回 根fd
    if(0 > g_efd)
        perr_exit("epoll_create falied..\n");

    initListenSocket(g_efd, port);          //初始化监听socket

    struct epoll_event events[MAX_EVENTS+1];    //保存已经满足就绪事件的文件描述符数组
    printf("server running:port[%d]\n", port);
    int checkpos = 0;

    while(1)
    {
        //超时验证,每次测试100个连接,不测listenfd 当客户端超过120s没有和服务器通信,则关闭该连接
        long now = time(NULL);                          //当前时间
        for (int i = 0; i < 100; i++, checkpos++) {         //一次循环检测100个。 使用checkpos控制检测对象
            if (checkpos == MAX_EVENTS)
                checkpos = 0;
            if (g_events[checkpos].status != 1)         //不在红黑树 g_efd 上
                continue;

            long duration = now - g_events[checkpos].last_active;       //客户端不活跃的世间

            if (duration >= 120) {
                close(g_events[checkpos].fd);                           //关闭与该客户端链接
                printf("[fd=%d] timeout\n", g_events[checkpos].fd);
                eventdel(g_efd, &g_events[checkpos]);                   //将该客户端 从红黑树 g_efd移除
            }
        }
        //监听红黑树 将满足事件的文件描述符加至 events数组中, 1秒没有事件满足,返回
        int eventNums = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
        if (eventNums < 0) {
            printf("epoll_wait error, exit\n");
            break;
        }

        for(int j = 0; j < eventNums; j++)
        {
            struct myevent_s *ev = (struct myevent_s*)events[j].data.ptr;

            if((events[j].events & EPOLLIN) && (ev->events & EPOLLIN))
            {
                ev->call_back(ev->fd, events[j].events, ev->arg);
            }
            else
            {
                //printf("读事件失败\n");
            }

            if ((events[j].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {         //写就绪事件
                ev->call_back(ev->fd, events[j].events, ev->arg);
            }
            else
            {
                //printf("写事件失败\n");
            }



        }
    }

    return 0;
}
                                                                                                                                                                                                                                                                                                                          

错误封装函数wrap.h

#ifndef __WRAP_H__                                                                                                                                                                                                                                                                                                                          
#define __WRAP_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n); 
ssize_t Writen(int fd, const void *vptr, size_t n); 
ssize_t Readline(int fd, void *vptr, size_t maxlen);

#endif

wrap.c

#include "wrap.h"                                                                                                                                                                                                                                                                                                                           

static ssize_t my_read(int fd, char *ptr);

void perr_exit(const char *s)
{
    perror(s);
    exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;

again:
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    n = connect(fd, sa, salen);
    if (n < 0) {
        perr_exit("connect error");
    }

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
    int n;

    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ( (n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }

    return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;

again:                                                                                                                                                                                                                                                                                                                                      
    if ((n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/                          //socket 4096  readn(cfd, buf, 4096)   nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char   *ptr;

    ptr = vptr;
    nleft = n;                  //n 未读取字节数

    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        } else if (nread == 0)
            break;

        nleft -= nread;   //nleft = nleft - nread 
        ptr += nread;
    }
    return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
again:
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {   //"hello\n"
            if (errno == EINTR)
                goto again;
            return -1;
        } else if (read_cnt == 0)
            return 0;

        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;

    return 1;
}

/*readline --- fgets*/
//传出参数 vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, *ptr;
    ptr = vptr;

    for (n = 1; n < maxlen; n++) {
        if ((rc = my_read(fd, &c)) == 1) {   //ptr[] = hello\n
            *ptr++ = c;
            if (c == '\n')
                break;
        } else if (rc == 0) {
            *ptr = 0;
            return n-1;
        } else
            return -1;
    }
    *ptr = 0;

    return n;
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coison_z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值