epoll 反应堆模型 linux网络编程

相比较epoll普通模式
主要的改进有以下:
1.利用了epoll_event结构体里的data.ptr(泛型指针),加入红黑树时能携带自己的信息和相应的回调函数。在epoll_wait()函数返回就绪事件时能执行相应回调函数。

The struct epoll_event is defined as:

           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 */
           };

2.在响应客户端信息请求时,先把对应节点从红黑树上摘除,修改(data.ptr)其属性为可写事件(为了保证客户端处于可接受状态,例如tcp通信中的滑动窗口技术可能导致缓存不够)。当下一次epoll_wait()函数返回时,判断事件为可写事件,向客户端发送数据,把节点从红黑树上摘下,修改(data.ptr)其属性为可读事件,再次加入红黑树中,等待下一次epoll_wait()函数返回。

服务器端

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

#define MAX_EVENTS  1024  //监听上限数
#define BUFLEN 4096
#define SERV_PORT 6666

void recvdata(int fd,int events,void * arg);
void senddata(int fd,int events,void * arg);
void initlistensocket(int,short);
void eventset(struct myevent_s *,int,void(*)(int,int,void *),void *);
void eventadd(int ,int,struct myevent_s *);
void acceptconn(int,int,void*);    //处理新连接请求
void eventdel(int ,struct myevent_s *);

struct myevent_s{
    int fd;           //要监听的文件描述符
    int events;      //对应监听事件
    void *arg;       //泛型参数
    void(*call_back)(int fd,int events,void *arg);   //回调函数
    int status;      //是否在监听:1->在红黑树上,2->不在
    char buf[BUFLEN];
    int len;
    long last_active;   //记录每次加入红黑树的时间

};

int g_efd;       //保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1];    //自定义结构数组


int main(int argc,char *argv[])
{
    unsigned short port=SERV_PORT;
    if(argc==2)
        port=atoi(argv[1]);
    g_efd=epoll_create(MAX_EVENTS+1);   //创建红黑树
    if(g_efd<=0)
        printf("create efd in %s err %s\n",__func__,strerror(errno));//_func_表示当前函数
    initlistensocket(g_efd,port);     //初始化

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

    int checkpos=0,i;
    while(1)
    {
        /*超时验证,每次测试100个链接,不测试listenfd,当客户端60秒内没有和服务器通信,则关闭此客户端连接*/
        long now=time(NULL);
        for(i=0;i<100;i++,checkpos++)  //一次循环检测100个,使用checkpos控制检测对象
        {
            if(checkpos==MAX_EVENTS)
                checkpos=0;
            if(g_events[checkpos].status != 1)  //不在红黑树上
                continue;
            long duration = now - g_events[checkpos].last_active;//客户端不活跃时间
            if(duration>=60)
            {
                close(g_events[checkpos].fd);
                printf("[fd=%d] timeout \n",g_events[checkpos].fd);
                eventdel(g_efd,&g_events[checkpos]);     //从红黑树中移除
            }
        }

        /*监听红黑树g_efd,将满足的事件的文件描述符加至events数组中,1秒没有事件满足,返回0*/
        int nfd=epoll_wait(g_efd,events,MAX_EVENTS+1,1000);
        if(nfd<0)
        {
            printf("epoll_wait error,exit!");
            exit(1);
        }
        /*使用自定义结构体myevent_s指针,接收联合体data的void *ptr成员*/
        for(i=0;i<nfd;i++)
        {
            struct myevent_s *ev=(struct myevent_s *)events[i].data.ptr;

            if((events[i].events & EPOLLIN)&&(ev->events & EPOLLIN)) //读就绪事件
                ev->call_back(ev->fd,events[i].events,ev->arg);
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))//写就绪事件
                ev->call_back(ev->fd,events[i].events,ev->arg);
        }

    }
    return 0;

}

/*结构体成员初始化*/
void eventset(struct myevent_s *ev,int fd,void(*call_back)(int,int,void*),void *arg)
{
    struct myevent_s *evp=(struct myevent_s *)arg;
    ev->fd=fd;
    ev->call_back=call_back;
    ev->events=0;
    ev->arg=arg;
    ev->status=0;
    ev->len=evp->len;
    if(ev->len==0)
        memset(ev->buf,0,sizeof(ev->buf));
    ev->last_active=time(NULL);    //调用eventset的时间
    return;
}
/*删除操作*/

void eventdel(int efd,struct myevent_s *ev)
{
    struct epoll_event epv={0,{0}};

    if(ev->status !=1)
        return;
    epv.data.ptr=ev;
    ev->status=0;
    epoll_ctl(efd,EPOLL_CTL_DEL,ev->fd,&epv);
    printf("event delet ok [fd=%d]\n",ev->fd);
    return;
}


