epoll学习
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链接服务器,可以看到效果
声明:如有转载本博文章,请注明出处。您的支持是我的动力!文章部分内容来自互联网,本人不负任何法律责任。