目录
1.select/poll简介
2.epoll(重点)
2.1epoll简介
2.2函数原型
epoll_create
epoll_event
epoll_ctl
epoll_wait
fcntl
2.3epoll的两种工作模式以及对应的案例
LT模式(水平触发模式)
ET模式(边沿触发模式)
1.select/poll简介
poll是对select缺点进行一个改进(没有解决内核态和用户态转变的问题,只是解决了1024比特位的限制以及不能重用每次都需要重置的问题。
2.epoll(重点)
2.1epoll简介
rbr红黑树+rdlist双链表
2.2函数原型
epoll_create
#include
int epoll_create(int size);
#includeint epoll_create(int size);
创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。
- 参数:
size : 目前没有意义了。随便写一个数,必须大于0
- 返回值:
-1 : 失败
> 0 : 文件描述符,操作epoll实例的
epoll_event
struct epoll_event {
uint32_t events;
epoll_data_t data;
};
常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 参数:
- epfd : epoll实例对应的文件描述符
- op : 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除
- fd : 要检测的文件描述符
- event : 检测文件描述符什么事情
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 参数:
- epfd : epoll实例对应的文件描述符
- events : 传出参数,保存了发送了变化的文件描述符的信息
- maxevents : 第二个参数结构体数组的大小
- timeout : 阻塞时间 - 0 : 不阻塞 - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - > 0 : 阻塞的时长(毫秒)
- 返回值: - 成功,返回发送变化的文件描述符的个数 > 0 - 失败 -1
fcntl
#include
#include
int fcntl(int fd, int cmd, ... );
参数:
fd:表示需要操作的文件描述符
cmd:表示对文件描述符进行如何操作
1.F_DUPFD:复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符
int ret = fcntl(fd,F_DUPFD);
2.F_GETFL:获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西
3.F_SETFL:设置文件描述符文件状态flag
必选项:O_RDONLY,O_WRONDLY,O_RDWR 不可以被修改
可选项:O_APPEND,O_NONBLOCK
O_APPEND 表示追加数据
O_NONBLOCK 设置成非阻塞
2.3epoll的两种工作模式以及对应的案例
LT模式(水平触发模式)
假设委托内核检测读事件 -> 检测fd的读缓冲区 读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知
LT(level - triggered)是缺省的工作方式,并且同时支持 block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。
服务器案例
#include#include #include #include #include #include int main() { //1.创建socket int lfd = socket(AF_INET,SOCK_STREAM,0); if(lfd==-1){ perror("socket"); exit(-1); } printf("创建socket成功n"); //2.绑定 struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_port=htons(9999); saddr.sin_addr.s_addr=INADDR_ANY; int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1){ perror("bind"); exit(-1); } printf("绑定成功n"); //3.监听 ret=listen(lfd,10); if(ret==-1){ perror("listen"); exit(-1); } printf("监听成功n"); //4.创建epoll实例 int epfd=epoll_create(1); if(epfd==-1){ perror("epoll_create"); exit(-1); } printf("创建epoll实例成功n"); //5.加入监听的文件描述相关信息 struct epoll_event epev; epev.events=EPOLLIN; epev.data.fd=lfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024]; while(1){ ret = epoll_wait(epfd,epevs,1024,-1); if(ret == -1){ perror("epoll_wait"); exit(-1); } printf("ret = %dn",ret); for(int i=0;i 0){ printf("read buf = %sn",buf); memset(buf,0,sizeof (buf)); sprintf(buf,"recv"); write(curfd,buf,strlen(buf)+1); } } } } close(lfd); close(epfd); return 0; }
客户端案例
#include #include#include #include // read write close #include int main() { //1.创建套接字 int cfd=socket(AF_INET,SOCK_STREAM,0); if(cfd==-1){ perror("socket"); exit(0); } printf("1.客户端创建套接字成功n"); //2.连接服务器 struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_port=htons(9999); inet_pton(AF_INET,"192.168.108.128",&saddr.sin_addr);//服务器的ip地址 int ret=connect(cfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1){ perror("connect"); exit(-1); } printf("2.客户端服务器连接成功n"); //3.通信 char recvBuf[1024]={0}; while(1){ printf("请输入你想要发给服务端的内容n"); memset(recvBuf,0,sizeof(recvBuf)); scanf("%s",recvBuf); write(cfd,recvBuf,strlen(recvBuf)+1); memset(recvBuf,0,sizeof(recvBuf)); int data=read(cfd,recvBuf,sizeof(recvBuf)); if(data>0) { printf("服务端返回数据:%sn",recvBuf); } else if(data==0){ printf("服务端断开连接n"); }else if(data<0){ perror("read"); exit(-1); } } close(cfd); return 0; }
ET模式(边沿触发模式)
假设委托内核检测读事件 -> 检测fd的读缓冲区 读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知
ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。ET 模式在很大程度上减少了epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
服务器案例:
#include#include #include #include #include #include #include #include int main(){ int lfd = socket(AF_INET,SOCK_STREAM,0); if(lfd==-1){ perror("socket"); exit(-1); } printf("服务器创建socket成功n"); struct sockaddr_in saddr; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_family=AF_INET; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret == -1){ perror("bind"); exit(-1); } printf("socket绑定成功n"); ret = listen(lfd,8); if(ret == -1){ perror("listen"); exit(-1); } printf("监听套接字成功n"); int epfd = epoll_create(1); if(epfd==-1){ perror("epoll_create"); exit(-1); } struct epoll_event epev; epev.events=EPOLLIN; epev.data.fd=lfd; ret = epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024]; while(1){ ret = epoll_wait(epfd,epevs,1024,-1); if(ret == -1){ perror("epoll_wait"); exit(-1); } printf("ret = %dn",ret); for(int i=0;i 0){ //打印数据 write(STDOUT_FILENO,buf,len); write(curfd,buf,len); } puts(""); if(len==0){ printf("连接断开n"); }else if(len==-1){ if(errno==EAGAIN){ printf("数据过多n"); }else{ perror("read"); exit(-1); } } } } } close(lfd); close(epfd); return 0; }



