一、epoll原理
监听集合中所有的文件描述符, 如果有文件描述符数据准备好了,直接解除阻塞,把数据准备好的文件描述符放到指定内存中。
二、epoll常用函数
1、创建集合空间
int epoll_create(int size);
参数:
size: 指定集合空间的大小
返回值:成功返回与集合关联的文件描述符,失败返回-1
2、管理集合空间的文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd: 与集合空间关联的文件描述符
op: 操作命令
EPOLL_CTL_ADD:向集合空间中添加文件描述符指令
EPOLL_CTL_DEL:从集合空间中删除文件描述符
fd: 操作的文件描述符
event: 如果是删除操作,该参数忽略,直接传NULL
如果是添加操作,通过该结构体告诉内核监听某个文件描述符的某个事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* 设置监听事件: EPOLLIN表示读事件*/
epoll_data_t data; /* data是一个联合体,通过联合体中的fd成员设置文件描述符*/
};
3、监听集合
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:监听指定集合中所有的文件描述符,如果所有的文件描述符都没有数据准备好, epoll_wait函数阻塞等待。如果有文件描述符数据准备好,epoll_wait直接将直接将准备好的文件描述符信息放到指定数组中。
参数:
epfd: 与集合空间关联的文件描述符
events: 传入参数,接收有数据准备好文件描述符信息数组的起始地址
maxevents: 数组大小
timeout: 设置超时时间,以毫秒为单位
>0 : 超时时间
=0 : 非阻塞函数
-1 : 永久阻塞
返回值:有三种情况
>0 : 准备好的文件描述符个数
=0 :超时时间到
-1 :出错
三、epoll的特点
1、集合在内核中的内部实现是红黑树
2、监听文件描述符的个数可以指定
3、有数据准备好的文件描述,直接告诉应用层, 放到指定的内存中。
四、三种服务端基本并发方案的比较:
1、多进程:
占用资源太多 只要系统资源允许,同时并发数可以任意
2、多线程:
占用资源较少,但同时并发数受系统描述符数组大小的控制
3、多路复用epoll:
占用资源最少,但仅用于对任意客户端请求的处理都是短平快的场合,且同时并发数受系统描述符数组大小的控制
实际项目中可能会采用以上三种方案的组合,例如:多进程+多线程,多进程+epoll
五、示例代码
模板:
#if 0
1、创建集合空间
int epoll_create(int size);
2、管理集合中的文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd: 与集合关联的文件描述符
op : 命令参数
EPOLL_CTL_ADD: 添加文件描述符
EPOLL_CTL_DEL: 删除文件描述符
fd : 操作的文件描述符
event : 如果是删除操作,该参数忽略直接传NULL
如果是添加操作,通过该参数设置监听的事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* EPOLLIN: 读事件 */
epoll_data_t data; /* data.fd: 设置文件描述符 */
};
返回值: 成功返回0, 出错返回-1
3、监听集合中所有的文件描述符
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
参数:
epfd: 与集合关联的文件描述符
events: 承接有数据准备好的文件描述符内存的起始地址
maxevents: 最大元素个数
timeout: 设置超时时间(以毫秒为单位)
> 0: 设置超时时间
= 0: 非阻塞函数
- 1: 永久阻塞函数
返回值:大于0表示有数据准备好的文件描述数量, 0表示超时时间到, -1表示出错
#endif
代码:
client.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
// 信号处理函数
void handle(int sig){
printf("resv sig: %d\n", sig);
}
int main(){
int sockfd, ret;
struct sockaddr_in seraddr;
char buff[1024];
signal(SIGPIPE, handle);
// 创建监听套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr );
// 连接服务端
ret = connect(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr_in));
if(ret < 0){
perror("connnect");
return -1;
}
// 发送请求
while(1){
printf("请输入数据:");
fflush(stdin);
scanf("%s", buff);
ret = write(sockfd, buff, strlen(buff));
if(ret < 0){
perror("write");
return -1;
}
}
// 关闭套接字
close(sockfd);
return 0;
}
server.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
// 成功返回监听套接字, 出错返回-1
int sockfd_init(){
int sockfd, ret;
//创建监听套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0){
perror("socket");
return -1;
}
//设置套接字端口复用选项
int opt = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if(ret < 0){
perror("setsockopt");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr );
//绑定ip+port
ret = bind(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr_in));
if(ret < 0){
perror("bind");
return -1;
}
//通知 内核监听
ret = listen(sockfd, 10);
if(ret < 0){
perror("listen");
return -1;
}
return sockfd;
}
int main()
{
int sockfd, ret, cfd, efd;
//创建集合空间
efd = epoll_create(10);
if(efd < 0){
perror("epoll_create");
return -1;
}
//创建监听套接字
sockfd = sockfd_init();
if(sockfd < 0){
return -1;
}
// 将sockfd加入到集合中
struct epoll_event ev,evs[10];
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&ev);
int count;
char buff[1024] = "";
while(1){
//监听集合中所有文件描述符
printf("wait...\n");
count = epoll_wait(efd,evs,10,-1);
printf("wait over...\n");
if(count < 0){
perror("epoll_wait");
break;
}
for(int i = 0; i < count; i++){
int tfd = evs[i].data.fd;
if(tfd == sockfd){
//1、接收客户端
printf("accept..\n");
cfd = accept(sockfd, NULL, NULL);
printf("accept over..\n");
if(cfd < 0){
perror("accept");
continue;
}
//2、将cfd加入集合
ev.data.fd = cfd;
epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&ev);
}
else{//已经建立连接的客户端发来数据
printf("read..\n");
ret = read(tfd, buff, 1024);
printf("read over..\n");
if(ret < 0){
perror("read");
close(tfd);
epoll_ctl(efd, EPOLL_CTL_DEL,tfd, NULL);
continue;
}
else if(0 == ret){
printf("tcp broken...\n");
close(tfd);
epoll_ctl(efd, EPOLL_CTL_DEL,tfd, NULL);
continue;
}
buff[ret] = '\0';
printf("buff: %s\n", buff);
}
}
}
return 0;
}