epoll多路复用
select效率随着描述符的增多,时间增多,因为内部是for循环。而epoll时间是固定的,不随着描述符的增多而增长。
在工作中不再使用select,而是用epoll.
epoll共有三个函数:
a int
epoll_create(int size); //
创建一个epoll句柄。现在epoll不限制数量,所以size现在失效了,写1就可以。
b int
epoll_ctl(int epfd, int
op, int fd, struct epoll_event *
event);
//
事件注册函数.dpfd是新创建的epoll句柄.
op为EPOLL_CTL_ADD,表示
注
册
新的fd;为EPOLL_CTL_MOD,表示修改已经注册的fd的监听事件;
为
EPOLL_CTL_DEL,表示从epfd中删除一个fd。fd是需要监听的fd。event
是
告诉内核需要监听
什么事。
typedef union epoll_data{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
struct
epoll_event{
__uint32_t events; //
events可以是几个宏的集合。常用的有:
EPOLLIN,表示对应的文件描述符可以读(包括对端SOCKET正常
关闭)。
EPOLLET
,表示将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)
来说的。
epoll_data_t data;
};
c int
epoll_wait(int epfd, struct epoll_event *
events, int maxevents, int timeout);
//等待事件的产生,类似于select()调用。
epfd为要监听的句
柄.从内核得到事件的集合保存到events中。
maxevents告之内核这个events有多大
(即,数组成员的个数).
timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说
是永久阻塞)
.
触发了几
个,返回值就是几。
例:用epoll实现即时聊天:
//通过epoll来实现tcp即时通信:服务端
#define NUM 10
int main(int argc,char* argv[]){
if(argc!=3){
printf("error args\n");
return -1;
}
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sfd){
perror("socket");
return -1;
}
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(atoi(argv[2]));
ser.sin_addr.s_addr=inet_addr(argv[1]);
int ret;
ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
if(-1==ret){
perror("bind");
return -1;
}
ret=listen(sfd,NUM);
if(-1==ret){
perror("listen");
return -1;
}
int epfd=epoll_create(1); //创建epoll句柄
struct epoll_event event,evs[NUM+1]; //event保存注册的事件的信息,evs 用来保存epoll_wait监听到了的事件
event.events=EPOLLIN;
event.data.fd=sfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd
if(-1==ret){
perror("epoll_ctl");
return -1;
}
event.events=EPOLLIN;
event.data.fd=0;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入
if(-1==ret){
perror("epoll_ctl");
return -1;
}
int i;
int new_fd;
char buf[128];
int n;
while(1){
memset(evs,0,sizeof(evs)); //每次都要清空,以便wait重新填写
ret=epoll_wait(epfd,evs,NUM+1,-1);//监听epfd,监听的结果放到evs数组 中,数组的元素个数为NUM+1,-1表示一直监听.
if(ret >0){
for(i=0;i<ret;i++){ //返回到了数组中,所以循环从evs重查看
if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd){//新请求
new_fd=accept(sfd,NULL,NULL);
printf("new_fd=%d\n",new_fd);
event.events=EPOLLIN;
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event);//注册
}
if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0){ //输入
memset(buf,0,sizeof(buf));
n=read(0,buf,sizeof(buf));
if(n>0){
send(new_fd,buf,strlen(buf)-1,0);
}else if(n==0){ //在新的一行输入ctrl+d,表示EOF
printf("bye\n");
event.events=EPOLLIN;
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
close(new_fd);
}
}
if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd){//对 方向你发送消息
memset(buf,0,sizeof(buf));
n=recv(new_fd,buf,sizeof(buf),0);
if(n>0){
printf("%s\n",buf);
}else if(n==0){ //对方关闭连接了
printf("bye\n");
event.events=EPOLLIN;
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
close(new_fd);
break;
}
}
}
}
}
return 0;
}
|
//客户端
int main(int argc,char** argv){
if(argc !=3){
printf("error args\n");
return -1;
}
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sfd){
perror("socket");
return -1;
}
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(atoi(argv[2]));
ser.sin_addr.s_addr=inet_addr(argv[1]);
int ret;
ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
if(-1==ret){
perror("connect");
return -1;
}
int epfd=epoll_create(1); //创建epoll句柄
struct epoll_event event,evs[2]; //只监听2个
event.events=EPOLLIN;
event.data.fd=sfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd
if(-1==ret){
perror("epoll_ctl");
return -1;
}
event.events=EPOLLIN;
event.data.fd=0;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入
if(-1==ret){
perror("epoll_ctl");
return -1;
}
int i;
char buf[128];
int n;
while(1){
memset(evs,0,sizeof(evs));
ret=epoll_wait(epfd,evs,2,-1);
if(ret>0){
for(i=0;i<ret;i++){
if(evs[i].events == EPOLLIN && evs[i].data.fd == 0){
memset(buf,0,sizeof(buf));
n=read(0,buf,sizeof(buf));
if(n==0){ //ctrl+d 结束
printf("bye\n");
close(sfd);
return 0;
}
n=send(sfd,buf,strlen(buf)-1,0);
if(-1==n){
perror("send");
return -1;
}
}
if(evs[i].events == EPOLLIN && evs[i].data.fd == sfd){
memset(buf,0,sizeof(buf));
n=recv(sfd,buf,sizeof(buf),0);
if(n > 0){
printf("%s\n",buf);
}else if(n==0){
printf("bye\n");
close(sfd);
return 0;
}
}
}
}
}
return 0;
}
|
注:
在上面的程序中,buf一次能够读128个字符,但是如果对方一次给我传输256个字符,我第一次只能接收128个,然后输出。等到下一次while循环时,epoll_wait会继续触发,我再接收剩下的128个字符。这样不仅我每次只能接收一部分,要分成很多段来显示。而且给epoll_wait增加了压力。
我们希望,1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.我们想一次把缓冲区里面的数据一次读完。
EPOLL 事件有两种模型:
Edge Triggered (ET) 边缘触发:只有数据到来,才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发:只要有数据都会触发。
水平触发:当缓冲区有数据时,epoll_wait会不断得到触发。
如果发的长度大于buf的长度,会分成多段来打。。。
新的场景:
1.针对客户端的每一次发送的数据,epoll_wait只触发一次。
2.我们想一次把缓冲区里面的数据一次读完。
如果数据来了,我buf比较小,需要读多次。面临两个问题,1.
边缘触发:只有在上升沿的时候才触发。
改成之后:若客户端发送I come from china .则只触发一次。比如buf为10,则只发送I come fro
发送几次数据,我就响应几次。
例:将描述符的属性改为非阻塞
void change_noblock(int fd){
int status;
status=
fcntl(fd,F_GETFL);
//fcntl函数可以改变已打开的文件描述符性质。F_GETFL表示读取文件描述符状态。
status=status
|O_NONBLOCK;
//将状态改为非阻塞。
要想再变回来,与上按位取反。
int ret=
fcntl(fd,F_SETFL,status);
//设置文件描述符状态,status为新状态。
if(-1==ret){
perror("fcntl");
return;
}
}
int main(){
char buf[128]={0};
change_noblock(0);
//调用函数,将标准输入改为非阻塞,0为标准输入
int ret=read(0,buf,sizeof(buf));
//默认情况下是阻塞的,当执行到这里的时候,程序会卡住,等待键盘输入。如果输入字符,ret为
成功读取的字节数,errno=0。如果输入ctrl+d,则ret=0,errno=0。按ctrl+d并不能让read退出,需要自 己根据返回值,写判断语句让read退出。
//标准输入改为非阻塞之后,如果在read之前输入数据,read会正常读入。如果没有输入数据,read 不会卡着等待输入,而是继续运行,此时ret=-1,errno=11.
printf("ret=%d\n",ret);
printf("errno=%d\n",errno);
printf("buf=%s\n",buf);
return 0;
}
read(0)函数返回值(标准输入阻塞的情况):
1. >0 成功读取的字节数,errno=0。
2. =0 到达文件尾。读标准输入时,按crtl +d,相当于EOF信号。此时errno=0。
3. -1 发生错误,通过errno确定具体错误值。
read(0)标准输入非阻塞的情况:
1. >0 成功读取的字节数
2. =0 到达文件尾。在阻塞前输入ctrl+d,相当于EOF。此时errno=0.
3. -1 有可能发生错误,通过errno确定具体错误值。
有可能因为没有输入数据,read没有阻塞的返回值,此时返回值是-1,errno=11.
recv(sfd)的返回值(网络描述符阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 出错
对方没有发送数据,有没有关闭:则读端阻塞。
recv(sfd)的返回值(网络描述符非阻塞的情况):
1. >0 接收到数据大小
2. =0 正常连接关闭
3. -1 有可能是出错,通过errno确定具体错误值。
有可能因为读取对应描述符,缓冲区为空,recv不阻塞,返回值是-1,errno=11(EAGAIN)
例子: 用epoll实现即时聊天且:1.针对客户端的每一次发送的数据,epoll_wait只触发一次。2.一次把缓冲区里面的数据一次读完。
//通过epoll来实现tcp即时通信:服务端
#define NUM 10
void change_noblock(int fd)
{ //将文件描述符的属性改为非阻塞
int status;
status=fcntl(fd,F_GETFL);
status=status|O_NONBLOCK;
int ret=fcntl(fd,F_SETFL,status);
if(-1==ret)
{
perror("fcntl");
return;
}
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("error args\n");
return -1;
}
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sfd)
{
perror("socket");
return -1;
}
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(atoi(argv[2]));
ser.sin_addr.s_addr=inet_addr(argv[1]);
int ret;
ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
if(-1==ret)
{
perror("bind");
return -1;
}
ret=listen(sfd,NUM);
if(-1==ret)
{
perror("listen");
return -1;
}
int epfd=epoll_create(1); //创建epoll句柄
struct epoll_event event,evs[NUM+1];
event.events=EPOLLIN;
event.data.fd=sfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //在epfd中注册sfd
if(-1==ret)
{
perror("epoll_ctl");
return -1;
}
event.events=EPOLLIN;
event.data.fd=0;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event); //注册标准输入
if(-1==ret)
{
perror("epoll_ctl");
return -1;
}
int i;
int new_fd;
char buf[10];
int n;
while(1)
{
memset(evs,0,sizeof(evs));
ret=epoll_wait(epfd,evs,NUM+1,-1); //监听epfd
if(ret >0)
{
for(i=0;i<ret;i++)
{
if(evs[i].events == EPOLLIN &&evs[i].data.fd == sfd)
{ //新请求
new_fd=accept(sfd,NULL,NULL);
printf("new_fd=%d\n",new_fd);
event.events=EPOLLIN|EPOLLET;//边沿触发的是否可读
change_noblock(new_fd); //改变描述符的属性为非阻塞
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event); //注册
}
if(evs[i].events == EPOLLIN &&evs[i].data.fd == 0)
{ //输入
memset(buf,0,sizeof(buf));
n=read(0,buf,sizeof(buf));
if(n>0)
{
send(new_fd,buf,strlen(buf)-1,0);
}else if(n==0)
{ //在新的一行输入ctrl+d,表示EOF
printf("bye\n");
event.events=EPOLLIN;
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
close(new_fd);
}
}
if(evs[i].events == EPOLLIN &&evs[i].data.fd == new_fd)
{
while(1)
{ //通过循环直到把数据读空
memset(buf,0,sizeof(buf));
n=recv(new_fd,buf,sizeof(buf),0);
if(n>0)
{
printf("%s",buf);
}else if(n == -1 && errno == EAGAIN)
{ //读空了
break;
}else if(n==0){ //对方关闭连接了
printf("bye\n");
event.events=EPOLLIN;
event.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
close(new_fd);
break;
}
}
printf("\n"); //读完之后打印换行
}
}
}
}
return 0;
}
|
//通过epoll来实现tcp即时通信:客户端 (未改进)
int main(int argc,char** argv)
{
if(argc !=3)
{
printf("error args\n");
return -1;
}
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sfd)
{
perror("socket");
return -1;
}
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(atoi(argv[2]));//一定要用htons
ser.sin_addr.s_addr=inet_addr(argv[1]);
int ret;
ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
if(-1==ret)
{
perror("connect");
return -1;
}
int epfd=epoll_create(1);
struct epoll_event event,evs[2];
event.events=EPOLLIN;
event.data.fd=sfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
if(-1==ret)
{
perror("epoll_ctl");
return -1;
}
event.events=EPOLLIN;
event.data.fd=0;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);
if(-1==ret)
{
perror("epoll_ctl");
return -1;
}
int i;
char buf[128];
int n;
while(1)
{
memset(evs,0,sizeof(evs));
ret=epoll_wait(epfd,evs,2,-1);
if(ret>0)
{
for(i=0;i<ret;i++)
{
if(evs[i].events == EPOLLIN && evs[i].data.fd == 0)
{
memset(buf,0,sizeof(buf));
n=read(0,buf,sizeof(buf));
if(n==0)
{
printf("bye\n");
close(sfd);
return 0;
}
n=send(sfd,buf,strlen(buf)-1,0);
if(-1==n)
{
perror("send");
return -1;
}
}
if(evs[i].events== EPOLLIN && evs[i].data.fd ==sfd)
{
memset(buf,0,sizeof(buf));
n=recv(sfd,buf,sizeof(buf),0);
if(n > 0)
{
printf("%s\n",buf);
}else if(n==0)
{
printf("bye\n");
close(sfd);
return 0;
}
}
}
}
}
return 0;
}
|