epoll的面向io和面向事件编程
epoll
epoll是Linux操作系统提供的一种高性能I/O多路复用机制,epoll既可以面向io编程也可以面向事件编程(Reactor模式)
Reactor模式
与面向io编程(判断文件描述符)模式不同,Reactor模式是一种以面向事件思维处理并发I/O操作的设计模式,根据EPOLLIN、EPOLLOUT事件(判断是否可读、可写)对应处理I/O,实现高并发高性能的网络编程。
epoll代码实现
设置监听字sockfd
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
return -1;
}
listen(sockfd, 10);
在主函数中,设置监听套接字,绑定端口和ip地址,开始监听
面向io编程
int epfd=epoll_create(1);
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=sockfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
struct epoll_event events[1024]={0};
创建epoll实例epfd,事件结构体ev以及设置并初始化ev中的结构体事件集合,设置监听sockfd并加入到epfd中
while(1){
int nready= epoll_wait(epfd,events,1024,-1);
int i=0;
for(i=0;i<nready;i++){
int connfd=events[i].data.fd;
if(sockfd==connfd){
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clientfd=accept(sockfd,(struct sockaddr*)&serveraddr,&len);
ev.events=EPOLLIN |EPOLLET;
ev.data.fd=clientfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
printf("clientfd:%d\n",clientfd);
}else if(events[i].events & EPOLLIN){
char buffer[10]={0};
int count=recv(connfd,buffer,10,0);
if(count==0){
printf("disconnect\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
close(i);
continue;
}
send(connfd,buffer,count,0);
printf("clientfd: %d,count:%d,buffer:%s\n",connfd,count,buffer);
开始循环,等待出现就绪事件,遍历就绪事件,将i的文件描述符设为connfd,将请求的客户端文件描述符clientfd中的事件状态设为可读,并将clientfd加入到epfd中,如果显示这是这是一个就绪事件并且是可读的,则接收数据并在epfd中将connfd删除,最后输出数据。
面向事件编程(Reactor模式)
#define BUFFER_LENGTH 1024
int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);
typedef int(*Rcallback)(int fd);
定义一个宏表示buffer长度,声明函数,定义回调函数类型
struct conn_item{
int fd;
char rbuffer[BUFFER_LENGTH];
int rlen;
char wbuffer[BUFFER_LENGTH];
int wlen;
union{
Rcallback accept_callback;
Rcallback recv_callback;}recv_t;
Rcallback send_callback;
};
定义链接结构体,读入rbuffer,写入wbuffer,以及三个回调函数用于调用上面三个函数
int epfd =0;
struct conn_item connlist[1024]={0};
int set_event(int fd,int event,int flag){
if(flag){
struct epoll_event ev;
ev.events=event ;
ev.data.fd=fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
}else
{struct epoll_event ev;
ev.events=event ;
ev.data.fd=fd;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);}
}
设置epoll实例,初始化连接结构体,设置状态改变函数
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len =sizeof(clientaddr);
int clientfd=accept(fd,(struct sockaddr*)&clientaddr,&len);
if(clientfd<0){return -1;}
set_event(clientfd,EPOLLIN,1);
connlist[clientfd].fd=clientfd;
memset(connlist[clientfd].rbuffer,0,BUFFER_LENGTH);
connlist[clientfd].rlen=0;
memset(connlist[clientfd].wbuffer,0,BUFFER_LENGTH);
connlist[clientfd].wlen=0;
connlist[clientfd].recv_t.recv_callback=recv_cb;
connlist[clientfd].send_callback=send_cb;
return clientfd;
}
监听函数,功能是创造clientfd,将其设置为可读状态并接入到epoll实例中,初始化可读可写buffer,设置回调函数
int recv_cb(int fd){
char* buffer=connlist[fd].rbuffer;
int idx =connlist[fd].rlen;
int count=recv(fd,buffer+idx,BUFFER_LENGTH-idx,0);
if(count==0){
printf("disconnect\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
return -1;
}
connlist[fd].rlen += count;
memcpy(connlist[fd].wbuffer,connlist[fd].rbuffer,connlist[fd].rlen);
connlist[fd].wlen=connlist[fd].rlen;
set_event(fd,EPOLLOUT,0);
return count;
}
接收函数:接收数据,将当前fd从epfd中去除,将rbuffer数据复制添加到wbuffer的后面(回声收发),设置fd状态为可写。
int send_cb(int fd){
char* buffer=connlist[fd].wbuffer;
int idx =connlist[fd].wlen;
int count=send(fd,buffer,idx,0);
set_event(fd,EPOLLIN,0);
return count;
}
发送函数:将wbuffer中的数据发出,并将fd设置为可读并将其从epfd中去掉
{
connlist[sockfd].fd=sockfd;
connlist[sockfd].recv_t.accept_callback=accept_cb;
epfd=epoll_create(1);
set_event(sockfd,EPOLLIN,1);
struct epoll_event events[1024]={0};
创建epoll实例epfd,事件结构体ev以及设置并初始化ev中的结构体事件集合,设置监听sockfd并加入到epfd中
while(1){
int nready= epoll_wait(epfd,events,1024,-1);
int i=0;
for(i=0;i<nready;i++){
int connfd=events[i].data.fd;
if(events[i].events & EPOLLIN){
int count=connlist[connfd].recv_t.recv_callback(connfd);
printf("recv count:%d<--buffer:%s\n",count,connlist[connfd].rbuffer);
}else if(events[i].events &EPOLLOUT){
int count=connlist[connfd].send_callback(connfd);
printf("send-->buffer:%s\n",connlist[connfd].wbuffer);
}
}
}
开始循环,等待出现就绪事件,遍历就绪事件,如果是可读的,就接收数据,若是可写的,就写入数据,最后输出数据。