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的最基本的用法,當然,在實際應用中不可能這麼簡單,還會有涉及到多線程的情況,更復雜的設計,會在時間空餘的時候繼續研究。