2.5.2 io_uring的使用场景

小李小李快乐不已 2025-03-20 15:14:27

https://github.com/0voice

目录

  • 2.5.2 io_uring的使用场景
  • 1. 测试工具
  • 1.需求
  • 2. 实现

2.5.2 io_uring的使用场景

1. 测试工具

1.需求

  1. 基于tcp客户端
  2. -n是请求数量(req) -t是线程数量(threadnum) -c链接数量(connection)

    2. 实现

    这个实现难得能看明白
    ```cpp
    #include <stdio.h>
    #include <liburing.h> // io_uring 异步IO库
    #include <netinet/in.h> // 套接字相关结构体
    #include <string.h> // memset/memcpy
    #include <unistd.h> // close

/* 事件类型枚举 */
#define EVENT_ACCEPT 0 // 接受连接事件
#define EVENT_READ 1 // 读取数据事件
#define EVENT_WRITE 2 // 写入数据事件

/* 连接信息结构体(存储于user_data) */
struct conn_info {
int fd; // 关联的文件描述符
int event; // 事件类型
};

/* 初始化TCP服务器 */
int init_server(unsigned short port) {
// 创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 配置服务器地址
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有接口
serveraddr.sin_port = htons(port);              // 指定端口

// 绑定地址
if (bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1) {
    perror("bind failed");
    return -1;
}

// 开始监听(等待队列长度10)
listen(sockfd, 10);
return sockfd;

}

/* io_uring 参数 */
#define ENTRIES_LENGTH 1024 // 环队列大小
#define BUFFER_LENGTH 1024 // 读写缓冲区大小

/* 设置接收事件到io_uring */
int set_event_recv(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags) {
// 获取提交队列项(SQE)
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);

// 构造连接信息(存储到user_data)
struct conn_info info = {
    .fd = sockfd,
    .event = EVENT_READ
};

// 准备RECV操作(异步接收数据)
io_uring_prep_recv(sqe, sockfd, buf, len, flags);

// 将连接信息存入user_data(用于后续识别事件)
memcpy(&sqe->user_data, &info, sizeof(info));
return 0;

}

/* 设置发送事件到io_uring */
int set_event_send(struct io_uring *ring, int sockfd, void *buf, size_t len, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
struct conn_info info = {
.fd = sockfd,
.event = EVENT_WRITE
};

// 准备SEND操作(异步发送数据)
io_uring_prep_send(sqe, sockfd, buf, len, flags);
memcpy(&sqe->user_data, &info, sizeof(info));
return 0;

}

/* 设置接受连接事件到io_uring */
int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
struct conn_info info = {
.fd = sockfd,
.event = EVENT_ACCEPT
};

// 准备ACCEPT操作(异步接受连接)
io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);
memcpy(&sqe->user_data, &info, sizeof(info));
return 0;

}

int main(int argc, char *argv[]) {
// 初始化服务器套接字
unsigned short port = 9999;
int sockfd = init_server(port);

// 初始化io_uring参数
struct io_uring_params params;
memset(&params, 0, sizeof(params));

// 创建io_uring实例
struct io_uring ring;
io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);

// 准备接受连接的异步操作
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);

// 共享缓冲区(注意:多连接时存在竞争风险)
char buffer[BUFFER_LENGTH] = {0};

// 主事件循环
while (1) {
    // 提交所有准备好的SQE到内核
    io_uring_submit(&ring);
    
    // 等待至少一个完成事件(阻塞等待)
    struct io_uring_cqe *cqe;
    io_uring_wait_cqe(&ring, &cqe);
    
    // 批量获取完成事件(最多128个)
    struct io_uring_cqe *cqes[128];
    int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);//epoll_wait
    
    // 处理每个完成事件
    for (int i = 0; i < nready; i++) {
        struct io_uring_cqe *entry = cqes[i];
        struct conn_info result;
        memcpy(&result, &entry->user_data, sizeof(result));
        
        if (result.event == EVENT_ACCEPT) {
            // 处理新连接
            int connfd = entry->res; // accept返回的新fd
            
            // 重新注册ACCEPT事件(持续监听新连接)
            set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);
            
            // 注册新连接的读事件
            set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0);
            
        } else if (result.event == EVENT_READ) {
            // 处理读完成
            int ret = entry->res;
            
            if (ret <= 0) { // 连接关闭或错误
                close(result.fd);
            } else {        // 收到数据,准备回写
                set_event_send(&ring, result.fd, buffer, ret, 0);
            }
            
        } else if (result.event == EVENT_WRITE) {
            // 写完成,重新注册读事件
            set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);
        }
    }
    
    // 推进完成队列(标记已处理的事件)处理完就清空
    io_uring_cq_advance(&ring, nready);
}

}

### 3. 和epoll区别
epoll是发起读写请求,如果有就读写
io_uring是发起读写请求,如果可以读写数据直接就出来了,是异步的

数据包之间的性能差异大概差10%,io_uring更快

1. qps
2. 并发链接数量
3. 新建建链时间
4. 断链时间
上述三点去看server能力

## 2. 常见面试题
### 1. udp并发如何做
1. ​模拟TCP,使用多个端口:
可以为每个客户端请求创建一个新的UDP套接字,绑定到不同的端口,从而实现并发处理。这种方式类似于TCP的“短连接”,每次通信都使用一个新的端口。

2. ​多线程处理:
使用多线程来处理UDP请求。主线程负责接收数据包,然后将数据包分发给工作线程处理。这种方式可以充分利用多核CPU的性能

### 2. tcp与udp区别
1. ​连接方式:

TCP是面向连接的协议,通信前需要通过三次握手建立连接,通信结束后通过四次挥手断开连接。
UDP是无连接的协议,发送数据前不需要建立连接,直接发送数据包。
2. ​可靠性:

TCP提供可靠的数据传输,通过确认机制、重传机制等保证数据包的顺序和完整性。
UDP不保证数据包的可靠性,数据包可能会丢失、重复或乱序。
3. ​分包与粘包解决方案:

TCP基于字节流传输,可能会发生粘包或拆包问题。解决方法包括:
  - 在数据包中添加长度字段,接收方根据长度字段解析数据包。
  - 使用特殊分隔符(如\n)来区分数据包。
UDP基于数据报传输,每个数据包是独立的,不会发生粘包问题,但需要应用程序自己处理数据包的顺序和完整性。
4. ​并发处理:

TCP通过多线程或多进程处理并发连接,每个连接由一个独立的线程或进程处理。
UDP通过多线程、并发容器或SO_REUSEPORT实现并发处理。
5. ​使用场景:

TCP适用于对可靠性要求高的场景,如文件传输、网页浏览、电子邮件等。
UDP适用于对实时性要求高的场景,如视频流、在线游戏、DNS查询等。
6. ​连接类型:

TCP适合长连接,连接建立后可以持续通信。
UDP适合短连接,通常是一次性通信,如DNS查询

...全文
37 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

433

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