IO多路复用之epoll
程序员文章站
2022-06-14 11:07:09
...
epoll同select,poll等,都是IO多路复用的一种方式,但是性能上远超前面两个。
以select为例,select监听多个文件描述符的时候,当其中某些文件描述符有事件发生,select需要我们自己去轮询,也就是从头到尾扫描所有文件描述符(下面简称fd),看是哪一个被置位,这种情况下效率是比较低的。
并且,select和poll的实现均存在拷贝的过程,也就是执行的时候,需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,会使得用户空间和内核空间在拷贝时复制开销大。
但是epoll就解决了这个问题,在用户空间和内核空间中,存在一个共享内存区,有事件发生的fd会进入到就绪链表中,通过epoll_wait的返回值,可以确定有事件发生的所有fd数量,我们只需要遍历这些fd并进行数据交互即可,省去了轮询的时间花费。
然后,epoll没有最大并发连接的限制,上限是最大可以打开文件的数目(一般远大于2048),一般跟系统内存关系很大。linux下具体数目可以cat /proc/sys/fs/file-max查看。
下面看一下epoll的主要接口:
下面写一个epoll的简易服务器模型:
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/epoll.h>
using namespace std;
int main(){
int listenfd;
listenfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(5000);
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0){
perror("bind");
close(listenfd);
return -1;
}
cout<<"绑定端口成功"<<endl;
if (listen(listenfd,5) != 0){
perror("listen");
close(listenfd);
return -1;
}
cout<<"开监听"<<endl<<endl;
int epfd = epoll_create(5); //参数为可容纳的fd数量
if(epfd == -1) {
perror("epfd");
exit(-1);
}
struct epoll_event ev; //epoll_event结构体,包含events和data
struct epoll_event Events[5]; //用于epoll_wait,共享内存区,存放fd
ev.events = EPOLLIN; //监听可读的fd
ev.data.fd = listenfd; //data是个联合体,用到它的第二个参数-fd
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //add一个fd
if(ret < 0){
perror("epoll_ctl");
exit(-1);
}
while(1) {
ret = epoll_wait(epfd, Events, 5, -1); //最后一个参数-1表示阻塞
if (ret == -1) { //ret 返回所有 有数据来的fd个数
perror("epoll_wait");
exit(-1);
}
for (int i = 0; i < ret; ++i){
if (Events[i].data.fd == listenfd){
//有客户端发起连接
int clientfd = accept(listenfd, 0, 0); //把连接socket放进去
if (clientfd < 0) {
perror("accept");
exit(-1);
}
cout << "客户端socket = " << clientfd << "已连接上。" << endl;
//为新的fd注册事件,下次有数据来 它就会自己通知进程
ev.data.fd = clientfd;
ev.events = EPOLLIN;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev); //add一个fd
if (ret < 0){
perror("epoll_ctl");
exit(-1);
}
}
else{
if (Events[i].events & EPOLLIN) {
//如果事件可读
char strbuffer[32];
memset(strbuffer,0,sizeof(strbuffer));
ret = recv(Events[i].data.fd, strbuffer, sizeof(strbuffer), 0);
if (ret == 0) {
cout<<" 客户端" << Events[i].data.fd << "已经退出!" <<endl;
//注销事件
ev.data.fd = Events[i].data.fd;
ev.events = EPOLLIN;
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, Events[i].data.fd, &ev); //注销一个fd
if(ret < 0) {
perror("epoll_ctl");
}
continue;
}
else if (ret > 0) {
cout<<"收到客户端"<<Events[i].data.fd << ": "<<strbuffer<<endl;
//发送数据
memset(strbuffer, 0, sizeof(strbuffer));
strcpy(strbuffer, "我收到了!");
if (send(Events[i].data.fd, strbuffer, strlen(strbuffer), 0) < 0) {
perror("send");
continue;
}
cout << "发送: "<< strbuffer << endl << endl;
memset(strbuffer,0,sizeof(strbuffer));
}
}
}
}
}
return 0;
}