使用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服务器端的主要内容,如果未能解决你的问题,请参考以下文章

tcp epoll echo服务端程序

聊聊select, poll 和 epoll

linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现

Linux IO多路复用之epoll网络编程及源码(转)

socket编程之TCP/UDP

C语言编写TCP的文件传输