关于epoll替代select作为高性能服务器的事件通知机制的资料相当多,我就不在这里班门弄斧了,有兴趣的同学可以参考末尾的文献链接。
这里说明如下:
1.epoll是linux下高并发服务器的完美方案,因为是基于事件触发的,所以比select快的不只是一个数量级。
2.单线程epoll,触发量可达到15000,参见文献[4]
3.高性能server要使用非阻塞方式。
使用ET模型的时候,一定要注意,每次收到有效通知,然后读取数据的时候,务必每次读取干净(读到出错为止)。当再次调用check(sockfd)的时候才能正确返回。
目前使用的epoll模型大多都是ET模式,socket都要设置为非阻塞的。
网上找了很多源码例子,但是大多不理想,下面是我根据网上资料修改尝试出的一个例子,供大家参考。后续更进一步地研究可以参考nginx,memcache,Apache traffic server这类开源代码。
epoll服务器端
//compile: g++ -g epoll_server.cpp -o epoll_server
//run: ./epoll_server
//
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 10
#define LISTENQ 20
#define PORT 5000 //8080
//设置socket连接为非阻塞模式
void setnonblocking (int fd)
{
int opts;
opts = fcntl (fd, F_GETFL);
if (opts < 0)
{
perror ("fcntl(F_GETFL)\n");
exit (1);
}
opts = (opts | O_NONBLOCK);
if (fcntl (fd, F_SETFL, opts) < 0)
{
perror ("fcntl(F_SETFL)\n");
exit (1);
}
}
int main ()
{
struct epoll_event ev, events[MAX_EVENTS];
int listenfd, connfd, nfds, epfd, sockfd, i, nread, n;
struct sockaddr_in local, remote;
socklen_t addrlen;
char buf[BUFSIZ];
//创建listen socket
if ((listenfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
perror ("sockfd\n");
exit (1);
}
setnonblocking (listenfd);
bzero (&local, sizeof (local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl (INADDR_ANY);;
local.sin_port = htons (PORT);
if (bind (listenfd, (struct sockaddr *) &local, sizeof (local)) < 0)
{
perror ("bind\n");
exit (1);
}
listen (listenfd, LISTENQ);
epfd = epoll_create (MAX_EVENTS);
if (epfd == -1)
{
perror ("epoll_create");
exit (EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listenfd;
if (epoll_ctl (epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1)
{
perror ("epoll_ctl: listen_sock");
exit (EXIT_FAILURE);
}
for (;;)
{
nfds = epoll_wait (epfd, events, MAX_EVENTS, -1);
if (nfds == -1)
{
perror ("epoll_wait error");
exit (EXIT_FAILURE);
}
for (i = 0; i < nfds; ++i)
{
sockfd = events[i].data.fd;
if (sockfd == listenfd)
{
while ((connfd = accept (listenfd, (struct sockaddr *) &remote, &addrlen)) > 0)
{
char *ipaddr = inet_ntoa (remote.sin_addr);
printf("accept a connection from [%s]\n", ipaddr);
setnonblocking (connfd); //设置连接socket为非阻塞
ev.events = EPOLLIN | EPOLLET; //边沿触发要求套接字为非阻塞模式;水平触发可以是阻塞或非阻塞模式
ev.data.fd = connfd;
if (epoll_ctl (epfd, EPOLL_CTL_ADD, connfd, &ev) == -1)
{
perror ("epoll_ctl: add");
exit (EXIT_FAILURE);
}
}
if (connfd == -1)
{
if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
perror ("accept");
}
continue;
}
if (events[i].events & EPOLLIN)
{
n = 0;
while ((nread = read (sockfd, buf + n, BUFSIZ - 1)) > 0)
{
n += nread;
}
if (nread == -1 && errno != EAGAIN)
{
perror ("read error");
}
printf("recv from client data [%s]\n", buf);
ev.data.fd = sockfd;
ev.events = events[i].events | EPOLLOUT;
if (epoll_ctl (epfd, EPOLL_CTL_MOD, sockfd, &ev) == -1)
{
perror ("epoll_ctl: mod");
}
}
if (events[i].events & EPOLLOUT)
{
snprintf (buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);
int nwrite, data_size = strlen (buf);
n = data_size;
while (n > 0)
{
nwrite = write (sockfd, buf + data_size - n, n);
if (nwrite < n)
{
if (nwrite == -1 && errno != EAGAIN)
{
perror ("write error");
}
break;
}
n -= nwrite;
}
printf("send to client data [%s]\n", buf);
close (sockfd);
events[i].data.fd = -1;
}
}
}
close (epfd);
close (listenfd);
return 0;
}
epoll客户端
//compile: g++ -g epoll_client.cpp -o epoll_client
//run: ./epoll_client
//
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
using namespace std;
#define PORT 5000
int main(int argc, char* argv[])
{
int sockfd, on = 1;
char buffer[512] = {0};
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
cout << "create socket fail" << endl;
return -1;
}
cout << "succeed to create client socket fd " << sockfd << endl;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
cout << "set socket reuse by etsockopt" << endl;
servaddr.sin_port = htons((short)PORT);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //此处更改epoll服务器地址
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
cout << "connect error" << endl;
return -1;
}
cout << "succeed to connect epoll server " << endl;
char target[] = "The Author: tao_627@aliyun.com";
memcpy(buffer, target, strlen(target));
int wlen = send(sockfd, buffer, strlen(buffer), 0);
if(wlen <= 0)
cout << " send data to server fail " << strerror(errno) << endl;
cout << "send data to server on success, data: [" << buffer << "]"<< endl;
memset(buffer, 0, sizeof(buffer));
int rlen = recv(sockfd, buffer, sizeof(buffer), 0);
if(rlen <= 0)
cout << " receive data from server fail " << strerror(errno) << endl;
cout << "receive data from server on success, data: [" << buffer << "]" << endl;
return 0;
}
下面的参考文献按照我认为的优先级递减排列
[1].http://www.cnblogs.com/aicro/archive/2012/12/27/2836170.html
[2].http://blog.csdn.net/hzhsan/article/details/23650465
[3].http://blog.csdn.net/ljx0305/article/details/4065058
[4].http://blog.csdn.net/win_lin/article/details/7843000
[5].http://blog.csdn.net/win_lin/article/details/7566466
[6].http://blog.csdn.net/qcghdy/article/details/22791077
[7].http://www.cnblogs.com/iTsihang/archive/2013/05/23/3095775.html
[8].http://www.cppblog.com/API/archive/2013/11/27/204456.html#204479
[9].http://blog.csdn.net/ljx0305/article/details/4065058