~~~~
说道epoll就要说道和select的比较了,这是比较常见的面试题目了。
~~~~
先介绍epoll:epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
~~~~
epoll获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
~~~~
epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
~~~~
epoll只提供了三个函数epoll_create, epoll_ctl和epoll_wait。
~~~~
两个模式
LT(level triggered)是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
~~~~ 因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。
下面记录下epoll和select的差异。
~~~~
epoll监视的描述符数量不受限制,他所支持的FD上限是最大可以打开的文件的数目,这个数字远远大于2048,具体可以cat /proc/sys/fs/file-max查看,这个数目和系统内存有关。seletc的fd数量有限制,一般大的服务器都不能满足。
~~~~
epoll的IO效率不会随着监视fd的数量增长而下降,epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现,只有就绪的fd才是执行回调函数
~~~~
支持两种工作方式,具体看上面介绍
~~~~
mmap加速内核与用户空间的信息传递。epoll是通过内核于用户空间mmap同一块内存,避免了的内存拷贝,而select需要对数据进行拷贝。
直接上源码,epoll,实现服务端,使用NetAssist工具测试
server
#include <stdio.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <vector>
#include<algorithm>
#define MAXBUFFSIZE 1024
#define MAXEVENTS 500
#define FDSIZE 1000
const int port_server = 9095;
const char* ip_address = "192.168.1.57";
char buf[MAXBUFFSIZE] = { 0 };
int buflen = 0;
std::vector<int> cli_fds;
void handle_events(struct epoll_event *events, int num, int socketfd, int epoll_fd) {
int i;
int fd;
//进行选好遍历
for (i = 0; i < num; i++) {
fd = events[i].data.fd;
//根据描述符的类型和事件类型进行处理
if ((fd == socketfd) && (events[i].events& EPOLLIN)) {
int cli_fd;
struct sockaddr_in cli_addr;
socklen_t cliaddrlen = sizeof(cli_addr);
cli_fd = accept(socketfd, (struct sockaddr*)&cli_addr, &cliaddrlen);
if (cli_fd == -1) {
fprintf(stderr, "client socket accpet failed! errno:%s.\r\n", strerror(errno));
return ;
}
else {
printf("accept a new client:%s:%d \r\n", inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = cli_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cli_fd, &ev);
cli_fds.push_back(cli_fd);
return;
}
}
else if (events[i].events & EPOLLIN){
if (fd == STDIN_FILENO) {
bzero(buf, MAXBUFFSIZE);
// 先获取输入数据
fgets(buf, MAXBUFFSIZE, stdin);
if (strlen(buf) > 0){
// 发送
fprintf(stdout, "send msg:%s.\r\n", buf);
// 轮询发送给所有客户端
std::vector<int>::iterator it = cli_fds.begin();
while (it != cli_fds.end()) {
send(*it, buf, strlen(buf), 0);
it++;
}
}
return;
}
else {
bzero(buf, MAXBUFFSIZE);
int ret_len = static_cast<int>(read(fd, buf, MAXBUFFSIZE));
if (ret_len == -1) {
fprintf(stderr, "read failed! errno:%s.\r\n", strerror(errno));
close(fd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
std::vector<int>::iterator it = find(cli_fds.begin(), cli_fds.end(), fd);
if (it != cli_fds.end()) {
cli_fds.erase(it);
}
else {
fprintf(stderr, "vector remove %d failed! errno:%s.\r\n", fd, strerror(errno));
}
return;
}
else if (ret_len == 0) {
fprintf(stdout, "client socket close! errno:%s.\r\n", strerror(errno));
close(fd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
std::vector<int>::iterator it = find(cli_fds.begin(), cli_fds.end(), fd);
if (it != cli_fds.end()) {
cli_fds.erase(it);
}
else {
fprintf(stderr, "vector remove %d failed! errno:%s.\r\n", fd, strerror(errno));
}
return;
}
else {
struct sockaddr_in cli_addr;
socklen_t cliaddrlen = sizeof(cli_addr);
getpeername(fd, (struct sockaddr*)&cli_addr, &cliaddrlen);
fprintf(stdout, "client:%s,msg:%s.\r\n", inet_ntoa(cli_addr.sin_addr), buf);
//struct epoll_event ev;
//ev.events = EPOLLOUT;
//ev.data.fd = fd;
//epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
}
}
}
//else if (events[i].events & EPOLLOUT){
// //写
// //int wr_len = write(fd, buf, buflen);
// fprintf(stdout, "EPOLLOUT msg:%s.\r\n", buf);
//}
else {
close(fd);
}
}
}
int main(){
int srv_fd = 0;
struct sockaddr_in srv_addr;
int tmp = 0;
srv_fd = socket(AF_INET, SOCK_STREAM, 0);
if (srv_fd < 0) {
fprintf(stderr, "socket create failed! errno:%s.\r\n", strerror(errno));
return -1;
}
//srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 让系统自己获取本机ip
inet_pton(AF_INET, ip_address, &srv_addr.sin_addr);
srv_addr.sin_port = htons(port_server);
srv_addr.sin_family = AF_INET;
// 非阻塞
int flags = fcntl(srv_fd, F_GETFL, 0);
fcntl(srv_fd, F_SETFL, flags | O_NONBLOCK);
// 地址重用
tmp = 1;
if (setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)) == -1) {
close(srv_fd);
fprintf(stderr, "setsockopt set SO_REUSEADDR failed. errno:%s.\r\n", strerror(errno));
return -1;
}
if (bind(srv_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) == -1) {
close(srv_fd);
fprintf(stderr, "bind socket failed. errno:%s.\r\n", strerror(errno));
return -1;
}
if (listen(srv_fd, 5) == -1) {
close(srv_fd);
fprintf(stderr, "listen socket failed. errno:%s.\r\n", strerror(errno));
return -1;
}
printf("create server socket success! \r\n start server. \r\n");
struct epoll_event e_events[MAXEVENTS];
int epoll_fd = 0;
if ((epoll_fd = epoll_create(FDSIZE)) == -1) {
close(srv_fd);
fprintf(stderr, "epoll create failed. errno:%s.\r\n", strerror(errno));
return -1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = srv_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, srv_fd, &ev);
//输入监测
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = STDIN_FILENO;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
while (1) {
tmp = epoll_wait(epoll_fd, e_events, MAXEVENTS, -1);
handle_events(e_events, tmp, srv_fd, epoll_fd);
}
close(epoll_fd);
close(srv_fd);
return 0;
}
client
#include <stdio.h>
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <vector>
#include<algorithm>
#define MAXBUFFSIZE 1024
#define MAXEVENTS 10
#define FDSIZE 10
const int port_server = 9095;
const char* ip_address = "192.168.1.57";
char buf[MAXBUFFSIZE] = { 0 };
int buflen = 0;
bool handle_events(struct epoll_event *events, int num, int socketfd, int epoll_fd) {
int i;
int fd;
//进行选好遍历
for (i = 0; i < num; i++) {
fd = events[i].data.fd;
//根据描述符的类型和事件类型进行处理
if ((fd == socketfd) && (events[i].events& EPOLLIN)) {
// 接收数据
bzero(buf, MAXBUFFSIZE);
int ret = static_cast<int>(read(fd, buf, MAXBUFFSIZE));
if (ret <= -1) {
fprintf(stderr, "read failed! errno:%s.\r\n", strerror(errno));
close(fd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
return false;
}
else {
fprintf(stdout, "revc msg:%s.\r\n", buf);
}
}
else if (fd == STDIN_FILENO && events[i].events & EPOLLIN) {
if (fd == STDIN_FILENO) {
bzero(buf, MAXBUFFSIZE);
// 先获取输入数据
fgets(buf, MAXBUFFSIZE, stdin);
if (strlen(buf) > 0) {
// 发送
fprintf(stdout, "send msg:%s.\r\n", buf);
send(socketfd, buf, strlen(buf), 0);
}
}
}
else {
fprintf(stderr, "line:%d.socket failed! errno:%s.\r\n", __LINE__, strerror(errno));
close(fd);
return false;
}
}
return true;
}
int main() {
int cli_fd = 0;
struct sockaddr_in srv_addr;
int tmp = 0;
cli_fd = socket(AF_INET, SOCK_STREAM, 0);
if (cli_fd < 0) {
fprintf(stderr, "socket create failed! errno:%s.\r\n", strerror(errno));
return -1;
}
bzero(&srv_addr, sizeof(srv_addr));
inet_pton(AF_INET, ip_address, &srv_addr.sin_addr);
srv_addr.sin_port = htons(port_server);
srv_addr.sin_family = AF_INET;
int ret = connect(cli_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret < 0) {
fprintf(stderr, "connect server failed! errno:%s.\r\n", strerror(errno));
close(cli_fd);
return -1;
}
printf("connect server success! \r\n start server. \r\n");
struct epoll_event e_events[MAXEVENTS];
int epoll_fd = 0;
if ((epoll_fd = epoll_create(FDSIZE)) == -1) {
close(cli_fd);
fprintf(stderr, "epoll create failed. errno:%s.\r\n", strerror(errno));
return -1;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = cli_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cli_fd, &ev);
//输入监测
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = STDIN_FILENO;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
while (1) {
tmp = epoll_wait(epoll_fd, e_events, MAXEVENTS, -1);
if (!handle_events(e_events, tmp, cli_fd, epoll_fd)) {
break;
}
}
close(epoll_fd);
close(cli_fd);
return 0;
}
复制错代码了,今天自己看的时候才发现