1、i/o复用
一个进程或者一个线程能够同时对多个文件描述符(socket)提供服务,服务器上的进程或线程如何将多个文件描述符统一监听,当任意一个文件描述符上有事件发生,其都能及时处理。
2、epoll()函数
函数原型 int epoll_create(int size);
int epoll_clt(int epollfd,int cmd,int fd,struct epoll_event *event);
int epoll_wait(int epollfd struct epoll_event *revents,int maxevents,int timeout);
cmd的事件
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
struct epoll_event
{
_uint32_t events; ————用户关注的事件
epoll_data_t data;
};
typedef union epoll_data
{
void *ptr;
int fd; ————常用用户关注的文件描述符
uint32_t u32;
uint64_t u64;
};
函数返回值:
(1)返回值小于0,表示出错
(2)返回值等于0,表示epoll函数等待超时
(3)返回值大于0,表示epoll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数。
3、epoll特有的ET模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
4、epoll的优势
1、文件描述符的范围更大了
2、文件描述符的个数更多(1G的内存上能监听约10万个端口)
3、事件的类型会更多
4、用户关注的事件由内核维护,每次调用eopll_wait时,不需要将用户空间的数据拷贝到内核空间
5、每次epoll只会返回就绪的文件描述符
6、每次调用的效率都是O(1)
7、可以回调
8、epoll支持高效的ET模式
5、简单的实现epoll来进行通信(代码)
1、服务端来连接接收数据的代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#define SIZE 100
int main()
{
int sockfd =socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
int epollfd=epoll_create(5);
assert(epollfd != -1);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = sockfd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
while(1)
{
struct epoll_event events[SIZE];
int n = epoll_wait(epollfd,events,SIZE,-1);
if(n<=0)
{
printf("epoll wait error");
continue;
}
int i=0;
for(;i<n;++i)
{
int fd =events[i].data.fd;
if(fd == sockfd)
{
int len = sizeof(caddr);
int c= accept(fd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
event.events = EPOLLIN | EPOLLRDHUP;
event.data.fd =c;
epoll_ctl(epollfd,EPOLL_CTL_ADD,c,&event);
}
else if(events[i].events & EPOLLRDHUP)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
printf("%d was over \n",fd);
}
else if(events[i].events & EPOLLIN)
{
char buff[128]={0};
recv(fd,buff,127,0);
printf("%d :%s\n",fd,buff);
send(fd,"OK",2,0);
}
}
}
}
2、客户端来连接发送数据的代码:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family =AF_INET;
saddr.sin_port =htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
while(1)
{
char buff[128]={0};
printf("Input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd);
}