linux下的epoll用法研究:
1. 先写一下epoll比select模型的优点:
其实select模型也挺好的,但是select有一个限制,那就是最多能同时检查1024个fd,这
linux/posix_types.h中有具体定义如下:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改这个值再重编译内核来扩大这个数目,但这似乎并不治本。
而epoll就没有这个限制,epoll的监听上跟内存大小有关,内存越大,那么可监听的fd数量就越多,同时由于select模型是遍历机制的,所以效率上没有epoll高。
2. epoll的使用:
epoll其实使用起来非常简单,只有3个函数:
创建: int epoll_create(int size);
控制: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
和等待事件:int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
于是写了个简单的程序测试了下,功能很简单,20000端口监听,打印客户端发送过来的消息,然后向客户端回复消息, 代码如下:
- //======================= server.c ====================
- #include <stdio.h>
- #include <fcntl.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/epoll.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- const int SERV_PORT = 20000;
- const int LISTEN_BACKLOG = 10;
- const int EPOLL_SIZE = 100;
- const int EPOLL_EVENTS_SIZE = 20;
- void setnonblocking(int sockfd)
- {
- int flags = fcntl(sockfd, F_GETFL, 0);
- fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
- printf("set nonblock success !!\n");
- return;
- }
- int main()
- {
- int servfd, epfd;
- struct epoll_event ev,events[EPOLL_EVENTS_SIZE];
- int ret;
- //create socket
- servfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //TCP SOCKET
- if(servfd < 0)
- {
- printf("create socket failed !!\n");
- return -1;
- }
- printf("create socket success !!\n");
- //set non block
- setnonblocking(servfd);
- //set reuseaddr
- int rep = 1;
- ret = setsockopt(servfd, SOL_SOCKET, SO_REUSEADDR, &rep, sizeof(rep) );
- if(ret < 0)
- {
- printf("set reuse addr failed !!\n");
- close(servfd);
- return -1;
- }
- printf("set reuse addr success !!\n");
- //init the serv addr struct
- struct sockaddr_in servAddr;
- memset(&servAddr, 0, sizeof(servAddr) );
- servAddr.sin_family = AF_INET;
- servAddr.sin_port = htons( SERV_PORT );
- servAddr.sin_addr.s_addr = htons( INADDR_ANY );
- //bind
- ret = bind(servfd, (struct sockaddr*)&servAddr, sizeof(servAddr) );
- if(ret < 0)
- {
- printf("bind failed !!\n");
- close(servfd);
- return -1;
- }
- printf("bind success !!\n");
- //listen
- ret = listen(servfd, LISTEN_BACKLOG);
- if(ret < 0)
- {
- printf("listen failed !!\n");
- close(servfd);
- return -1;
- }
- printf("begin to listen ... \n");
- //create the epoll
- epfd = epoll_create( EPOLL_SIZE );
- if(epfd < 0)
- {
- printf("epoll create failed !!\n");
- close(servfd);
- return -1;
- }
- printf("epoll create success !!\n");
- //add to epoll
- ev.data.fd = servfd;
- ev.events = EPOLLIN | EPOLLET;
- ret = epoll_ctl(epfd, EPOLL_CTL_ADD, servfd, &ev);
- if(ret < 0)
- {
- printf("epoll ctl failed !!\n");
- close( servfd );
- close( epfd );
- return -1;
- }
- printf("epoll ctl success !!\n");
- while(true)
- {
- int nfds = epoll_wait(epfd, events, EPOLL_EVENTS_SIZE, 500);
- if(nfds < 0) //error happens
- {
- printf("wait error !!\n");
- close(servfd);
- close(epfd);
- return -1;
- }
- if(nfds == 0) //wait timeout
- {
- printf("epoll wait timeout !!\n");
- sleep(1);
- continue;
- }
- printf("%d requests to process ... \n", nfds);
- for(int index=0; index<nfds; index++)
- {
- if(events[index].data.fd == servfd)
- {
- printf("accept ... \n");
- struct sockaddr_in cliaddr;
- memset(&cliaddr, 0, sizeof(cliaddr) );
- socklen_t addrlen = sizeof(cliaddr);
- //accept connect
- int confd = accept(servfd, (struct sockaddr*)&cliaddr, &addrlen);
- if(confd < 0)
- {
- printf("accept failed !!\n");
- close(servfd);
- close(epfd);
- return -1;
- }
- printf("accept success !!\n");
- printf("client address : %s--%d \n", inet_ntoa(cliaddr.sin_addr),
- ntohs(cliaddr.sin_port));
- //set non block
- setnonblocking(confd);
- //add to epoll
- epoll_event conev;
- conev.data.fd = confd;
- conev.events = EPOLLET | EPOLLIN;
- int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, confd, &conev);
- if(ret < 0)
- {
- printf("add confd to epoll failed !!\n");
- close( confd );
- close( servfd );
- close( epfd );
- return -1;
- }
- printf("add confd to epoll success !!\n");
- }
- else
- {
- printf("other request ... \n");
- if((events[index].events&EPOLLIN) != 0) //通过events来判断是接收还是发送
- {
- //sleep(5);
- printf("begin recv ... \n");
- int clifd = events[index].data.fd;
- char msg[1024*10] = {0};
- int nMsg = 0;
- while(true)
- {
- char temp[1024] = {0};
- int nrecv = recv(clifd, temp, 1024, 0);
- //*************************************
- //if(nrecv < 0) //这么判断是有问题的
- //{
- // printf("recv error !!\n");
- // close( clifd );
- // close( servfd );
- // close( epfd );
- // return -1;
- //} //下面是正确的,非阻塞socket返回-1,同时error为EAGAIN,则认为全部接收:
- if(errno == EAGAIN) //non block, recv again
- {
- printf("eagain, all data has recved !!\n", errno);
- break;
- }
- else
- {
- printf("recv error , errno is %d !!\n", errno);
- close( clifd );
- close( servfd );
- close( epfd );
- return -1;
- }
- //*************************************
- if(nrecv == 0)
- {
- break;
- }
- memcpy(msg, temp, nrecv);
- nMsg += nrecv;
- }
- printf("recved %d bytes : %s \n", nMsg, msg);
- struct epoll_event recvev;
- recvev.data.fd = clifd;
- recvev.events = EPOLLET | EPOLLOUT;
- int ret = epoll_ctl(epfd, EPOLL_CTL_MOD, clifd, &recvev);
- if(ret < 0)
- {
- printf("add confd to epoll failed !!\n");
- close( clifd );
- close( servfd );
- close( epfd );
- return -1;
- }
- printf("add clifd to epoll fro send success !! \n");
- }
- else if((events[index].events&EPOLLOUT) != 0) //判断是接收还是发送
- {
- printf("begin to send ... \n");
- int clifd = events[index].data.fd;
- char pmsg[1024] = {0};
- sprintf(pmsg, "%s", "copy it !!");
- int msglen = strlen( pmsg );
- int nsend = send(clifd, pmsg, msglen, 0);
- if(nsend <= 0)
- {
- printf("send error !!\n");
- close(servfd);
- close(clifd);
- close(epfd);
- return -1;
- }
- printf("send success : %d bytes!!\n", nsend);
- close(clifd);
- }
- }
- }
- usleep( 100 );
- }
- close(servfd);
- close(epfd);
- printf("finished !!\n");
- return 1;
- }
本来以为只是一个简单的socket服务端程序,结果中间出了两个问题,研究了半天,
一个是非阻塞的socket,当对端发送的数据全部接收到以后,也就是说底层缓冲区中没有数据后,recv调用不是返回0,而是返回-1,并且errno值为EAGAIN。查了半天,还是经验不足导致的。
第二个问题很幼稚,就是判断应该send还是recv的地方,最初的写法是:
events[index].events&EPOLLOUT != 0
可能基础好的人一看就看出来,运算符的优先级不对,会先计算EPOLLOUT!=0部分,返回必然为true,也就是1,而events[index].events如果是EPOLLIN(0x001),那可能还蒙对了,可以进循环,但是如果是EPOLLOUT(0x004),那么循环就进不去,哎,基础不牢,地动山摇啊 。
下面是测试用的client端的代码(这个就是最简单的tcp client代码了 ),如下:
- //======================== client.c ======================
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <arpa/inet.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- const int SERV_PORT = 20000;
- const int MSG_LEN = 1024;
- int main()
- {
- int clifd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(clifd < 0)
- {
- printf("create socket failed !!\n");
- return -1;
- }
- printf("create socket success !!\n");
- struct sockaddr_in servaddr;
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons( SERV_PORT );
- servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
- int ret = connect(clifd, (struct sockaddr*)&servaddr, sizeof(servaddr) );
- if(ret < 0)
- {
- printf("connect failed !!\n");
- close( clifd );
- return -1;
- }
- printf("connect success !!\n");
- char msg[1024] = "fuck you!!";
- int msglen = strlen(msg);
- int nsend = send(clifd, msg, msglen, 0);
- if(nsend < 0)
- {
- printf("send failed !!\n");
- close(clifd);
- return -1;
- }
- printf("send success : %d \n", nsend);
- memset(msg, 0, 1024);
- int nrecv = recv(clifd, msg, 1024, 0);
- if(nrecv < 0)
- {
- printf("recv failed !!\n");
- close(clifd);
- return -1;
- }
- printf("recved %d bytes : %s .\n", nrecv, msg);
- close(clifd);
- return 1;
- }
以上是epoll的最基本的用法,当然,在实际应用中不可能这么简单,还会有涉及到多线程的情况,更复杂的设计,会在时间空余的时候继续研究。
转载于:https://blog.51cto.com/20yuchen/1056546