个人作品,未经允许禁止转载!!!
解决IO问题的编程模型及特点
1.阻塞等待
缺点:一个fd等待时,其他fd无法被监听
2.多线程阻塞等待
缺点:一个fd占用一个线程,线程过多,效率很低
3.非阻塞、忙轮询
缺点:无效轮询占用大量CPU时间
4.Select
缺点:能够打开的fd数量受内核限制,如需修改,需要编译内核。效率较低,每次需要遍历fd数组,复杂度和最大fd数量相同
5.Epoll
优点:用红黑树管理fd,每次返回可以读写或者发生其他事件的fd,大部分情况下效率高于select。
缺点:Linux系统的API。当每个连接都非常活跃时,效率会低于select
Epoll API(Doc地址:epoll Document)
1.头文件:
#include <sys/epoll.h>
2.创建Epoll
//size是Epoll最大监听的fd的数量,对应内部红黑树的最大节点数
//返回一个epoll的描述符 epoll fd
int epoll_create(int size);
3.设置Epoll
/*
epfd为epoll描述符
op为需要的操作,可以是EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL,对应添加,修改,删除
fd为需要监听的描述符,可以是文件描述符、Socket描述符等
*event定义为:
struct epoll_event {
__uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户传递的数据 */
}
其中,events为监听的事件,可以是:
EPOLLIN 可读
EPOLLOUT 可写
EPOLLRDHUP socket另一端关闭连接
POLLPRI 预料之外的错误,很少用,参见https://man7.org/linux/man-pages/man2/poll.2.html
EPOLLERR 发生错误,默认开启,无法关闭,所以也没必要传入
EPOLLHUP 描述符被挂起
EPOLLET 边沿触发事件。默认情况下是水平触发
等
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
data是一个结构体,可以是用户自行传入的任意数据的指针(这是实现Reactor模式的关键)
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
4.监听Epoll事件
/*
其中
epfd是epoll描述符
epoll_event在上一步中有解释,这里传入一个数组,数组大小必须大于epoll_create中传入的最大大小
maxevents是最大监听数量,和epoll_create保持一致
timeout代表超时事件
-1为永久阻塞,直到至少有一个fd返回事件
0为立即返回
大于0时,为阻塞事件,超时返回
*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
5.Epoll编程常用步骤
#define MAX_FD 1024
//创建 epoll
int epfd = epoll_crete(MAX_FD);
//将监听的socket添加进 epoll 中
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event);
while (1) {
//阻塞等待 epoll 中 的fd 触发
int active_cnt = epoll_wait(epfd, events, 1000, -1);
# 对返回的fd进行
for (i = 0 ; i < active_cnt; i++) {
//如果监听的fd触发,那么accept请求,并将fd加入epoll
if (evnets[i].data.fd == listen_fd) {
}
//如果其他fd触发,则进行读写
else if (events[i].events & EPOLLIN) {
}
else if (events[i].events & EPOLLOUT) {
}
}
}
Epoll回显服务器
服务器端
#include <stdio.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#define SERVER_PORT 9998
#define MAX_PENDING_CONNECTION 1024
#define EPOLL_MAX_FD (1024*1024)
#define MAX_BUFFER_SIZE (1024*1024)
using namespace std;
bool running = true;
int main(int argc, char* argv[]){
int32_t listen_fd = 0, epoll_fd = 0;
sockaddr_in server_addr{};
// 创建监听套接字
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 监听
listen(listen_fd, MAX_PENDING_CONNECTION);
// 创建epoll
epoll_fd = epoll_create(EPOLL_MAX_FD);
if (epoll_fd < 0){
perror("Epoll create failed");
return -1;
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = listen_fd;
// 将监听套接字加入epoll
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) < 0){
perror("Epoll CTL add listen fd");
return -1;
}
struct epoll_event *events = new struct epoll_event[EPOLL_MAX_FD];
int client_fd, active_fd;
sockaddr_in client_addr{};
char buffer[MAX_BUFFER_SIZE];
while(running){
// 等待epoll事件触发
active_fd = epoll_wait(epoll_fd, events ,EPOLL_MAX_FD, -1);
// 遍历epoll
for (int i=0; i<active_fd; i++){
// 如果是连接事件,则连接并将新的fd加入到epoll监听read事件
if (events[i].data.fd == listen_fd){
socklen_t clientlen = sizeof(client_addr);
client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &clientlen);
if (client_fd < 0){
perror("Cannot accept");
continue;
}
event = {};
event.events = EPOLLIN;
event.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
}
// 如果是读事件,读取并且发回去
else if (events[i].events & EPOLLIN){
client_fd = events[i].data.fd;
int n = read(client_fd, buffer, MAX_BUFFER_SIZE);
if (n < 0){
perror("read");
continue;
}
else if (n == 0){
event = {};
event.events = EPOLLIN | EPOLLOUT;
event.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, &event);
close(client_fd);
}
else{
buffer[n] = '\0';
printf("Get Data -> %s", buffer);
write(client_fd, buffer, n);
}
}
// 套接字可写事件,没有监听,也不处理
else if (events[i].events & EPOLLOUT){
printf("Send Data -> Epoll out end! \n");
}
}
}
close(epoll_fd);
close(listen_fd);
return 0;
}
客户端
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#define MAX_BUFFER_SIZE (1024*1024)
#define SERVER_PORT 9998
int main(int argc, char* argv[]) {
int sockfd;
char buffer[MAX_BUFFER_SIZE];
struct sockaddr_in server_addr{};
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
perror("socket");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, "0.0.0.0", &server_addr.sin_addr) <= 0){
perror("inet_pton");
return -1;
}
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
perror("connect");
return -1;
}
int opts = 0;
opts = fcntl(sockfd, F_GETFL);
opts = opts | O_NONBLOCK;
fcntl(sockfd, F_SETFL);
char input[1024];
while(fgets(input, 1024, stdin) != nullptr){
printf("Sending -> %s", input);
int n = send(sockfd, input, strlen(input), 0);
if (n < 0){
perror("send");
continue;
}
usleep(500000);
n = read(sockfd, buffer, MAX_BUFFER_SIZE);
if (n <= 0){
perror("read");
return -1;
}
else{
buffer[n] = '\0';
printf("Recv <- %s", buffer);
continue;
}
}
close(sockfd);
return 0;
}
运行服务器和客户端后,从终端向客户端输入即可看到现象。