epoll下的服务器编程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了epoll下的服务器编程相关的知识,希望对你有一定的参考价值。
1.什么是epoll
epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll,目的是为了得到已经就绪的文件描述符,当时间就绪是按顺序添加进去。
epoll的相关系统调用
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。
1>. int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
typedef union epoll_data
{
void* ptr;
int fd;
_uint32_t u32;
_uint64_t u64
}epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event
{
epoll_data_t data;
_uint32_t events;
}
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level
Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个
socket的话,需要再次把这个socket加入到EPOLL队列里
3> int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,
epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复
制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个
events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时
时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,
返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
2.epoll工作原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
3.epoll的2种工作方式-水平触发(LT)和边缘触发(ET)
水平触发(LT):
以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,并且无论后面的数据是否被使用。
LT是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法
中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果
你不作任何操作,内核还是会继续通知你的。
边缘触发(ET):只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件.即只会在数据到来时通知一次。
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死.
I.基于非阻塞文件句柄.
II. 只有当read或者write返回EAGAIN时才需要挂起,等待。但这并不是说每次read()
时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
4.具体代码实现:
//epoll_server.c 1 #include<stdio.h> 2 #include<arpa/inet.h> 3 #include<sys/socket.h> 4 #include<sys/epoll.h> 5 #include<stdlib.h> 6 #include<errno.h> 7 #include<netinet/in.h> 8 #include<string.h> 9 #include<assert.h> 10 #include<fcntl.h> 11 #include<errno.h> 12 #define _MAX_ 20 13 14 typedef struct data_buf 15 { 16 int fd; 17 char buf[1024]; 18 }data_buf_t,*data_buf_p; 19 static void SetNonBlock(int fd) 20 { 21 int fds=0; 22 if((fds=fcntl(fd,F_GETFL))<0) 23 { 24 perror("fcntl"); 25 exit(1); 26 } 27 if(fcntl(fd,F_SETFL)<0) 28 { 29 perror("fcntl"); 30 exit(2); 31 } 32 33 34 35 } 36 static void usage(const char*proc) 37 { 38 printf("usage: %s [ip] [port]",proc); 39 40 } 42 static int startup(int port,char*ip) 43 { 44 int sock=socket(AF_INET,SOCK_STREAM,0); 45 if(sock<0) 46 { 47 perror("socket"); 48 exit(1); 49 } 50 struct sockaddr_in local; 51 local.sin_family=AF_INET; 52 local.sin_port=htons(port); 53 local.sin_addr.s_addr=inet_addr(ip); 54 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 55 { 56 perror("bind"); 57 exit(2); 58 } 59 if(listen(sock,5)<0) 60 { 61 perror("listen"); 62 exit(2); 63 } 64 return sock; 65 66 } 67 68 static int read_data(int fd,char*buf,int size)//什么时候读完用EADAIN检测 69 { 70 printf("read_data\n"); 71 assert(buf); 72 int index=0; 73 ssize_t ret=-1; 74 while(ret=read(fd,buf+index,size-index)) 75 { 76 if(errno==EAGAIN)//客户端无数据发送 77 { 78 break; 79 } 80 index+=ret; 81 82 } 83 84 //printf("read_data\n"); 85 return index; 86 } 87 88 static void write_data(int fd,char *buf)//什么时候写完 89 90 { 91 92 ssize_t len=strlen(buf); 93 int index=0; 94 int ret=0; 95 do 96 { 97 ret=write(fd,buf+index,len-index); 98 index+=ret; 99 100 }while(ret>0); 101 102 } 103 static int epoll_server(int sock) 104 { 105 //1.chuangjian 106 int epoll_fd= epoll_create(256);//创建epoll 107 if(epoll_fd<0) 108 { 109 perror("epoll_create"); 110 return 1; 111 } 112 struct sockaddr_in client; 113 socklen_t len=sizeof(client); 114 //注册套接字信息 115 struct epoll_event ev; 116 ev.events=EPOLLIN; 117 ev.data.fd=sock; 118 SetNonBlock(sock);//将套接字设置为非阻塞 119 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev);//将套接字添加到epoll 120 struct epoll_event ret_ev[_MAX_]; 121 int ret_num=_MAX_; 122 int timeout=8000; 123 int ready_num=-1; 124 while(1) 125 { //有多少准备就绪 126 switch(ready_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout)) 127 { 128 case -1://error 129 perror("epoll_wait"); 130 break; 131 case 0://timeout 132 printf("timeout\n"); 133 break; 134 default: 135 { 136 int i=0; 137 for(;i<ready_num;++i) 138 { //为listen_sock 140 if((ret_ev[i].data.fd==sock)&&(ret_ev[i].events&EPOLLIN)) 141 { 142 int fd=ret_ev[i].data.fd; 143 144 int new_sock=accept(sock,(struct sockaddr*)&client,&len); 145 if(new_sock<0) 146 { 147 perror("accept"); 148 continue; 149 } 150 151 printf("get connect...\n"); 152 //将新的套接字添加到epoll 153 ev.events=EPOLLIN|EPOLLET; 154 ev.data.fd=new_sock; 155 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev); 156 157 } 158 159 else if(ret_ev[i].events&EPOLLIN) 160 { 162 //可读就绪 163 int fd=ret_ev[i].data.fd; 164 // printf("%d\n",fd); 165 data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t)); 166 mem->fd=fd; 167 168 ssize_t s=read(mem->fd,mem->buf,sizeof(mem->buf)-1); 169 //ssize_t s=read_data(mem->fd,mem->buf,sizeof(mem->buf)-1) ; 170 printf("%d\n",s); 171 if(s>0) 172 { 173 174 mem->buf[s]=‘\0‘; 175 printf("client:%s\n",mem->buf); 176 //epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL); 177 ev.events=EPOLLOUT|EPOLLET; 178 ev.data.ptr=mem; 179 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);//将其修改为关心写 180 } 181 182 else if(s==0) 183 { 184 close(fd); 185 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);//删除套接字 186 free(mem); 187 } 188 else 189 { 190 191 printf("error\n"); 192 } 193 } 194 //写就绪 195 else if(ret_ev[i].events&EPOLLOUT) 196 { 197 data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr; 205 char*msg="HTTP/1.0 200 OK\r\n\r\nhello bit :)\r\n"; 206 207 write_data(mem->fd,msg); 208 close(mem->fd); 212 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NULL); 213 free(mem); 214 } 215 216 } 217 } 218 break; 219 } 220 } 221 } 222 int main(int argc,char*argv[]) 223 { 224 if(argc!=3) 225 { 226 usage(argv[0]); 227 return 1; 228 } 229 int port=atoi(argv[2]); 230 char* ip=argv[1]; 231 int listen_sock=startup(port,ip); 232 int opt=1;//端口复用 233 setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) ; 234 epoll_server(listen_sock); 235 close(listen_sock); 236 237 return 0; 238 239 } //epoll_client.c 1 #include<stdio.h> 2 #include<errno.h> 3 #include<poll.h> 4 #include<string.h> 5 #include<sys/types.h> 6 #include<arpa/inet.h> 7 #include<netinet/in.h> 8 #include<sys/socket.h> 9 #include<stdlib.h> 10 11 static void usage(char *proc) 12 { 13 printf("usage: %s [ip] [port] ",proc); 14 } 15 16 17 int main(int argc, char*argv[]) 18 { 19 if(argc!=3) 20 { 21 usage(argv[0]); 22 return 1; 23 } 24 int port=atoi(argv[2]); 25 char* ip=argv[1]; 26 int sock=socket(AF_INET,SOCK_STREAM,0); 27 if(sock<0) 28 { 29 perror("socket"); 30 exit(1); 31 } 32 struct sockaddr_in remote; 33 remote.sin_family=AF_INET; 34 remote.sin_port=htons(port); 35 remote.sin_addr.s_addr=inet_addr(ip); 36 int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote)); 37 if(ret<0) 38 { 39 perror("coonect"); 40 exit(3); 41 42 } 43 char buf[1024]; 44 while(1) 45 { 46 memset(buf,‘\0‘,sizeof(buf)); 47 printf("please enter: "); 48 fflush(stdout); 49 ssize_t s=read(0,buf,sizeof(buf)-1); 50 if(s>0) 51 { 52 write(sock,buf,strlen(buf)); 53 54 } //回显消息 55 ssize_t size=read(sock,buf,sizeof(buf)); 56 printf("server: %s\n",buf); 57 58 } 59 close(sock); 60 return 0; 61 }
使用客户端运行结果:
使用浏览器运行结果:
本文出自 “输出菱形图案” 博客,请务必保留此出处http://10541571.blog.51cto.com/10531571/1786668
以上是关于epoll下的服务器编程的主要内容,如果未能解决你的问题,请参考以下文章
简单通讯聊天 群聊功能 Windows下的客户端 Linux下的epoll服务器
linux网络编程 - epoll边沿触发/水平触发内核实现代码分析
linux网络编程 - epoll边沿触发/水平触发内核实现代码分析