文章链接:https://codemouse.online/archives/2020-03-27223443
AIO编程方法
对象
struct aiocb {
/* 下面所有字段依赖于具体实现 */
int aio_fildes; /* 文件描述符 */
off_t aio_offset; /* 文件偏移 */
volatile void *aio_buf; /* 缓冲区地址 */
size_t aio_nbytes; /* 传输的数据长度 */
int aio_reqprio; /* 请求优先级 */
struct sigevent aio_sigevent; /* 通知方法 */
int aio_lio_opcode; /* 仅被 lio_listio() 函数使用 */
/* Various implementation-internal fields not shown */
};
// 第五个参数普遍不使用,除非要让它在特定的cpu上运行。
struct sigevent {
int sigev_notify; //Notification type.
int sigev_signo; //Signal number.
union sigval sigev_value; //Signal value.
void (*sigev_notify_function)(union sigval); //Notification function.
pthread_attr_t *sigev_notify_attributes; //Notification attributes.
};
union sigval
{
int sival_int;
void *sival_ptr;
};
-
sigev_notify可用参数:
- SIGEV_NONE:空的提醒,事件发生时不做任何事情
- SIGEV_SIGNAL:向进程发送sigev_signo中指定的信号,具体详细的状况参照上面的文档,这涉及到sigaction的使用
- SIGEV_THREAD:通知进程在一个新的线程中启动sigev_notify_function函数,函数的实参是sigev_value,系统API自动启动一个线程,我们不用显式启动。
aio_error
- aio_error 函数被用来确定请求的状态。
int aio_error( struct aiocb *aiocbp );
-
返回值:
- EINPROGRESS,说明请求尚未完成。
- ECANCELLED,说明请求被应用程序取消了。
- -1,说明发生了错误,具体错误原因可以查阅 errno。
- 0 ,说明完成当前请求。
aio_return
- 异步 I/O 和标准块 I/O 之间的另外一个区别是我们不能立即访问这个函数的返回状态,因为我们并没有阻塞在 read 调用上。在标准的 read 调用中,返回状态是在该函数返回时提供的。但是在异步 I/O 中,我们要使用 aio_return 函数。这个函数的原型如下:
ssize_t aio_return( struct aiocb *aiocbp );
aio_read
- aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。
- aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下
int aio_read(struct aiocb *aiocbp);
aio_read demo
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
//aio操作所需结构体
struct aiocb rd;
int fd,ret,couter;
fd = open("test.txt",O_RDONLY);
if(fd < 0)
{
perror("test.txt");
}
//将rd结构体清空
bzero(&rd,sizeof(rd));
//为rd.aio_buf分配空间
rd.aio_buf = malloc(BUFFER_SIZE + 1);
//填充rd结构体
rd.aio_fildes = fd;
rd.aio_nbytes = BUFFER_SIZE;
rd.aio_offset = 0;
//进行异步读操作
ret = aio_read(&rd);
if(ret < 0)
{
perror("aio_read");
exit(1);
}
//do other things
couter = 0;
// 循环等待异步读操作结束
while(aio_error(&rd) == EINPROGRESS)
{
// printf("第%d次\n",++couter);
}
//获取异步读返回值
ret = aio_return(&rd);
printf("\n\n返回值为:%d\n",ret);
printf("%s\n",rd.aio_buf);
free(rd.aio_buf);
close(fd);
return 0;
}
aio_write
- aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,失败时返回值为 -1,并相应地设置 errno)。
- aio_write 函数用来请求一个异步写操作。其函数原型如下:
int aio_write(struct aiocb *aiocbp);
- 这与 read 系统调用类似,但是有一点不一样的行为需要注意。回想一下对于 read 调用来说,要使用的偏移量是非常重要的。然而,对于 write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。
aio_write demo
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
//定义aio控制块结构体
struct aiocb wr;
int ret,fd;
char str[20] = {"hello,world"};
//置零wr结构体
bzero(&wr,sizeof(wr));
fd = open("test.txt",O_WRONLY | O_APPEND);
if(fd < 0)
{
perror("test.txt");
}
wr.aio_buf = str;
//填充aiocb结构
wr.aio_fildes = fd;
wr.aio_nbytes = sizeof(str);
//异步写操作
ret = aio_write(&wr);
if(ret < 0)
{
perror("aio_write");
}
//等待异步写完成
while(aio_error(&wr) == EINPROGRESS)
{
printf("hello,world\n");
}
//获得异步写的返回值
ret = aio_return(&wr);
printf("\n\n\n返回值为:%d\n",ret);
return 0;
}
aio_cancel
函数允许我们取消对某个文件描述符执行的一个或所有 I/O 请求。
int aio_cancel(int fd, struct aiocb *aiocbp);
-
要取消一个请求,我们需要提供文件描述符和
aiocb
引用。如果这个请求被成功取消了,那么这个函数就会返回AIO_CANCELED
。如果请求完成了,这个函数就会返回AIO_NOTCANCELED
。 -
要取消对某个给定文件描述符的所有请求,我们需要提供这个文件的描述符,以及一个对
aiocbp
的NULL
引用。如果所有的请求都取消了,这个函数就会返回AIO_CANCELED
;如果至少有一个请求没有被取消,那么这个函数就会返回AIO_NOT_CANCELED
;如果没有一个请求可以被取消,那么这个函数就会返回AIO_ALLDONE
。我们然后可以使用aio_error
来验证每个 AIO 请求。如果这个请求已经被取消了,那么aio_error
就会返回-1
,并且errno
会被设置为ECANCELED
。
aio_suspend
- aio_suspend函数可以时当前进程挂起,直到有向其注册的异步事件完成为止
- 阻塞等待完成
- 所有aio完成后或者超时时间到了 才会返回
int aio_suspend(const struct aiocb *const cblist[],int n,const struct timespec *timeout);
-
参数:
第一个参数是个保存了aiocb块地址的数组,我们可以向其内添加想要等待阻塞的异步事件。
第二个参数为向cblist注册的aiocb个数。
第三个参数为等待阻塞的超时时间,NULL为无限等待
aio_suspend demo
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1024
int MAX_LIST = 2;
int main(int argc,char **argv)
{
//aio操作所需结构体
struct aiocb rd,wd;
int fd1,fd2,ret,couter;
//cblist链表
struct aiocb *aiocb_list[2];
fd1 = open("test1.txt",O_RDONLY);
if(fd1 < 0)
{
perror("test1.txt");
}
fd2 = open("test2.txt",O_WRONLY | O_APPEND);
if(fd2 < 0)
{
perror("test2.txt");
}
//将rd结构体清空
bzero(&rd,sizeof(rd));
//为rd.aio_buf分配空间
rd.aio_buf = malloc(BUFFER_SIZE + 1);
//填充rd结构体
rd.aio_fildes = fd1;
rd.aio_nbytes = BUFFER_SIZE;
rd.aio_offset = 0;
//将读fd的事件注册
aiocb_list[0] = &rd;
//进行异步读操作
ret = aio_read(&rd);
if(ret < 0)
{
perror("aio_read");
exit(1);
}
//将rd结构体清空
bzero(&wd,sizeof(rd));
//为rd.aio_buf分配空间
wd.aio_buf = malloc(BUFFER_SIZE + 1);
//填充rd结构体
wd.aio_fildes = fd1;
wd.aio_nbytes = BUFFER_SIZE;
//将读fd的事件注册
aiocb_list[1] = &wd;
//进行异步读操作
ret = aio_write(&wd);
if(ret < 0)
{
perror("aio_write");
exit(1);
}
printf("我要开始等待异步读事件完成\n");
//阻塞等待异步读事件完成
ret = aio_suspend(aiocb_list,MAX_LIST,NULL);
//获取异步读返回值
ret = aio_return(&rd);
printf("%s\n",rd.aio_buf);
printf("\n\nrd返回值为:%d\n",ret);
ret = aio_return(&wd);
printf("\n\nwd返回值为:%d\n",ret);
return 0;
}
lio_listio
aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio
这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作
int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);
第一个参数mode可以有俩个实参,LIO_WAIT和LIO_NOWAIT,前一个会阻塞该调用直到所有I/O都完成为止,后一个则会挂入队列就返回,通过回调函数或信号操作。
- LIO_WAIT 阻塞发起:阻塞等到所有发起的AIO全部完成后,才会返回。
- LIO_NOWAIT 非阻塞发起:发起后立即返回,通过绑定的信号来通知或者线程回调通知。
LIO_WAIT demo
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1025
int MAX_LIST = 2;
int main(int argc,char **argv)
{
struct aiocb *listio[2];
struct aiocb rd,wr;
int fd,ret;
//异步读事件
fd = open("test1.txt",O_RDONLY);
if(fd < 0)
{
perror("test1.txt");
}
bzero(&rd,sizeof(rd));
rd.aio_buf = (char *)malloc(BUFFER_SIZE);
if(rd.aio_buf == NULL)
{
perror("aio_buf");
}
rd.aio_fildes = fd;
rd.aio_nbytes = 1024;
rd.aio_offset = 0;
rd.aio_lio_opcode = LIO_READ; ///lio操作类型为异步读
//将异步读事件添加到list中
listio[0] = &rd;
//异步些事件
fd = open("test2.txt",O_WRONLY | O_APPEND);
if(fd < 0)
{
perror("test2.txt");
}
bzero(&wr,sizeof(wr));
wr.aio_buf = (char *)malloc(BUFFER_SIZE);
if(wr.aio_buf == NULL)
{
perror("aio_buf");
}
wr.aio_fildes = fd;
wr.aio_nbytes = 1024;
wr.aio_lio_opcode = LIO_WRITE; ///lio操作类型为异步写
//将异步写事件添加到list中
listio[1] = ≀
//使用lio_listio发起一系列请求
ret = lio_listio(LIO_WAIT,listio,MAX_LIST,NULL);
//当异步读写都完成时获取他们的返回值
ret = aio_return(&rd);
printf("\n读返回值:%d",ret);
ret = aio_return(&wr);
printf("\n写返回值:%d",ret);
return 0;
}
LIO_NOWAIT demo
当我们的异步I/O操作完成之时,我们可以通过信号通知我们的进程也可用回调函数来进行异步通知,接下来我会为大家主要介绍以下回调函数来进行异步通知。
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#include<unistd.h>
#define BUFFER_SIZE 1025
void aio_completion_handler(sigval_t sigval)
{
//用来获取读aiocb结构的指针
struct aiocb *prd;
int ret;
prd = (struct aiocb *)sigval.sival_ptr;
printf("hello\n");
//获取返回值
ret = aio_return(prd);
}
int main(int argc,char **argv)
{
int fd,ret;
struct aiocb rd;
fd = open("test.txt",O_RDONLY);
if(fd < 0)
{
perror("test.txt");
}
//填充aiocb的基本内容
bzero(&rd,sizeof(rd));
rd.aio_fildes = fd;
rd.aio_buf = (char *)malloc(sizeof(BUFFER_SIZE + 1));
rd.aio_nbytes = BUFFER_SIZE;
rd.aio_offset = 0;
//填充aiocb中有关回调通知的结构体sigevent
rd.aio_sigevent.sigev_notify = SIGEV_THREAD;//使用线程回调通知
rd.aio_sigevent.sigev_notify_function = aio_completion_handler;//设置回调函数
rd.aio_sigevent.sigev_notify_attributes = NULL;//使用默认属性
rd.aio_sigevent.sigev_value.sival_ptr = &rd;//在aiocb控制块中加入自己的引用
//异步读取文件
ret = aio_read(&rd);
if(ret < 0)
{
perror("aio_read");
}
printf("异步读以开始\n");
sleep(1);
printf("异步读结束\n");
return 0;
}