—epoll I/O多路复用—
提高服务器并发量的重要手段
1、epoll I/O多路复用技术
只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。
2、epoll原理
执行epoll_create()时,创建了红黑树(rbr)和就绪双向链表(rdllist)(均存在于eventpoll结构体中);
二叉查找树(BST)–> AVL树 –> 红黑树
二叉查找树在某些特殊的情况下会退化成链表,AVL是严格平衡的BST(平衡因子不超过1),二叉平衡树的严格平衡策略以牺牲建立查找结构(插入,删除操作)的代价,换来了稳定的O(logN) 的查找时间复杂度。
执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;
执行**epoll_wait()**时立刻返回准备就绪链表里的数据即可。
3、epoll的两种触发模式
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。
ET模式(边缘触发)只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回;
LT 模式(水平触发,默认)只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。
4、函数原型
4.1 epoll_create()
#include <sys / epoll.h>
int nfd1 = epoll_creat(max_size)
int nfd1 = epoll_create1(int flags);
4.2 epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
4.3 epoll_wait()
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
5、代码实例
5.1 client.cpp
#include <iostream>
using namespace std;
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
//client ----- socket -> connect -> send -> close
//1.socket
int c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1)
cout << "socket():" <<strerror(errno) <<endl;
//2.connect
sockaddr_in c_addr;
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(5188);
inet_pton(AF_INET,"127.0.0.1",&c_addr.sin_addr);
int ret =connect(c_fd, (sockaddr *)&c_addr,sizeof(c_addr));
if(ret == -1)
cout<<"connect():" <<strerror(errno) <<endl;
//3.send
//char buf[4] = "YES";
while(1)
{
char buf[1024];
memset(buf,0,sizeof(buf));
scanf("%s",buf);
if ( send(c_fd,buf,sizeof(buf),0) ==-1)
cout<<"send():" <<strerror(errno) <<endl;
cout << buf <<endl;
cout << "send successed! " <<endl;
}
//4.recv
//char recvbuf[3];
//memset(buf,0,sizeof(recvbuf));
//if ( recv(c_fd,recvbuf,3,0) ==-1)
// cout<<"send():" <<strerror(errno) <<endl;
//cout << "sizeof(recvbuf):"<<sizeof(recvbuf)<<endl;
//cout << "back():"<<recvbuf <<endl
//4.close
close(c_fd);
return 0;
}
5.2 server.cpp
#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <algorithm>
#include <signal.h>
int main()
{
//server socket->bind->listen->accept->recv->close
signal(SIGPIPE,SIG_IGN);
signal(SIGCHLD,SIG_IGN);
//创建一个备用文件描述符
int idlefd = open("/dev/null",O_RDONLY|O_CLOEXEC);
//1.socket
int listenfd = socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,0);
if(listenfd == -1)
{
cout<< "socket():" <<strerror(errno) <<endl;
return -1;
}
//2.bind
sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_port = htons(5188);
inet_pton(AF_INET,"0.0.0.0",&bindaddr.sin_addr);
if ( bind(listenfd,(sockaddr *)&bindaddr,sizeof(bindaddr)) == -1)
{
cout<< "bind():" <<strerror(errno) <<endl;
return -1;
}
//3.listen
if ( listen(listenfd,SOMAXCONN) == -1)
{
cout<< "listen():" <<strerror(errno) <<endl;
return -1;
}
// epoll LT
vector<int> clients;
int epollfd = epoll_create1(EPOLL_CLOEXEC);
if(epollfd == -1)
{
cout << "epoll_create1():" << strerror(errno) <<endl;
return -1;
}
epoll_event event;
event.data.fd = listenfd;
event.events = EPOLLIN; //注册读事件
if( epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&event) == -1)
{
cout << "epoll_ctl():" << strerror(errno) <<endl;
return -1;
}
vector<epoll_event> events(16);
sockaddr_in peeraddr;
socklen_t peerlen;
int connfd;
int nready;
int cnt = 0;
while(true)
{
//放在events的事件队列里
nready = epoll_wait(epollfd,&*events.begin(),static_cast<int>(events.size()),-1);
if(nready == -1)
{
if(errno == -1)
continue;
cout << "epoll_wait():" << strerror(errno)<<endl;
return -1;
}
if(nready == 0)
continue;
if( (size_t)nready == events.size())
events.resize(events.size()*2);
for(int i = 0 ; i < nready ; ++i)
{
//监听套接字处于活跃状态
if(events[i].data.fd == listenfd)
{
peerlen = sizeof(peeraddr);
//非阻塞的
connfd = accept4(listenfd,(sockaddr *)&peeraddr,&peerlen,SOCK_NONBLOCK | SOCK_CLOEXEC);
if(connfd == -1)
{
//如果文件描述符满了
if(errno == EMFILE)
{
close(idlefd);
idlefd = accept(listenfd,NULL,NULL);
close(idlefd);
idlefd = open("/dev/null",O_RDONLY|O_CLOEXEC);
continue;
}
else
{
cout << "accept4():"<<strerror(errno)<<endl;
return -1;
}
}
//cout << "ip= " <<inet_ntoa(peeraddr.sin_addr) << "port = "<<ntohs(peeraddr.sin_port) << endl;
clients.push_back(std::move(connfd));
//添加到epollfd文件描述符所在的事件队列里
event.data.fd = connfd;
event.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&event);
}
else if(events[i].events & EPOLLIN) // & 都为1则1 否则0 //?? 若产生了很多事件
{
connfd =events[i].data.fd;
if(connfd <0)
continue;
char buf[1024] ;
int ret = recv(connfd,buf,sizeof(buf),0);
if(ret == -1)
{
cout << "recv():" << strerror(errno)<<endl;
return -1;
}
if(ret == 0)
{
cout << cnt << "--client close" <<endl;
close(connfd);
event = events[i];
epoll_ctl(epollfd,EPOLL_CTL_DEL,connfd,&event);
//将connfd移动到(remove)最后的位置,然后删除(erase)
clients.erase( remove(clients.begin(),clients.end(),connfd),clients.end() );
continue;
}
else
{
++cnt;
cout << cnt<< "-- connection is success! " << endl ;
}
}
}
}
return 0;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com