上一次我们用epoll实现了reactor模式,这一次我们用io_uring来实现Proactor模式的tcp服务器。我们已经可以达到用一个线程去处理大量的连接。但此时这个线程每处理一个I/O,线程会一直等待(就是我们常说的阻塞I/O或者同步I/O下边有解释),是不是有点傻瓜?是否可以让线程多工作,别傻傻的等待。当然可以!这就是今天我们所要介绍的目前常用的Proactor模式。
阻塞I/O(同步I/O):I/O操作按照顺序执行,并且每一步都需要等待上一步完成。
非阻塞I/O(异步I/O):不需要等待特定任务完成,可以并行执行其他任务,并通过回调函数或事件通知机制处理结果。
举个例子,你想要出去买份饭带回家吃,假设只有这有两种方式。第一种方式这时候你可以先在手机上下单,然后等一段时间后去饭店拿饭,等待的期间呢,你可以去洗衣服(处理其他事情),这种就是非阻塞I/O。
还有一种方式就是,你直接去饭店,到那之后跟老板说要一份饭,打包带走,到饭做完打包好的期间呢,你不能去做其他事情,只能在饭店傻傻等待,这种就类似于阻塞I/O。
Proactor和Reactor的区别:
Proactor和Reactor是两种常见的并发模式,用于处理I/O操作的异步编程。
Reactor模式基于事件驱动,在一个主循环中等待多个I/O事件,并根据不同类型的事件调用相应的处理程序。Reactor负责监听和分发事件,但实际的I/O操作仍然由应用程序自己完成。
Proactor模式则更进一步,它将实际的I/O操作也交给框架来完成,应用程序只需提供数据和回调函数。在Proactor模式下,当一个I/O操作完成时,框架会自动触发相应的回调函数进行后续处理。
因此,区别可以总结如下:
- Reactor模式中,应用程序负责执行实际的I/O操作;而Proactor模式中,框架负责执行实际的I/O操作。
- Reactor模式是同步IO(阻塞IO);而Proactor模式是异步IO(非阻塞IO)。
- Reactor模式中,应用程序需要主动检查事件并进行处理;而Proactor模式中,框架会主动通知应用程序。
选择使用哪种模式取决于具体需求和场景。如果需要更大程度上解放应用程序对I/O操作的控制,并充分利用异步非阻塞特性,则可以考虑使用Proactor模式。如果对于同步IO已经能够满足需求,或者对于I/O操作的控制要求更高,则可以选择Reactor模式。
//io_uring中的liburing库安装可以参考这篇文章
io_uring之liburing库安装_liburing 需要安装吗-CSDN博客
//编译 需要从gitlab上下载liburing库
gcc -o uring_tcp_server uring_tcp_server.c -lpthread -I /home/xxxx(看你自己的安装路径)/liburing/src/include/liburing -luring
实现代码:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <liburing.h>
#include <netinet/in.h>
#include <unistd.h>
//io_uring实现tcp服务器
#define EVENT_ACCEPT 0
#define EVENT_READ 1
#define EVENT_WRITE 2
struct conn_info
{
int fd;
int event;
};
int init_server(unsigned short port)
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_port = htons(port);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(-1 == bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr)))
{
perror("bind");
return -1;
}
listen(sockfd,10);
return sockfd;
}
#define ENTRIES_LENGTH 1024
#define BUFFER_LENGTH 1024
int set_event_recv(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 accept_info ={
.fd = sockfd,
.event = EVENT_READ,
};
io_uring_prep_recv(sqe,sockfd,buf,len,flags);
memcpy(&sqe->user_data,&accept_info,sizeof(struct conn_info));
}
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 accept_info ={
.fd = sockfd,
.event = EVENT_WRITE,
};
io_uring_prep_send(sqe,sockfd,buf,len,flags);
memcpy(&sqe->user_data,&accept_info,sizeof(struct conn_info));
}
int set_event_accept(struct io_uring *ring,int fd,struct sockaddr *addr,
socklen_t *addrlen,int flags)
{
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
struct conn_info accept_info ={
.fd = fd,
.event = EVENT_ACCEPT,
};
io_uring_prep_accept(sqe,fd,(struct sockaddr*)addr,addrlen,flags);
memcpy(&sqe->user_data,&accept_info,sizeof(struct conn_info));
}
//实现tcpserver
int main(int argc,char *argv[])
{
unsigned short port = 9999;
int sockfd = init_server(port);
struct io_uring_params params;
memset(¶ms,0,sizeof(params));
struct io_uring ring;
io_uring_queue_init_params(ENTRIES_LENGTH,&ring,¶ms);
//struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
#if 0
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
accept(sockfd,(struct sockaddr*)&clientaddr,&len);
#else
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
set_event_accept(&ring,sockfd,(struct sockaddr*)&clientaddr,&len,0);
// struct conn_info accept_info ={
// .fd = sockfd,
// .event = EVENT_ACCEPT,
// };
// io_uring_prep_accept(sqe,sockfd,(struct sockaddr*)&clientaddr,&len,0);
// memcpy(&sqe->user_data,&accept_info,sizeof(struct conn_info));
#endif
char buffer[BUFFER_LENGTH] = {0};
while(1)
{
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring,&cqe);
struct io_uring_cqe *cqes[128];
int nready = io_uring_peek_batch_cqe(&ring,cqes,128);//epoll_wait
int i = 0;
for(i=0;i<nready;i++)
{
struct io_uring_cqe *entries = cqes[i];
struct conn_info result;
memcpy(&result,&entries->user_data,sizeof(struct conn_info));
if(result.event == EVENT_ACCEPT)
{
set_event_accept(&ring,sockfd,(struct sockaddr*)&clientaddr,&len,0);
printf("set_event_accept\n");
int connfd = entries->res;
set_event_recv(&ring,connfd,buffer,BUFFER_LENGTH,0);
}
else if(result.event == EVENT_READ)
{
int ret = entries->res;
printf("set_event_recv ret:%d,buffer:%s\n",ret,buffer);
if(ret == 0)
{
close(result.fd);
}
else if(ret > 0)
{
set_event_send(&ring,result.fd,buffer,ret,0);
}
}
else if(result.event == EVENT_WRITE)
{
int ret = entries->res;
printf("set_event_send ret:%d,buffer:%s\n",ret,buffer);
set_event_recv(&ring,result.fd,buffer,BUFFER_LENGTH,0);
}
}
io_uring_cq_advance(&ring,nready);
}
}