使用epoll编写TCP服务器端
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用epoll编写TCP服务器端相关的知识,希望对你有一定的参考价值。
epoll:结合了select与poll的优点,以及优化了它们的不足,来实现同时控制多个句柄,以此来实现多路复用。它也是使用文件系统的相关信息来实现的
它所使用的三个系统调用函数
1.epoll_create函数
创建一个句柄,size大小可不关心,该句柄会占用一个文件描述符位置
2.epoll_ctl函数,它需要使用一个结构体告诉内核需监听什么事件
它为一个事件注册函数,先将要监听的何种事件进行注册,不同于select函数,它是在监听的时候就要告诉是何种事件
op指要对某个描述符进行何种操作(添加,删除,更改)
epoll_event该结构体第一个参数为要监听的事件类型,第二个参数为一个联合体类型,可为一个fd,也可为一个指针
3.使用epoll_wait函数
使用该函数它只需检查哪个就绪队列(链表)是否为空,用它可以获得已经就绪的描述符
maxevents表示一次最多允许返回多少个就绪事件个数
events是已经分配好的结构体数组(需要用户自己分配),不能为空
避免让客户端等待2MSL时间
当客户端要进行四次挥手时,发送FIN后,会进入一个TIME-WAIT状态等待2MSL,此时它与服务器端的连接还未真正断开,因此在该时间内该用户的端口号等信息不能被其它用户所使用。因此为了避免等待2MSL,要使用setsockopt函数来实现端口复用功能
epoll的两种工作方式:
LT:它支持阻塞套接字和非阻塞套接字,它读取缓冲区中的数据时可以读一部分,因为当一个事件对应的套接字的缓冲区中还有内容时,内核会每次都告知你某个描述符已经就绪,可以进行IO操作
ET(相对高效):数据只有从网络上到达时才会通知你一次,如果没有任何的状态改变,若一次没有将某事件对应的套接字缓冲区内容处理完,它会一直在读或写时组等等待,这样会使后续的多个描述符一直等待。因此必须将套接字设置为非阻塞的,这样当读或写返回一个EAGAIN时才需要等待,即当读到的数据小于请求数据长度时说明缓冲区中的数据已经读完。
服务器端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <netinet/in.h> 9 #include <assert.h> 10 #include <unistd.h> 11 #include <sys/epoll.h> 12 #include<fcntl.h> 13 #define _MAX_ 64 14 #define _MAX_SIZE_ 1024 15 #define BACK_LOG 5 16 typedef struct epoll_buf 17 { 18 int fd; 19 char buf[_MAX_SIZE_]; 20 }epoll_t,*epoll_data_p; 21 static void Usage(const char* proc) 22 { 23 assert(proc); 24 printf("Usage:%s [ip] [port]\n",proc); 25 } 26 27 static void SetNonBlock(int fd) 28 { //set fd to nonblock 29 int fds=0; 30 if(fds=fcntl(fd,F_GETFL)<0) 31 { 32 perror("fcntl"); 33 exit(1); 34 } 35 if(fcntl(fd,F_SETFL,fds|O_NONBLOCK)<0) 36 { 37 perror("fcntl"); 38 exit(2); 39 } 40 41 } 42 static int startup(char *ip,int port) 43 { 44 assert(ip); 45 int listen_sock=socket(AF_INET,SOCK_STREAM,0); 46 if(listen_sock < 0) 47 { 48 perror("socket"); 49 exit(1); 50 } 51 struct sockaddr_in local; 52 local.sin_family=AF_INET; 53 local.sin_port=htons(port); 54 local.sin_addr.s_addr=inet_addr(ip); 55 socklen_t len=sizeof(local); 56 int opt=1; //让客户端避免2MSL时间,实现端口复用 57 setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); 58 if(bind(listen_sock,(struct sockaddr*)&local,len)<0) 59 { 60 perror("bind"); 61 exit(2); 62 } 63 if(listen(listen_sock,BACK_LOG)<0) 64 { 65 perror("listen"); 66 exit(3); 67 } 68 return listen_sock; 69 } 70 int read_data(int fd,char *buf,int size) 71 { 72 assert(buf); 73 int ret=0; 74 int index=0; 75 memset(buf,‘\0‘,sizeof(buf)); 76 while((ret=read(fd,buf+index,size-index))<size) 77 { 78 if(errno==EAGAIN) //客户端已无数据发送 79 { 80 break; 81 } 82 index+=ret; 83 } 84 return index; 85 86 } 87 int write_data(int fd,char* buf,int size) 88 { 89 assert(buf); 90 int ret=0; 91 int index=0; 92 while((ret=write(fd,buf+index,size-index))<size) 93 { 94 if(errno==EAGAIN) 95 break; 96 index+=ret; 97 } 98 return index; 99 } 100 static int epoll_server(int listen_sock) 101 { 102 int epoll_fd=epoll_create(256); 103 if(epoll_fd < 0) 104 { 105 perror("epoll_create"); 106 exit(1); 107 } 108 int ready_num=-1; 109 struct epoll_event ev; 110 ev.events=EPOLLIN|EPOLLET; 111 ev.data.fd=listen_sock; 112 SetNonBlock(listen_sock);//将套接字设置为非阻塞,因使用ET工作模式 113 struct epoll_event ret_ev[_MAX_]; 114 int num=_MAX_; 115 int timeout=5000; 116 struct sockaddr_in client; 117 socklen_t size=sizeof(client); 118 int i=0; 119 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0) 120 { 121 perror("epoll_ctl"); 122 123 exit(1); 124 } 125 int done=0; 126 while(!done) 127 { 128 switch(ready_num=epoll_wait(epoll_fd,ret_ev,num,timeout)) 129 {//只需检查就绪队列(就绪链表)是否为空 130 case 0: 131 printf("timeout...\n"); 132 break; 133 case -1: 134 perror("epoll_wait"); 135 break; 136 default: 137 { 138 for(i=0;i<ready_num;++i) 139 { 140 if(ret_ev[i].data.fd==listen_sock &&141 (ret_ev[i].events &EPOLLIN)) 142 {//判断是否处于监听状态且所关心的事件类型 143 int fd=ret_ev[i].data.fd; 144 int new_sock=accept(fd,(struct sockaddr*)&client ,&size); 145 if(new_sock < 0) 146 { 147 perror("accept"); 148 exit(1); 149 } 150 printf("get a new connect...\n"); 151 SetNonBlock(new_sock); 152 ev.data.fd=new_sock; 153 ev.events=EPOLLIN|EPOLLET; 154 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev )<0) 155 {//将new_sock添加到epoll中 156 perror("epoll_ctl"); 157 exit(2); 158 } 159 }else if(ret_ev[i].events&EPOLLIN) 160 { 161 int fd=ret_ev[i].data.fd; 162 163 epoll_data_p msg ; 164 msg=(epoll_data_p)malloc(sizeof(epoll_t)); 165 if(!msg) 166 { 167 perror("malloc"); 168 exit(1); 169 } 170 msg->fd=fd; 171 // ssize_t _s=read(msg->fd,msg->buf,172 sizeof((msg->buf))-1); 173 int _s=read_data(msg->fd,msg->buf,174 sizeof(msg->buf)-1); 175 if(_s > 0) 176 { 177 msg->buf[_s]=‘\0‘; 178 printf("client:%s",msg->buf); 179 ev.data.ptr=msg; 180 ev.events=EPOLLOUT|EPOLLET; 181 if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,msg->fd, &ev)<0) 182 { 183 perror("epoll_ctl"); 184 return -1; 185 } 186 }else if(_s==0) 187 { 188 printf("client is closed...\n"); 189 free(msg); 190 close(msg->fd); 191 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL); 192 193 } 194 }else if(ret_ev[i].events& EPOLLOUT) 195 { //write ready 196 epoll_data_p msg=(epoll_data_p)ret_ev[i].data.p tr; 197 // write(msg->fd,msg->buf,strlen(msg->buf)); 198 write_data(msg->fd,msg->buf,sizeof(msg->buf)); 199 // close(msg->fd); 200 // char* mem="HTTP/1.0 200 OK\r\n\r\n hello worl d\r\n"; 201 202 //write(msg->fd,mem,strlen(mem)); 203 ev.data.fd=msg->fd; 204 ev.events=EPOLLIN|EPOLLET; 205 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,msg->fd,&ev); 206 //close(msg->fd); 207 // epoll_ctl(epoll_fd,EPOLL_CTL_DEL,msg->fd,NULL ); 208 //free(msg); 209 } 210 211 } 212 213 } 214 break; 215 } 216 } 217 return epoll_fd; 218 219 } 220 int main(int argc,char* argv[]) 221 { 222 if(argc!=3) 223 { 224 Usage(argv[0]); 225 exit(1); 226 } 227 char *_ip=argv[1]; 228 int _port=atoi(argv[2]); 229 int listen_sock=startup(_ip,_port); 230 epoll_server(listen_sock); 231 return 0; 232 }
客户端代码
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <assert.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <netinet/in.h> 9 #include <sys/socket.h> 10 11 static void Usage(const char* proc) 12 { 13 assert(proc); 14 printf("Usage:%s [ip] [port]\n",proc); 15 } 16 17 int main(int argc,char* argv[]) 18 { 19 if(argc!=3) 20 { 21 Usage(argv[0]); 22 exit(1); 23 } 24 int sock=socket(AF_INET,SOCK_STREAM,0); 25 if(sock < 0) 26 { 27 perror("socket"); 28 exit(2); 29 } 30 char *_ip=argv[1]; 31 int _port=atoi(argv[2]); 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 socklen_t len=sizeof(remote); 37 if(connect(sock,(struct sockaddr*)&remote,len)<0) 38 { 39 perror("connect"); 40 return -1; 41 } 42 43 char buf[1024]; 44 while(1) 45 { 46 memset(buf,‘\0‘,sizeof(buf)); 47 printf("please input:"); 48 fflush(stdout); 49 ssize_t _size=read(0,buf,sizeof(buf)-1); 50 if(_size >0) 51 { 52 buf[_size]=‘\0‘; 53 write(sock,buf,strlen(buf)); 54 } 55 56 _size=read(sock,buf,sizeof(buf)-1); 57 if(_size>0) 58 { 59 printf("server---client:%s\n",buf); 60 } 61 } 62 close(sock); 63 return 0; 64 }
运行结果:
使用浏览器为客户端:
以上是关于使用epoll编写TCP服务器端的主要内容,如果未能解决你的问题,请参考以下文章