在这篇开始之前,可以查看前一篇对poll的概念的描述,这样阅读起这篇比较不困难。首先我们要知道,epoll模型和前面poll,select是有差别的,他实现的方法不大一样,我们来看看下面的代码,为了和之前的poll,select进行区别,我们依旧采用C/S架构实现。服务器代码不同,客户端都是一样的,好的,废话不多说,先上代码,再进行分析。
服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h> /* for struct sockaddr_in*/
#include <sys/errno.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/epoll.h>
//关于IO复用服务器的epoll
#define LISTEN_SIZE 1024
void error_exit(char *name)
{
perror(name);
exit(-1);
}
int main(int argc,char *argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
error_exit("create error");
}
//绑定地址(ip和端口号)
struct sockaddr_in svraddr;
memset(&svraddr,0,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr=INADDR_ANY;
//svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");第二种写法
svraddr.sin_port=htons(5555);
int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));
if(ret<0)
{
error_exit("bind error");
}
//设置监听参数back login 半连接数最大
ret=listen(sockfd,1024);
if(ret<0)
{
error_exit("listen error");
}
//创建epoll监听集合
int epfs=epoll_create(100);
//添加svrfd
struct epoll_event event;
event.data.fd=sockfd;
event.events=EPOLLIN;
epoll_ctl(epfs,EPOLL_CTL_ADD,sockfd,&event);
struct epoll_event ret_events[20]={0};
int i=0;
struct sockaddr_in removeaddr;
memset(&removeaddr,0,sizeof(removeaddr));
int addr_len=sizeof(removeaddr);
char buf[1024]={0};
while(1)
{
int nevent =epoll_wait(epfs,ret_events,20,-1);
if(nevent<0)
{
error_exit("timeout\n");
}
else if(nevent==0)
{
printf("timeout\n");
continue;
}
for(i=0;i<nevent;i++)
{
if(ret_events[i].data.fd== sockfd)
{
int clientfd=accept(sockfd,(struct sockaddr*)&removeaddr,&addr_len);
if(clientfd<0)
{
error_exit("accept error");
}
printf("new connection fd %d\n",clientfd);
event.data.fd=clientfd;
event.events=EPOLLIN;
epoll_ctl(epfs,EPOLL_CTL_ADD,clientfd,&event);
}
else if(ret_events[i].events&EPOLLIN)
{
int rdsize=read(ret_events[i].data.fd,buf,1024);
if(rdsize<=0)
{
printf("close %d\n",ret_events[i].data.fd);
close(ret_events[i].data.fd);
event.data.fd=ret_events[i].data.fd;
event.events=EPOLLIN;
epoll_ctl(epfs,EPOLL_CTL_DEL,ret_events[i].data.fd,&event);
}
else
{
printf("read data %s\n",buf);
}
}
}
}
close(sockfd);
}
客户端依旧是前面一篇的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h> /* for struct sockaddr_in*/
#include <sys/errno.h>
#include <signal.h>
//关于客户端的socket
void error_exit(char *name)
{
perror(name);
exit(-1);
}
int main(int argc,char *argv[])
{
if(argc<3)
{
printf("run program+ip+port\n");
return-1;
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
error_exit("create error");
}
//连接服务器,设置服务器的地址(ip和端口)
struct sockaddr_in svraddr;
memset(&svraddr,0,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr= inet_addr(argv[1]);
svraddr.sin_port=htons(atoi(argv[2]));
int ret =connect(sockfd,(struct sockaddr *)&svraddr,sizeof(svraddr));
if(ret<0)
{
error_exit("connect error");
}
write(sockfd,"hello",strlen("hello"));
sleep(5);
close(sockfd);
return 0;
}
先执行服务端,再执行客户端代码:
以下是客户端结果:
接下来查看服务端结果:
epoll总结:epoll其实有两种模式,EPOLLLT和EPOLLET,其实前面我们实现poll,select都是EPOLLLT模式,这篇也是。EPOLLLT为监听模式,数据在缓冲区读完,wait还是会触发。而EPOLLET数据哪怕读一点点,再次wait就不会触发,因为ET会从不可读变为可读,也只有这一过程wait才会触发,所以它只能触发一次。
这时候我们会想,EPOLLET只触发一次,那我们如何读取它所有的数据,所以我们实现的时候必须一次性读干净文件,不然它的文件描述符就不能用了。所以为了实现这样的方法,就要对文件设置为非阻塞。
下面为设置非阻塞代码:
void setnonblock(int fd)
{
int flags = 0;
fcntl(fd,F_GETFL,flags);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
}
在设置非阻塞的时候,读的时候的错误必须避免,我们进行判断,以下是代码(errno为错误码,EAGAIN为无数据读的时候)
int n = 0;
while((nread = read(fd,buf+n,BUFSIZE-n))>0)
{
n+=nread;
}
if(nread == -1 && errno != EAGAIN)
{
perror("read error");
}
还有就是accept的错误,以下是代码
while(conn_sock=accept(listenfd,(struct sockaddr *)&removeaddr,(size_t *)addr_len))>0)
{
handle_clinet(conn_sock);
}
if(conn_sock == -1)
{
if(errno!=EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
perror("error");
}
最后对select,poll,epoll三种实现方法进行总结:
select 监听的文件描述符为固定,哪怕产生一个,也要遍历全部,效率不高
poll 虽然文件描述符可以变大,但是是一个数组
epoll 每次读,只返回读的数量,效率高(而且ET效率比LT更高,不过ET编码要求高,而且一次读就要读干净)