linux学习笔记 linux下I/O接口的演变之路
Posted 坐望云起
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux学习笔记 linux下I/O接口的演变之路相关的知识,希望对你有一定的参考价值。
一、概述
Linux(实际上是 Unix)的一个基本概念是 Unix/Linux 中的一切都是文件的规则。每个进程都有一个指向文件、套接字、设备和其他操作系统对象的文件描述符表。
与许多 IO 源一起工作的典型系统有一个初始化阶段,然后进入某种待机模式——等待任何客户端发送请求并响应它。
1、用户和内核模式
在任何现代操作系统中,CPU 实际上都在两种截然不同的模式下花费时间:
内核模式
在内核模式下,执行代码可以完全且不受限制地访问底层硬件。它可以执行任何 CPU 指令并引用任何内存地址。内核模式通常保留给操作系统的最低级别、最受信任的功能。内核模式下的崩溃是灾难性的;他们将停止整个 PC。
用户模式
在用户模式下,执行代码无法直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统 API 来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可以恢复的。在您的计算机上运行的大部分代码都将在用户模式下执行。
2、进程切换
进程切换是操作系统调度程序从一个正在运行的程序更改为另一个。这需要保存当前执行程序的所有状态,包括它的寄存器状态、相关的内核状态以及它的所有虚拟内存配置。
一个进程切换将通过以下更改:
保存处理器上下文,包括程序计数器和其他寄存器
更新过程控制块 (PCB) 信息
将PCB的进程移动到相应的队列中,如就绪队列、事件块队列等队列
选择另一个进程执行并更新其 PCB
更新内存中的数据结构
恢复 PCB 上下文
3、阻塞进程
阻塞进程通常在等待一个事件,例如释放信号量或消息到达其消息队列。在多任务系统中,此类进程被期望通过系统调用通知调度程序它要等待,以便可以将它们从活动调度队列中删除,直到事件发生。在等待期间继续运行的进程(即,在紧密循环中不断轮询事件)被称为忙等待,这是不可取的,因为它浪费了可用于其他进程的时钟周期。当进程进入阻塞状态时,不会占用CPU资源。
4、缓冲 I/O
缓冲的输出流会将写入结果累积到一个中间缓冲区中,仅当累积了足够的数据(或请求了 flush())时才将其发送到 OS 文件系统。这减少了文件系统调用的数量。由于在大多数平台上文件系统调用可能很昂贵(与短 memcpy 相比),因此在执行大量小型写入时,缓冲输出是一种净赢。当您已经有大缓冲区要发送时,无缓冲输出通常会更好——复制到中间缓冲区不会进一步减少操作系统调用的数量,并且会引入额外的工作。数据在传输过程中需要进行数据复制操作,操作操作带来的CPU内存开销非常高。
5、文件描述符 (FD)
在 Unix 和相关计算机操作系统中,文件描述符(FD,不太常见的 fildes)是用于访问文件或其他输入/输出资源(例如管道或网络套接字)的抽象指示符(句柄)。文件描述符构成 POSIX 应用程序编程接口的一部分。它是一个非负的索引值,很多底层程序经常基于它。
当发生读取操作时,数据会经历两个阶段:
1、等待数据准备好
2、将数据从内核复制到进程
因为这两个阶段,Linux系统产生了以下5种网络模型:
阻塞 I/O
非阻塞 I/O
I/O 多路复用
信号驱动I/O
异步 I/O
其中I/O 多路复用貌似是最常用的方式。
二、使用 fork()
Fork()创建一个与其父进程同步运行的新子进程,Fork 比多线程效率低。在使用fork的服务器中,一个进程对应一个客户端,即使有性能空间,内存也会耗尽,响应性能明显下降。最终导致著名的 C10K 问题。
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define INF_TIME -1
#define DISABLE -1
int listen_fd;
void int_handle(int n)
close(listen_fd);
exit(EXIT_SUCCESS);
// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n)
ssize_t n_left = n, n_written;
while (n_left > 0)
if ((n_written = write(fd, ptr, n_left)) <= 0)
return n_written;
n_left -= n_written;
ptr += n_written;
return EXIT_SUCCESS;
int main(int argc, char **argv)
// Create listen socket
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
fprintf(stderr, "Error: socket\\n");
return EXIT_FAILURE;
// TCP port number
int port = 8080;
// Initialize server socket address
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind socket to an address
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0)
fprintf(stderr, "Error: bind\\n");
return EXIT_FAILURE;
// Listen
if (listen(listen_fd, BACKLOG_SIZE) < 0)
fprintf(stderr, "Error: listen\\n");
return EXIT_FAILURE;
// Set INT signal handler
signal(SIGINT, int_handle);
fprintf(stderr, "listen on port %d\\n", port);
while (1)
// Check new connection
struct sockaddr_in client_addr;
socklen_t len_client = sizeof(client_addr);
int conn_fd;
if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
&len_client)) < 0)
fprintf(stderr, "Error: accept\\n");
return EXIT_FAILURE;
printf("Accept socket %d (%s : %hu)\\n", conn_fd,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
pid_t pid = fork();
if (pid < 0)
fprintf(stderr, "Error: fork\\n");
return EXIT_FAILURE;
if (pid == 0)
// child
char buf[BUF_SIZE];
close(listen_fd);
while (1)
ssize_t n = read(conn_fd, buf, BUF_SIZE);
if (n < 0)
fprintf(stderr, "Error: read from socket %d\\n", conn_fd);
close(conn_fd);
exit(-1);
else if (n == 0) // connection closed by client
printf("Close socket %d\\n", conn_fd);
close(conn_fd);
exit(0);
else
printf("Read %zu bytes from socket %d\\n", n, conn_fd);
write_n(conn_fd, buf, n);
else
// parent
close(conn_fd);
close(listen_fd);
return EXIT_SUCCESS;
三、I/O多路复用
1、select
计算复杂度为 O(n),因为必须线性搜索描述符(select函数仅仅知道有几个I/O事件发生了,但并不知道具体是哪几个socket连接有I/O事件,还需要轮询去查找,时间复杂度为O(n),处理的请求数越多,所消耗的时间越长。)。特点是可以管理的描述符数量有上限。
select() 只能监视小于 FD_SETSIZE (1024) 的文件描述符数量——对于许多现代应用程序来说,这是一个不合理的下限——并且这个限制不会改变。所有现代应用程序都应该使用 poll(2) 或 epoll(7 ),不受此限制。
参考代码
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1
int listen_fd;
void int_handle(int n)
close(listen_fd);
exit(EXIT_SUCCESS);
// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n)
ssize_t n_left = n, n_written;
while (n_left > 0)
if ((n_written = write(fd, ptr, n_left)) <= 0)
return n_written;
n_left -= n_written;
ptr += n_written;
return EXIT_SUCCESS;
int main(int argc, char **argv)
char buf[BUF_SIZE];
fd_set fds;
FD_ZERO(&fds);
int clients[N_CLIENT];
for (int i = 0; i < N_CLIENT; i++)
clients[i] = DISABLE;
memset(&fds, 0, sizeof(fds));
// Create listen socket
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
fprintf(stderr, "Error: socket\\n");
return EXIT_FAILURE;
// Set INT signal handler
signal(SIGINT, int_handle);
// TCP port number
int port = 8080;
// Initialize server socket address
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind socket to an address
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0)
fprintf(stderr, "Error: bind\\n");
return EXIT_FAILURE;
// Listen
if (listen(listen_fd, BACKLOG_SIZE) < 0)
fprintf(stderr, "Error: listen\\n");
return EXIT_FAILURE;
fprintf(stderr, "listen on port %d\\n", port);
FD_SET(listen_fd, &fds);
int max_fd = listen_fd; // max fd
int max_i = 0; // max client into clients[] array
while (1)
FD_ZERO(&fds);
FD_SET(listen_fd, &fds);
for (int i = 0; i < N_CLIENT; i++)
if (clients[i] != DISABLE)
FD_SET(clients[i], &fds);
int res_select = select(max_fd + 1, &fds, NULL, NULL, NULL);
if (res_select < 0)
fprintf(stderr, "Error: select");
return EXIT_FAILURE;
// Check new connection
if (FD_ISSET(listen_fd, &fds))
struct sockaddr_in client_addr;
socklen_t len_client = sizeof(client_addr);
int connfd;
if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
&len_client)) < 0)
fprintf(stderr, "Error: accept\\n");
return EXIT_FAILURE;
printf("Accept socket %d (%s : %hu)\\n", connfd,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// Save client socket into clients array
int i;
for (i = 0; i < N_CLIENT; i++)
if (clients[i] == DISABLE)
clients[i] = connfd;
break;
// No enough space in clients array
if (i == N_CLIENT)
fprintf(stderr, "Error: too many clients\\n");
close(connfd);
if (i > max_i)
max_i = i;
if (connfd > max_fd)
max_fd = connfd;
// Check all clients to read data
for (int i = 0; i <= max_i; i++)
int sock_fd;
if ((sock_fd = clients[i]) == DISABLE)
continue;
// If the client is readable or errors occur
ssize_t n = read(sock_fd, buf, BUF_SIZE);
if (n < 0)
fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
close(sock_fd);
clients[i] = DISABLE;
else if (n == 0) // connection closed by client
printf("Close socket %d\\n", sock_fd);
close(sock_fd);
clients[i] = DISABLE;
else
printf("Read %zu bytes from socket %d\\n", n, sock_fd);
write_n(sock_fd, buf, n);
write_n(1, buf, n);
close(listen_fd);
return EXIT_SUCCESS;
2、poll
它和select的功能几乎一样,同样的计算量只需要O(n)。但是,它可以被认为是向上兼容的,因为对要管理的描述符没有限制。
参考代码
#include <arpa/inet.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1
int listen_fd;
void int_handle(int n)
close(listen_fd);
exit(EXIT_SUCCESS);
// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n)
ssize_t n_left = n, n_written;
while (n_left > 0)
if ((n_written = write(fd, ptr, n_left)) <= 0)
return n_written;
n_left -= n_written;
ptr += n_written;
return EXIT_SUCCESS;
int main(int argc, char **argv)
char buf[BUF_SIZE];
struct pollfd clients[N_CLIENT];
// Create listen socket
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
fprintf(stderr, "Error: socket\\n");
return EXIT_FAILURE;
// TCP port number
int port = 8080;
// Initialize server socket address
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind socket to an address
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0)
fprintf(stderr, "Error: bind\\n");
return EXIT_FAILURE;
// Listen
if (listen(listen_fd, BACKLOG_SIZE) < 0)
fprintf(stderr, "Error: listen\\n");
return EXIT_FAILURE;
// Set INT signal handler
signal(SIGINT, int_handle);
fprintf(stderr, "listen on port %d\\n", port);
clients[0].fd = listen_fd;
clients[0].events = POLLIN;
for (int i = 1; i < N_CLIENT; i++)
clients[i].fd = DISABLE;
int max_i = 0; // max index into clients[] array
while (1)
int n_ready = poll(clients, max_i + 1, INF_TIME);
// Time out
if (n_ready == 0)
continue;
// Error poll
if (n_ready < 0)
fprintf(stderr, "Error: poll %d\\n", errno);
return errno;
// Check new connection
if (clients[0].revents & POLLIN)
struct sockaddr_in client_addr;
socklen_t len_client = sizeof(client_addr);
int connfd;
if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
&len_client)) < 0)
fprintf(stderr, "Error: accept\\n");
return EXIT_FAILURE;
printf("Accept socket %d (%s : %hu)\\n", connfd,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// Save client socket into clients array
int i;
for (i = 0; i < N_CLIENT; i++)
if (clients[i].fd == DISABLE)
clients[i].fd = connfd;
break;
// No enough space in clients array
if (i == N_CLIENT)
fprintf(stderr, "Error: too many clients\\n");
close(connfd);
clients[i].events = POLLIN;
if (i > max_i)
max_i = i;
// Check all clients to read data
for (int i = 1; i <= max_i; i++)
int sock_fd;
if ((sock_fd = clients[i].fd) == DISABLE)
continue;
// If the client is readable or errors occur
if (clients[i].revents & (POLLIN | POLLERR))
ssize_t n = read(sock_fd, buf, BUF_SIZE);
if (n < 0)
fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
close(sock_fd);
clients[i].fd = DISABLE;
else if (n == 0) // connection closed by client
printf("Close socket %d\\n", sock_fd);
close(sock_fd);
clients[i].fd = DISABLE;
else
printf("Read %zu bytes from socket %d\\n", n, sock_fd);
write_n(sock_fd, buf, n);
write_n(1, buf, n);
close(listen_fd);
return EXIT_SUCCESS;
3、epoll
select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。
由于描述符的状态是在内核空间监控的,所以直接返回修改后的描述符,所以计算量为O(1),可以高速处理。
系统调用帮助我们在内核中创建和管理上下文。我们将任务分为 3 个步骤:
#include <sys/epoll.h>
# 调用epoll_create()创建一个ep对象,即红黑树的根节点,返回一个文件句柄
int epoll_create(int size);
# 调用epoll_ctl()向这个ep对象(红黑树)中添加、删除、修改感兴趣的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
# 调用epoll_wait()等待,当有事件发生时网卡驱动会调用fd上注册的函数并将该fd添加到rdlist中,解除阻塞
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参考代码
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1
int listen_fd;
void int_handle(int n)
close(listen_fd);
exit(EXIT_SUCCESS);
// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n)
ssize_t n_left = n, n_written;
while (n_left > 0)
if ((n_written = write(fd, ptr, n_left)) <= 0)
return n_written;
n_left -= n_written;
ptr += n_written;
return EXIT_SUCCESS;
int main(int argc, char **argv)
char buf[BUF_SIZE];
// Create listen socket
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
fprintf(stderr, "Error: socket\\n");
return EXIT_FAILURE;
// TCP port number
int port = 8080;
// Initialize server socket address
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind socket to an address
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0)
fprintf(stderr, "Error: bind\\n");
return EXIT_FAILURE;
// Listen
if (listen(listen_fd, BACKLOG_SIZE) < 0)
fprintf(stderr, "Error: listen\\n");
return EXIT_FAILURE;
// Set INT signal handler
signal(SIGINT, int_handle);
fprintf(stderr, "listen on port %d\\n", port);
// Create epoll
int epfd = epoll_create1(0);
if (epfd < 0)
fprintf(stderr, "Error: epoll create\\n");
close(listen_fd);
return EXIT_FAILURE;
struct epoll_event listen_ev;
memset(&listen_ev, 0, sizeof(listen_ev));
listen_ev.events = EPOLLIN;
listen_ev.data.fd = listen_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &listen_ev) < 0)
fprintf(stderr, "Error: epoll ctl add listen\\n");
close(listen_fd);
return EXIT_FAILURE;
struct epoll_event evs[N_CLIENT];
while (1)
// Wait epoll listener
int n_fds = epoll_wait(epfd, evs, N_CLIENT, -1);
// Error epoll
if (n_fds < 0)
fprintf(stderr, "Error: epoll wait\\n");
close(listen_fd);
return EXIT_FAILURE;
for (int i = 0; i < n_fds; i++)
if (evs[i].data.fd == listen_fd) // Add epoll listener
struct sockaddr_in client_addr;
socklen_t len_client = sizeof(client_addr);
int conn_fd;
if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
&len_client)) < 0)
fprintf(stderr, "Error: accept\\n");
return EXIT_FAILURE;
printf("Accept socket %d (%s : %hu)\\n", conn_fd,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
struct epoll_event conn_ev;
memset(&conn_ev, 0, sizeof(listen_ev));
conn_ev.events = EPOLLIN;
conn_ev.data.fd = conn_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &conn_ev) < 0)
fprintf(stderr, "Error: epoll ctl add listen\\n");
close(listen_fd);
return EXIT_FAILURE;
else if (evs[i].events & EPOLLIN) // Read data from client
int sock_fd = evs[i].data.fd;
ssize_t n = read(sock_fd, buf, BUF_SIZE);
if (n < 0)
fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
close(sock_fd);
else if (n == 0) // connection closed by client
printf("Close socket %d\\n", sock_fd);
struct epoll_event sock_ev;
memset(&sock_ev, 0, sizeof(listen_ev));
sock_ev.events = EPOLLIN;
sock_ev.data.fd = sock_fd;
if (epoll_ctl(epfd, EPOLL_CTL_DEL, sock_fd, &sock_ev) < 0)
fprintf(stderr, "Error: epoll ctl dell\\n");
close(listen_fd);
return EXIT_FAILURE;
close(sock_fd);
else
printf("Read %zu bytes from socket %d\\n", n, sock_fd);
write_n(sock_fd, buf, n);
write_n(1, buf, n);
close(listen_fd);
return EXIT_SUCCESS;
四、io_uring
io_uring是由 Facebook 的 Jens Axboe 创建的用于 Linux 的新异步 I/O API。它旨在提供一个不受当前select、poll、epoll或aio系列系统调用限制的 API。
从linux kernel 5.1 开始提供一个新的异步 I/O API。处理是通过操纵两个环形缓冲区,提交队列(SQ)和完成队列(CQ )来执行的。例如,要执行从套接字接收消息,通过将操作码提交队列条目 (SQE) 注入 SQ 来异步执行处理。这些进程的完成通知也可以通过从CQ接收完成队列条目 (CQE) 来实现。
io_uring 的用户态 API
io_uring_setup(2)
io_uring_enter(2)
io_uring_register(2)
一个名为 liburing 的库为 io_uring 提供了一个简单的接口。
#include <arpa/inet.h>
#include <errno.h>
#include <liburing.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define N_ENTRY 2048
#define GID 1
int listen_fd;
enum
ACCEPT,
READ,
WRITE,
;
typedef struct UserData
__u32 fd;
__u16 type;
UserData;
void int_handle(int n)
close(listen_fd);
exit(EXIT_SUCCESS);
// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n)
ssize_t n_left = n, n_written;
while (n_left > 0)
if ((n_written = write(fd, ptr, n_left)) <= 0)
return n_written;
n_left -= n_written;
ptr += n_written;
return EXIT_SUCCESS;
int main(int argc, char **argv)
char buf[BUF_SIZE] = 0;
// Create listen socket
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
fprintf(stderr, "Error: socket\\n");
return EXIT_FAILURE;
// TCP port number
int port = 8080;
// Initialize server socket address
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// Bind socket to an address
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
0)
fprintf(stderr, "Error: bind\\n");
return EXIT_FAILURE;
// Listen
if (listen(listen_fd, BACKLOG_SIZE) < 0)
fprintf(stderr, "Error: listen\\n");
return EXIT_FAILURE;
// Set INT signal handler
signal(SIGINT, int_handle);
fprintf(stderr, "listen on port %d\\n", port);
// Initialize io_uring
struct io_uring ring;
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int init_ret = io_uring_queue_init(N_ENTRY, &ring, 0);
if (init_ret < 0)
fprintf(stderr, "Error: init io_uring queue %d\\n", init_ret);
close(listen_fd);
return EXIT_FAILURE;
// Setup first accept
sqe = io_uring_get_sqe(&ring);
io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
&client_len, 0);
io_uring_sqe_set_flags(sqe, 0);
UserData conn_info =
.fd = listen_fd,
.type = ACCEPT,
;
memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));
while (1)
io_uring_submit(&ring);
io_uring_wait_cqe(&ring, &cqe);
struct UserData conn_info;
memcpy(&conn_info, &cqe->user_data, sizeof(conn_info));
int type = conn_info.type;
if (cqe->res == -ENOBUFS)
fprintf(stderr, "Error: no buffer %d\\n", cqe->res);
close(listen_fd);
return EXIT_FAILURE;
else if (type == ACCEPT)
int conn_fd = cqe->res;
printf("Accept socket %d \\n", conn_fd);
if (conn_fd >= 0) // no error
// Read from client
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv(sqe, conn_fd, buf, BUF_SIZE, 0);
UserData read_info =
.fd = conn_fd,
.type = READ,
;
memcpy(&sqe->user_data, &read_info, sizeof(read_info));
// Add new client
sqe = io_uring_get_sqe(&ring);
io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
&client_len, 0);
io_uring_sqe_set_flags(sqe, 0);
UserData conn_info =
.fd = listen_fd,
.type = ACCEPT,
;
memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));
else if (type == READ)
int n_byte = cqe->res;
if (cqe->res <= 0) // connection closed by client
printf("Close socket %d\\n", conn_info.fd);
close(conn_info.fd);
else
// Add Write
printf("Read %d bytes from socket %d\\n", n_byte, conn_info.fd);
sqe = io_uring_get_sqe(&ring);
io_uring_prep_send(sqe, conn_info.fd, buf, n_byte, 0);
write_n(1, buf, n_byte); // output stdout
io_uring_sqe_set_flags(sqe, 0);
UserData write_info =
.fd = conn_info.fd,
.type = WRITE,
;
memcpy(&sqe->user_data, &write_info, sizeof(write_info));
else if (type == WRITE)
// Add read
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv(sqe, conn_info.fd, buf, BUF_SIZE, 0);
UserData read_info =
.fd = conn_info.fd,
.type = READ,
;
memcpy(&sqe->user_data, &read_info, sizeof(read_info));
io_uring_cqe_seen(&ring, cqe);
close(listen_fd);
return EXIT_SUCCESS;
以上是关于linux学习笔记 linux下I/O接口的演变之路的主要内容,如果未能解决你的问题,请参考以下文章