相比较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;
}
测试结果
客户端
服务器端