epoll通信
2017-10-27 16:31
120 查看
epoll_create:
调用此函数内核会产生一个epoll instance数据结构并返回一个文件描述符,这个特殊的描述符就是epoll instance的句柄,后面的两个接口都以它为中心。size参数表示所要监视文件描述符的最大值,不过在目前的Linux版本中已经被弃用(调用的时候不要用0,否则会报invalid argument)
epoll_ctl:
2
3
4
5
6
7
8
9
10
11
12
13
对于需要监视的文件描述符集合,epoll API使用interest list进行管理,list中每个成员由描述符值和所要监控的文件描述符指向的文件表项的引用等组成。epoll_ctl就是管理interest list的接口,op参数说明操作类型:
struct epoll_event结构描述一个文件描述符的epoll行为。在使用epoll_wait函数返回处于ready状态的描述符列表时,data域是唯一能给出描述符信息的字段,所以在调用epoll_ctl加入一个需要监测的描述符时,一定要在此域写入描述符相关信息;events域是bit mask,描述一组epoll事件,在epoll_ctl调用中解释为:描述符所期望的epoll事件。常用的事件描述如下:
EPOLLONESHOT:带有这个标志的描述符,在第一次处于ready状态并被epoll_wait返回之后,内核就会将此描述符标记为inactive状态,之后不会对它进行检测。
epoll_wait
处于ready状态的那些文件描述符会被复制进ready list中,epoll_wait用于向用户进程返回ready list。evlist和maxevents两个参数描述一个由用户分配的struct epoll event数组,调用返回时,内核将ready list复制到这个数组中,并将实际复制的个数作为返回值。注意,如果ready list比evlist长,则只能复制前maxevents个成员;反之,则能够完全复制ready list。另外,struct epoll event结构中的events域在这里的解释是:在被监测的文件描述符上实际发生的事件。参数timeout描述在函数调用中阻塞时间上限,单位是ms:
timeout = -1表示调用将一直阻塞,直到有文件描述符进入ready状态或者捕获到信号才返回;
timeout = 0用于非阻塞检测是否有描述符处于ready状态,不管结果怎么样,调用都立即返回;
timeout > 0表示调用将最多持续timeout时间,如果期间有检测对象变为ready状态或者捕获到信号则返回,否则直到超时。
Q:Edge triggered和Level triggered两种模式有什么不同?
ET模式(边缘触发):在两次epoll_wait调用之间,如果所监测文件描述符没有状态上的变化(not ready到ready),即便其处于ready状态也不会被放进ready list中。
LT模式(水平触发):每次调用epoll_wait的时候,只要被监测的文件描述符处于ready的状态就予以返回。
int epoll_create(int size);1
调用此函数内核会产生一个epoll instance数据结构并返回一个文件描述符,这个特殊的描述符就是epoll instance的句柄,后面的两个接口都以它为中心。size参数表示所要监视文件描述符的最大值,不过在目前的Linux版本中已经被弃用(调用的时候不要用0,否则会报invalid argument)
epoll_ctl:
typedef union epoll_data { void *ptr; /* 指向用户自定义数据 */ int fd; /* 注册的文件描述符 */ uint32_t u32; /* 32-bit integer */ uint64_t u64; /* 64-bit integer */ } epoll_data_t; struct epoll_event { uint32_t events; /* 描述epoll事件 */ epoll_data_t data; /* 见第一个结构体 */ }; int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);1
2
3
4
5
6
7
8
9
10
11
12
13
对于需要监视的文件描述符集合,epoll API使用interest list进行管理,list中每个成员由描述符值和所要监控的文件描述符指向的文件表项的引用等组成。epoll_ctl就是管理interest list的接口,op参数说明操作类型:
op value | meaning |
---|---|
EPOLL_CTL_ADD | 向interest list添加一个需要监视的描述符 |
EPOLL_CTL_DEL | 从interest list中删除一个描述符 |
EPOLL_CTL_MOD | 修改interest list中一个描述符 |
epoll event | meaning |
---|---|
EPOLLIN | 描述符处于可读状态 |
EPOLLOUT | 描述符处于可写状态 |
EPOLLET | 将epoll event通知模式设置成edge triggered |
EPOLLONESHOT | 第一次进行通知,之后不再监测 |
EPOLLHUP | 本端描述符产生一个挂断事件,默认监测事件 |
EPOLLRDHUP | 对端描述符产生一个挂断事件 |
EPOLLPRI | 由带外数据触发 |
EPOLLERR | 描述符产生错误时触发,默认检测事件 |
epoll_wait
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);1
处于ready状态的那些文件描述符会被复制进ready list中,epoll_wait用于向用户进程返回ready list。evlist和maxevents两个参数描述一个由用户分配的struct epoll event数组,调用返回时,内核将ready list复制到这个数组中,并将实际复制的个数作为返回值。注意,如果ready list比evlist长,则只能复制前maxevents个成员;反之,则能够完全复制ready list。另外,struct epoll event结构中的events域在这里的解释是:在被监测的文件描述符上实际发生的事件。参数timeout描述在函数调用中阻塞时间上限,单位是ms:
timeout = -1表示调用将一直阻塞,直到有文件描述符进入ready状态或者捕获到信号才返回;
timeout = 0用于非阻塞检测是否有描述符处于ready状态,不管结果怎么样,调用都立即返回;
timeout > 0表示调用将最多持续timeout时间,如果期间有检测对象变为ready状态或者捕获到信号则返回,否则直到超时。
Q:Edge triggered和Level triggered两种模式有什么不同?
ET模式(边缘触发):在两次epoll_wait调用之间,如果所监测文件描述符没有状态上的变化(not ready到ready),即便其处于ready状态也不会被放进ready list中。
LT模式(水平触发):每次调用epoll_wait的时候,只要被监测的文件描述符处于ready的状态就予以返回。
#include <iostream> #include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <string.h> #include<list> #include<string.h> #define SERVER_PORT 5008 #define MAX_LISTEN 10 #define MAX_EVENTS 20 using namespace std; void setnoblocking(int sock) { int opts; opts=fcntl(sock,F_GETFL); if(opts<0) { perror("fcntl error"); return ; } opts=opts|O_NONBLOCK; if(fcntl(sock,F_SETFL,opts)<0) { perror("fcntl error"); return ; } } void CloseAndDisable(int sockid,epoll_event ee) { close(sockid); ee.data.fd=-1; } int main() { int BUFF_SIZE=5; list<char> datalist; bool bReadOk = false; bool bWritten = false; struct epoll_event ev,events[20]; int epfd=epoll_create(256); struct sockaddr_in client_addr; struct sockaddr_in server_addr; int listen_fd=socket(AF_INET,SOCK_STREAM,0); memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_port=htons(SERVER_PORT); socklen_t client_len=sizeof(client_addr); //bind and listen bind(listen_fd,(sockaddr *)&server_addr,sizeof(server_addr)); listen(listen_fd,MAX_LISTEN); //设置epoll轮循文件描述符 ev.data.fd=listen_fd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev); while(1) { int nfds=epoll_wait(epfd,events,MAX_EVENTS,-1);//0为没事件超时后立即返回,-1为永久等待 int socketfd; for(int i=0;i<nfds;i++) { //如果新监听到socket用户连接到绑定的socket端口,建立新的连接关系 if(events[i].data.fd==listen_fd) { int connect_fd=accept(listen_fd,(sockaddr *)&client_addr,&client_len); if(connect_fd<0) { perror("connfd<0"); return 1; } char *str=inet_ntoa(client_addr.sin_addr); cout<<"accept a connection from "<<str<<endl; //把已经建立连接的socket加入到epoll监听 setnoblocking(connect_fd); ev.data.fd=connect_fd; ev.events=EPOLLET|EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,connect_fd,&ev); } //如果是已经连接的用户,并且收到数据 else if(events[i].events&EPOLLIN) { if((socketfd=events[i].data.fd)<0) { continue; } char buff[BUFF_SIZE]; while(1) { int recvNum=recv(socketfd,buff,5,0); if(recvNum<0) { if(errno==EAGAIN) { // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读 // 在这里就当作是该次事件已处理处. bReadOk=true; break; } else if (errno == ECONNRESET) { // 对方发送了RST CloseAndDisable(socketfd, events[i]); cout << "counterpart send out RST\n"; break; } else if (errno == EINTR) { // 被信号中断 continue; } else { //其他不可弥补的错误 CloseAndDisable(socketfd, events[i]); cout << "unrecovable error\n"; break; } } else if(recvNum==0) { // 这里表示对端的socket已正常关闭.发送过FIN了。 CloseAndDisable(socketfd, events[i]); cout << "counterpart has shut off\n"; break; } //recvNum>0 for(int x=0;x<sizeof(buff);x++) { datalist.push_back(buff[x]); } memset(buff,0,sizeof(buff)); if(recvNum==BUFF_SIZE) { continue; } else { //0<recvNum<BUFF_SIZE //完全读完 bReadOk=true; break; } } if(bReadOk==true) { char data_str[datalist.size()]; int x=0; for(list<char>::iterator iter=datalist.begin();iter!=datalist.end();iter++) { data_str[x]=*iter; x++; } cout<<"recv data: "<<data_str<<endl; //读取完数据后开始写数据 ev.data.fd=socketfd; ev.events=EPOLLOUT|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,socketfd,&ev); } } //如果是已经连接的用户,并且有数据发送 else if(events[i].events&EPOLLOUT) { socketfd=events[i].data.fd; int count=0; const char buff[]="hello from epoll server"; int size_t=sizeof(buff); while(1) { int writeLen=send(socketfd,buff+count,BUFF_SIZE,0); if(writeLen==-1) { if(errno==EAGAIN) { // 对于nonblocking 的socket而言,这里说明了已经全部发送成功了 bWritten = true; break; } else if(errno == ECONNRESET) { // 对端重置,对方发送了RST CloseAndDisable(socketfd, events[i]); cout << "counterpart send out RST\n"; break; } else if (errno == EINTR) { // 被信号中断 continue; } else { // 其他错误 continue; } } if(writeLen==0) { // 这里表示对端的socket已正常关闭. CloseAndDisable(socketfd, events[i]); cout << "counterpart has shut off\n"; break; } //writeLen>0 count+=writeLen; if(size_t-count<BUFF_SIZE) { BUFF_SIZE=size_t-count; send(socketfd,buff+count,BUFF_SIZE,0); bWritten=true; break; } } if(bWritten==true) { //设置用于读操作的文件描述符 ev.data.fd=socketfd; //设置用于注测的读操作事件 ev.events=EPOLLIN | EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,socketfd,&ev); } } } } return 0; }
相关文章推荐
- 详解Vue2中组件间通信的解决全方案
- C#实现子窗体与父窗体通信方法实例总结
- 详解docker容器间通信的一种方法
- vue.js入门(3)――详解组件通信
- C#使用UdpClient类进行简单通信的实例
- 基于C#的UDP协议的同步通信实现代码
- Vue 兄弟组件通信的方法(不使用Vuex)
- java和c#使用hessian通信的方法
- win32下进程间通信(共享内存)实例分析
- nodejs socket服务端和客户端简单通信功能
- WinForm实现跨进程通信的方法
- C#中使用UDP通信实例
- C#利用服务器实现客户端之间通信
- ASP.NET UserControl 通信的具体实现
- C# NetRemoting实现双向通信
- 详解Vue组件之间的数据通信实例
- Vue2.0基于vue-cli+webpack父子组件通信(实例讲解)
- Vue 父子组件、组件间通信
- vue子父组件通信的实现代码
- vue2.0s中eventBus实现兄弟组件通信的示例代码