epoll全名event poll,他是poll的加强版本,从linux 2.6开始。
select,poll,epoll的关系:
- select,IO多路归并,也就是在单一线程中监控多个fd
- poll:具有select的作用,但是select有个局限,被监听的fd数量有限,poll改进了这一点,并且相比于select而言,接口更方便
- epoll:具有epoll的作用,但是poll是O(n)操作,即需要线性遍历所有的fd,逐一检测,而epoll进行了改进,通过事件注册,直接触发相关函数,不需要遍历所有注册的fd。
epoll有三个接口:
- epoll_create:创建epoll对象
- epoll_ctl:控制epoll对象
- epoll_wait:等待epoll事件发生
这里一遍介绍epoll使用方法的文章 https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
我将里面的元代码修改了,将一些方法提取出来,使得程序的整体脉络更清晰,记录在这里,以便参考:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#define MAXEVENTS 64
/**
* 将socket设置为非堵塞
* static 函数防止外部调用,默认都是extern
*/
static int MakeSocketNonBlocking (int nSockFd);
/**
* 接受新的链接,并将其放到epoll中
* @param nListenSock 监听socket
* @param nEpoll epoll对象实例
* @return 0 for OK,-1 for error
*/
static int AcceptConnections(int nListenSock, int nEpoll);
/**
* 处理新链接的客户端
*/
static int ProcessClient(int nClientSock);
/**
* 根据端口创建一个socket,绑定到指定端口并返回此socket。
* 此方法兼容IPv4和IPv6
*/
static int CreateAndBindSocket (char *szPort);
/**
* 主入口
*/
int main (int argc, char *argv[])
{
if (argc != 2)
{
fprintf (stderr, "Usage: %s [port]\n", argv[0]);
exit (EXIT_FAILURE);
}
int nListenSock = CreateAndBindSocket(argv[1]);
if (nListenSock == -1)
{
abort();
}
int iRet = MakeSocketNonBlocking(nListenSock);
if (iRet == -1)
{
abort();
}
iRet = listen(nListenSock, SOMAXCONN);
if (iRet == -1)
{
perror ("listen");
abort ();
}
// 创建epoll对象
int nEpoll = epoll_create(1024);
if (nEpoll == -1)
{
perror ("epoll_create");
abort ();
}
struct epoll_event oEvent;
oEvent.data.fd = nListenSock; // 此事件fd设置为监听socket
oEvent.events = EPOLLIN | EPOLLET; // 注册读(in)事件和边界触发事件,默认为level-triggered
iRet = epoll_ctl (nEpoll, EPOLL_CTL_ADD, nListenSock, &oEvent); // 将此事件添加到epoll对象中
if (iRet == -1)
{
perror ("epoll_ctl");
abort ();
}
/* Buffer where events are returned */
struct epoll_event* pEventList = (epoll_event*)calloc(MAXEVENTS, sizeof oEvent);
/* The event loop */
while (1)
{
// 等待epoll事件发生,n为发生的个数,-1那么wait的事件有内核指定
int n = epoll_wait(nEpoll, pEventList, MAXEVENTS, -1);
for (int i = 0; i < n; i++)
{
if ((pEventList[i].events & EPOLLERR) ||
(pEventList[i].events & EPOLLHUP) ||
(!(pEventList[i].events & EPOLLIN)))
{
/* An error has occured on this fd, or the socket is not
ready for reading (why were we notified then?) */
fprintf (stderr, "epoll error\n");
close (pEventList[i].data.fd);
continue;
}
else if (nListenSock == pEventList[i].data.fd) // 监听socket有in事件触发,说明有新的链接,那么添加到epoll中
{
int iRet = AcceptConnections(nListenSock, nEpoll);
if (iRet == -1)
{
abort();
}
}
else // 处理所有的fd
{
int iRet = ProcessClient(pEventList[i].data.fd);
if (iRet != 0)
{
abort();
}
} // end of if
} // end of for
} // end of while
free (pEventList);
close (nListenSock);
return EXIT_SUCCESS;
}
/**
* 将socket设置为非堵塞
* static 函数防止外部调用,默认都是extern
*/
int MakeSocketNonBlocking (int nSockFd)
{
// 获取当前的flags
int nFlags = fcntl (nSockFd, F_GETFL, 0);
if (nFlags == -1)
{
perror ("fcntl");
return -1;
}
// 添加O_NONBLOCK标记,也就是非堵塞
nFlags |= O_NONBLOCK;
int iRet = fcntl (nSockFd, F_SETFL, nFlags);
if (iRet == -1)
{
perror ("fcntl");
return -1;
}
return 0;
}
/**
* 根据端口创建一个socket,绑定到指定端口并返回此socket。
* 此方法兼容IPv4和IPv6
*/
int CreateAndBindSocket (char *szPort)
{
// 传给函数getaddrinfo的提示数据结构,用于IPv4和IPv6兼容
struct addrinfo oAddrHints;
memset (&oAddrHints, 0, sizeof (struct addrinfo));
oAddrHints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
oAddrHints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
oAddrHints.ai_flags = AI_PASSIVE; /* All interfaces */
// 获取当前host的信息数据
struct addrinfo *pHostAddrInfo;
int iRet = getaddrinfo (NULL, szPort, &oAddrHints, &pHostAddrInfo);
if (iRet != 0)
{
fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (iRet));
return -1;
}
int nSock = -1;
struct addrinfo *pCurAddr; // 当前地址,host可以装有多个网卡,需要遍历每一个可用的ip
for (pCurAddr = pHostAddrInfo; pCurAddr != NULL; pCurAddr = pCurAddr->ai_next)
{
nSock = socket (pCurAddr->ai_family, pCurAddr->ai_socktype, pCurAddr->ai_protocol);
if (nSock == -1)
{
continue;
}
iRet = bind (nSock, pCurAddr->ai_addr, pCurAddr->ai_addrlen);
if (iRet == 0)
{
/* We managed to bind successfully! */
break;
}
close (nSock);
}
if (pCurAddr == NULL)
{
fprintf (stderr, "Could not bind\n");
return -1;
}
// 释放内存
freeaddrinfo (pHostAddrInfo);
// 返回可以用的socket
return nSock;
}
/**
* 接受新的链接,并将其放到epoll中
* @param nListenSock 监听socket
* @param nEpoll epoll对象实例
* @return 0 for OK,-1 for error
*/
int AcceptConnections(int nListenSock, int nEpoll)
{
// We have a notification on the listening socket, which means one or more incoming connections.
while (1)
{
struct sockaddr oClientAddr;
socklen_t nSockLen = sizeof oClientAddr;
int nConnSock = accept (nListenSock, &oClientAddr, &nSockLen);
if (nConnSock == -1)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
// We have processed all incoming connections.
break;
}
else
{
perror ("accept");
return -1;
}
}
// 输出新建连接的客户端地址
char szHostBuf[NI_MAXHOST], szServerBuf[NI_MAXSERV];
int iRet = getnameinfo( &oClientAddr, nSockLen,
szHostBuf, sizeof szHostBuf,
szServerBuf, sizeof szServerBuf,
NI_NUMERICHOST | NI_NUMERICSERV);
if (iRet == 0)
{
printf("Accepted connection on descriptor %d "
"(host=%s, port=%s)\n", nConnSock, szHostBuf, szServerBuf);
}
/* Make the incoming socket non-blocking and add it to the
list of fds to monitor. */
iRet = MakeSocketNonBlocking (nConnSock);
if (iRet == -1)
{
return -1;
}
struct epoll_event oEvent;
oEvent.data.fd = nConnSock;
oEvent.events = EPOLLIN | EPOLLET;
iRet = epoll_ctl(nEpoll, EPOLL_CTL_ADD, nConnSock, &oEvent); // 将新的fd添加到epoll中
if (iRet == -1)
{
perror ("epoll_ctl");
return -1;
}
}
return 0;
}
/**
* 处理新链接的客户端
*/
int ProcessClient(int nClientSock)
{
/* We have data on the fd waiting to be read. Read and
display it. We must read whatever data is available
completely, as we are running in edge-triggered mode
and won't get a notification again for the same
data. */
int done = 0;
while (1)
{
ssize_t count;
char buf[512];
count = read(nClientSock, buf, sizeof buf);
if (count == -1)
{
/* If errno == EAGAIN, that means we have read all
data. So go back to the main loop. */
if (errno != EAGAIN)
{
perror ("read");
done = 1;
}
break;
}
else if (count == 0)
{
/* End of file. The remote has closed the
connection. */
done = 1;
break;
}
/* Write the buffer to standard output */
int iRet = write (1, buf, count);
if (iRet == -1)
{
perror ("write");
return -1;
}
}
if (done)
{
printf ("Closed connection on descriptor %d\n", nClientSock);
/* Closing the descriptor will make epoll remove it
from the set of descriptors which are monitored. */
close (nClientSock);
}
return 0;
}
编译好后,使用telnet链接服务器,可以看到效果