EPOLL服务器流程
使用cat命令去查看一个进程可以打开的socket描述符上限
cat /proc/sys/fs/file-max
9223372036854775807
突破文件描述符1024限制
修改:
- 打开sudo vi /etc/security/limits.cof -->设置默认值,可以直接借助命令
- 在文件尾部写入
* soft nofile 10000
* hard nofile 20000
记住要用tab键去对齐。
epoll系列系统调用
epoll是linux系统独有的IO复用函数,它与select和poll有很大的区别
- epoll用一组函数完成任务 epoll_create, epoll_ctl, epoll_wait
- epoll将用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复闯入文件描述符集和事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
#include<sys/epoll.h>
int epoll_create(int size)
args:
size: 创建的红黑树的监听节点数量(仅供内核参考,内核会进行扩容)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: epoll_create函数的返回值,即要操作的文件描述符
op: 对监听红黑树所做的操作
取值有:
- EPOLL_CTL_ADD 向事件表中注册fd上的事件
- EPOLL_CTL_MOD 修改fd上的注册事件
- EPOLL_CTL_DEL 删除fd上的注册事件
fd 待监听的fd
event:
sturct epoll_event{ uint32_t events; epoll_data_t data; } typedef union epoll_data{ void* ptr; //泛型指针 int fd; //对应的监听事件的fd uint32_t u32; unit64_t u64; }epoll_data_t;
events能够取的值有:
EPOLLIN, EPOLLOUT, EPOLLERR
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epfd: epoll_create函数的返回值。
events: 他和ctl的epoll_event结构体不一样,它是一个数组(传出参数),传出满足监听事件的文件描述符
maxevents:数组元素的总个数
timeout:-1 阻塞, 0非阻塞,>0超时时间。
返回值:
>0:满足监听的总个数,可以用作循环上限。
0:
使用epoll来制作服务器
常规步骤
- socket创建套接字listenfd
- 使用setsockopt实现端口复用
- 定义服务器地址结构体serv_addr
- Bind将服务器地址体和套接字绑定在一起
- 使用Listen开始监听
- 使用epoll_create创建epll模型
- 创建变量epoll_event变量ted,输入对应的监听事件EPOLL_CTL_ADD, 和套接字lfd
- 使用epoll_ctl将对应套接字加入到红黑树结构中
业务流程
- 打开while循环
- 使用epoll_wait打开efd的红黑树结构,将监听到的数据放入数组ep(epoll_event类型)
- 使用for循环遍历目前监听到的事件和套接字
- 如果
ep[i].data.fd == listenfd
,那么调用accept创建connfd套接字 - 创建之后,用tep和epoll_ctl将connfd放进红黑树结构
- 如果
ep[i].data.fd != listenfd
,那么说明这是个connfd - 定义一个sockfd来作为connfd1, connfd2…
- 对sockfd这个套接字进行Read读取,如果是0,说明客户端关闭连接,从红黑树结构中删除这个结点即
EPOLL_CTL_DEL
,并且close(sockfd)
- 如果小于0,对其error进行判断操作
- 如果大于0,那么将buf(收到的数据)进行处理后wirte回去给客户端。
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<ctype.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<poll.h>
#include<sys/epoll.h>
#include"wrap.h"
#define MAX_LINE 80
#define SERV_PORT 8888
#define OPEN_MAX 5000
int main(int argc, char* argv[]){
int listenfd, connfd, sockfd, efd;
ssize_t nready, res;
struct sockaddr_in serv_addr, cli_addr;
socklen_t clientaddrLength;
char buf[MAX_LINE], str[INET_ADDRSTRLEN];
struct epoll_event tep, ep[OPEN_MAX]; //tep:用于传入epoll_ctl的, ep[]是epoll_wait参数。
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
Listen(listenfd, 128);
efd = epoll_create(OPEN_MAX); //创建epoll模型,efd指向红黑树根结点
if(efd == -1){
perr_exit("epoll create error");
}
tep.events = EPOLLIN;tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if(res == -1){
perr_exit("epoll ctl error");
}
int num, n;
while(true){
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if(nready == -1){
perr_exit("epoll wait error");
}
for(int i = 0;i < nready;i++){
if(!ep[i].events & EPOLLIN){ //if not a read event, continue loop
continue;
}
if(ep[i].data.fd == listenfd){ //if fd is listenfd
clientaddrLength = sizeof(cli_addr);
connfd = Accept(listenfd, (struct sockaddr *)&cli_addr, &clientaddrLength);
printf("received form %s at port %d\n", inet_ntop(AF_INET, &cli_addr.sin_addr, str, sizeof(str)), ntohs(cli_addr.sin_port));
printf("cfd %d ---- client %d\n", connfd, ++num);
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if(res == -1){
perr_exit("epoll_ctl_error");
}
}
else{
sockfd = ep[i].data.fd;
n = Read(sockfd, buf,MAX_LINE);
if(n == 0){
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if(res == -1){
perr_exit("epoll ctl error");
}
Close(sockfd);
printf("client[%d] closed connection\n", sockfd);
}else if(n < 0){
perror("read n < 0 error");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
Close(sockfd);
}else{
for(i = 0;i < n;i++){
buf[i] = toupper(buf[i]);
}
Write(STDOUT_FILENO, buf, n);
Write(sockfd, buf, n);
}
}
}
}
Close(listenfd);
Close(efd);
return 0;
}
epoll的重要性
epoll是linux下多路复用iO接口select的增强版本, 它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统cpu的利用率,因为它会复用文件描述符集合来传递结果而不使开发者每次等待事件之前都必须重新准备被监听的文件描述符集合。
另一点原因就是获取事件的时候,无须遍历整个被监听的文件描述符集合,只要遍历那些已经被内核IO事件异步唤醒而加入Ready队列的描述符集合