用Rust的mio
crate的APIs实现了一个echo程序后,发现mio
的APIs和epoll
的APIs非常相似,故写这篇文章对比一下。
下面是引入头文件
,宏定义
和设置套接字非阻塞
函数的代码:
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define LISTER_QUEUE 128 // 监听队列的大小
#define BUF_SIZE 1024 // 缓冲区的大小
#define MAX_EVENTS 64 // epoll一次迭代返回的最大事件数
#define MAX_SOCKS 1024 // 最大并发套接字数量
// 设置套接字非非阻塞
bool _set_nonblocking(int fd) {
int flags = 0;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
printf("fcntl F_GETFL error: %d - %s", errno, strerror(errno));
return false;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
printf("fcntl F_SETFL error: %d - %s", errno, strerror(errno));
return false;
}
return true;
}
下面是整个程序的主流程框架:
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s port\n", argv[0]);
return -1;
}
// 变量定义
int listen_fd = -1, epollfd = -1, client_fd = -1, nfd = 0, i = 0;
struct sockaddr_in server_addr = {0}, client_addr = {0};
struct epoll_event ev = {0}, events[MAX_EVENTS] = {0};
int listen_port = strtol(argv[1], NULL, 10);
bool sock_close = false, read_ok = false;
char client_ip[INET_ADDRSTRLEN] = {0};
char *msgs[MAX_SOCKS] = {NULL}, *msg = NULL;
int port = 0, rdlen = 0;
char buf[BUF_SIZE] = {0};
socklen_t len = 0;
const int one = 1;
// 创建epoll实例
if (epollfd = epoll_create(MAX_EVENTS), epollfd < 0) {
printf("epoll_create error, errno: %d - %s\n", errno, strerror(errno));
goto out;
}
// 创建监听套接字,并进行监听
if (listen_fd = socket(AF_INET, SOCK_STREAM, 0), listen_fd < 0) {
printf("socket error, errno: %d - %s\n", errno, strerror(errno));
goto out;
}
// 设置套接字非阻塞和端口复用
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
_set_nonblocking(listen_fd);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(listen_port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
len = sizeof(client_addr);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0) {
printf("socket error, errno: %d - %s\n", errno, strerror(errno));
goto out;
}
if (listen(listen_fd, LISTER_QUEUE) < 0) {
printf("socket error, errno: %d - %s\n", errno, strerror(errno));
goto out;
}
// 设置监听套接字为可读,并挂到epoll实例中进行监听
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listen_fd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {
printf("epoll_ctl error, errno: %d - %s\n", errno, strerror(errno));
goto out;
}
printf("listen...\n");
// 下面是eventloop的模板代码
while (true) {
if ((nfd = epoll_wait(epollfd, events, MAX_EVENTS, -1)) < 0) {
printf("epoll_wait error, errno: %d - %s\n", errno,
strerror(errno));
goto out;
}
for (i = 0; i < nfd; i++) {
/* 新连接 */
if (events[i].data.fd == listen_fd) {
...
} else {
sock_close = false;
read_ok = false;
client_fd = events[i].data.fd;
// 读事件
if (EPOLLIN && events[i].events) {
...
}
// 设置套接字可写,等待下一次迭代时写数据
if (read_ok) {
ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &ev);
}
// 套接字是否可写
if (EPOLLOUT && events[i].events) {
` ...
}
// 关闭套接字
if (sock_close) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, client_fd, NULL);
close(client_fd);
}
}
}
}
out:
if (listen_fd >= 0)
close(listen_fd);
if (epollfd >= 0)
close(epollfd);
return 0;
}
下面是接收新客户端代码:
if (events[i].data.fd == listen_fd) {
while (true) {
// 错误处理
if (client_fd = accept(
listen_fd, (struct sockaddr *)&client_addr, &len),
client_fd < 0) {
if (errno == EINTR) {
continue;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
break;
} else {
printf("accept error, errno: %d - %s\n", errno,
strerror(errno));
goto out;
}
}
// 关闭多余的客户端
if (client_fd >= MAX_SOCKS) {
printf("too many clients\n");
close(client_fd);
continue;
}
// 获取客户端信息,并进行打印
port = ntohs(client_addr.sin_port);
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip,
sizeof(client_ip));
printf("client ip: %s, port: %d, fd: %d\n", client_ip, port,
client_fd);
_set_nonblocking(client_fd);
setsockopt(client_fd, SOL_SOCKET, SO_REUSEPORT, &one,
sizeof(one));
// 添加客户端到eventloop
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &ev);
}
}
下面是读事件的处理:
if (EPOLLIN && events[i].events) {
while (true) {
if (rdlen = read(client_fd, buf, sizeof(buf)),
rdlen > 0) {
read_ok = true;
// 数据存放到数组中,等待写事件时读取
// 为了代码的简单,不考虑数据覆盖和内存泄露的情况
msg = calloc(1, rdlen + 1);
memcpy(msg, buf, rdlen);
msgs[client_fd] = msg;
printf("recv msg: %s\n", msg);
} else if (0 == rdlen) {
sock_close = true;
break;
} else {
// 错误处理
if (errno == EINTR) {
continue;
} else if (errno == EWOULDBLOCK ||
errno == EAGAIN) {
break;
} else {
printf("read error, errno: %d - %s\n", errno,
strerror(errno));
sock_close = true;
break;
}
}
}
}
下面是写事件的处理:
if (EPOLLOUT && events[i].events) {
if (msgs[client_fd]) {
write(client_fd, msgs[client_fd],
strlen(msgs[client_fd]));
free(msgs[client_fd]);
msgs[client_fd] = NULL;
}
// 此次很重要,数据写完后必须关闭写事件
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, client_fd, &ev);
}
通过上面程序主流程框架的代码,可以发现与Rust crate mio
实现的echo程序主流程框架代码几乎一样。而且两者都是通过eventloop
实现对事件的监听。