/* 添加操作*/
 void eventadd(int efd, int events,struct myevent_s *ev)
{
    struct epoll_event epv={0,{0}};
    int op;
    epv.data.ptr=ev;
    epv.events=ev->events = events;   //EPOLLIN EPOLLOUT

    if(ev->status == 1)
        op=EPOLL_CTL_MOD;   //已经在树上就修改
    else
    {
        op=EPOLL_CTL_ADD;  //不在就添加
        ev->status=1;
    }
    if(epoll_ctl(efd,op,ev->fd,&epv)<0)
        printf("event add failed [fd=%d],events[%d]\n",ev->fd,events);
    else
        printf("event add ok [fd=%d], op=%d ,events[%0X]\n",ev->fd,op,events);
    return;

}
 /*当有文件描述符就绪时,epoll返回,调用该函数,与客户端建立连接*/
void acceptconn(int lfd,int events,void *arg)
{
    struct sockaddr_in clie_addr;
    socklen_t clie_len=sizeof(clie_addr);
    int cfd,i;

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

    do{
        for(i=0;i<MAX_EVENTS;i++)
            if(g_events[i].status==0)  //从全局变量数组中找一个空位置加入
                break;
        if(i==MAX_EVENTS)
        {
            printf("%s,max connect limit[%d]\n",__func__,MAX_EVENTS);
            break;
        }
        int flag=0;
        flag=fcntl(cfd,F_SETFL,O_NONBLOCK);   //设置非阻塞读方式
        if(flag<0)
        {
            printf("%s:fcntl nonblocking failed, %s\n",__func__,strerror(errno));
            break;
        }
        eventset(&g_events[i],cfd,recvdata,&g_events[i]);  //设置属性
        eventadd(g_efd,EPOLLIN,&g_events[i]);          //将cfd添加到红黑树中,监听事件
    }while(0);
}

/*客户端回调处理信息函数 */
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); //读,同read

    eventdel(g_efd,ev);   //从红黑树上摘除

    if(len>0)
    {
        ev->len=len;
        ev->buf[len]='\0';    //手动添加字符串结束标记
        printf("C[%d] : %s\n",fd,ev->buf);

        eventset(ev,fd,senddata,ev);  //设置该fd回调函数为senddata
        eventadd(g_efd,EPOLLOUT,ev);  //重新加入红黑树,监听可写事件
    }
    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));
    }

    return;

}

/* 客户端可写的话,发送数据*/
void senddata(int fd,int events,void *arg)
{
    struct myevent_s *ev=(struct myevent_s *)arg;
    int len;
    printf("fd=%d \t ev->buf=%s \t ev->len=%d\n",fd,ev->buf,ev->len);
    
    len=send(fd,ev->buf,ev->len,0);  //发送,同write

    if(len>0)
    {
        printf("send[fd=%d], [len=%d] %s\n",fd,len,ev->buf);
        eventdel(g_efd,ev);          //从红黑树中摘除
        eventset(ev,fd,recvdata,ev); //修改回调函数
        eventadd(g_efd,EPOLLIN,ev);  //重新加入红黑树,监听读事件
        printf("-------------------------------------------\n");
    }
    else
    {
        close(ev->fd);
        eventdel(g_efd,ev);
        printf("send[fd=%d] error %s\n",fd,strerror(errno));
    }

    return;
}
void initlistensocket(int efd,short port)
{
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    fcntl(lfd,F_SETFL,O_NONBLOCK);   //设置非阻塞读方式

    /* void eventset(struct myevent_s *ev,int fd,void (*call_back)(int,int,void*),void *arg);*/
    eventset(&g_events[MAX_EVENTS],lfd,acceptconn,&g_events[MAX_EVENTS]);
    //服务器监听新客户端的文件描述符放在数组最后位置
    //回调函数传入的参数也是自己本身

    /* void eventadd(int efd, int events,struct myevent_s *ev)*/
    eventadd(efd,EPOLLIN,&g_events[MAX_EVENTS]);
   
    int opt=1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family =AF_INET;
    serv_addr.sin_addr.s_addr=INADDR_ANY;
    serv_addr.sin_port=htons(port);

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

    listen(lfd,128);
    return;

}

客户端

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

#define SERV_IP "127.0.0.1"
#define SERV_PORT  6666
int main()
{
    int cfd,sfd;
    struct sockaddr_in serv_addr;
    socklen_t serv_addr_len;
    char buf[BUFSIZ],clien_ip[BUFSIZ];
    int n;
    char *find;

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERV_PORT);//转换为网络字节序
    //serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY可以自行寻找ip
    inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);

    cfd=socket(AF_INET,SOCK_STREAM,0);//创建本地套接字

    // bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//绑定,客户端隐式绑定

    connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr) );

    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);//从输入端读一行数据存入缓存区中
        find=strchr(buf,'\n');
        if(find)
            *find='\0';//去掉fgets中的换行符
        write(cfd,buf,strlen(buf));
        n=read(cfd,buf,sizeof(buf));
        write(STDOUT_FILENO,buf,n);
        printf("\n");
    }   
    close(cfd);
    return 0;
}

测试结果

客户端
在这里插入图片描述服务器端
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值