【unix】IO相关

IO

文件IO

文件 I/O 指的是对文件的输入/输出操作,就是对文件的读写操作
文件IO相关知识点

标准IO库

流和FILE对象

在文件IO中,所有I/O函数都是围绕文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。而对于标准I/O库,它们的操作时围绕流(stream)进行的,当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相关联。
当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。

#include <wchar.h>
//fwide函数:设置流的定向
int fwide(FILE *stream, int mode); 
//返回值:若流是宽定向的,返回正值;若流是字节定向的,返回负值;若流是未定向的,返回0
// 根据mode参数的不同值,fwide函数执行不同的工作。

// 如若mode参数值为负,fwide将试图使指定的流是字节定向的。
// 如若mode参数值为正,fwide将试图使指定的流是宽定向的。
// 如若mode参数为0,fwide将不试图设置流的定向,但返回表示该流定向的值。

标准IO缓冲

标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。它也对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦
标准 IO 提供三种缓冲:
全缓存 :当缓冲区被填满或出现特定的条件,才会刷新缓冲区
行缓存 :当输入输出遇到新行符( ‘\n’ ),就会刷新缓冲区
无缓存 :不进行缓存,直接刷新
通过 setbuf() 、 setvbuf()函数 可以设置打开文件的缓冲类型以及缓冲大小

改变缓冲类型

//修改缓冲区模式
void setbuf(FILE *stream,char *buf);
// stream : 流指针
// buf : 所指向的缓冲区大小
//setbuf(stdout,buf);
void setvbuf(FILE *stream,char *buf,int mode,size_t size)
// stream:流指针
// buf:所指向的缓冲区大小
// size:全缓存中设置需要缓存的空间大小
// mode:模式
// 1. 全缓存(_IOFBF):当填满i/o缓冲区后或者达到一定条件就执行fflush刷新操作;
// 2. 行缓存(_IOLBF):输入和输出中,当遇到'\n'时执行i/o操作,刷新传入内核;
// 3 .无缓存(_IONBF):标准i/o不对数据进行缓存,即全都传入内核;
// stevbu(fp,buf,_IONBF,sizeof(buf));  

其他

打开流 fopen..
关闭流 fclose..
读写流 getc..
二进制IO(读写二进制数据) fread..
定位流 ftell(文件位置存放在长整型中)..
临时文件 tmpnam..
内存流 fmemopen..

高级IO

阻塞式IO
非阻塞式IO
IO复用
信号驱动式IO
异步IO

阻塞式IO

readn wriden readline

raeden 从一个文件描述符中读取n字节
writen 往一个文件描述符写n字节
readline 从一个文件描述符读取文本行,一次1字节

ssize_t readen(int fileds , void * buff , size_t nbytes)
ssize_t writen(int fileds , const void * buff , size_t nbytes)
ssize_t readen(int fileds , void * buff , size_t maxlen)

fileds :文件描述符 , buff : 指向要读取/写入的地址 , nbytes : 读/写的长度 , maxlen : 读取文本行的最大长度
成功为读或者写的字节数 , 出错则为-1

recvmsg sendmsg

发送/接收数据

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

msghdr结构:
struct msghdr
{

    void *msg_name;           /* 指向地址结构 */
    int msg_namelen;          /* 地址结构长度 */
    struct iovec *msg_iov;    /* 数据 */
    int msg_iovlen;           /* 数据区个数 */
    void *msg_control;        /* 控制信息 */
    socklen_t msg_controllen; /* 控制信息缓冲区长度 */
    unsigned int msg_flags;   /* 接收信息的标志 */
};

缓冲区iovec结构:
struct iovec{
    void *iov_base; //指向缓冲区指针
    size_t iov_len; //缓冲区大小
};

控制信息msg_control由一个或多个辅助数据对象cmaghdr构成
cmsghdr结构:
struct cmsghdr{
    socklen_t cmsg_len;
    int cmsg_level;
    int cmsg_type;
};

msg_name 和 msg_namelen:这两个成员用于套接字未连接的场合(如 UDP 套接字)
类似于 recvfrom 和 sendto 的第五个和第六个参数:msg_name 指向一个套接字地址结构,调用者在其中存放接收者或发送者的协议地址。如果无需知名协议地址,msg_name 应为 NULL
msg_iov 和 msg_iovlen:输入或者输出缓冲区数组
只有 recvmsg 使用 msg_flags 成员。recvmsg 被调用时,flags 参数被赋值到 msg_flags 成员
而 sendmsg 则忽略 msg_flags 成员,因为它直接使用 flags 参数驱动发送处理过程
成功返回读入或者写出的字节数,出错返回 -1 并设置 errno

非阻塞式IO

实现非阻塞IO的两种方法
1.打开文件时添加O_NONBLOCK标志。
2.普通打开文件后,使用fcntl函数设置文件描述符的属性。

IO复用

IO复用模型的思路就是系统提供了一种函数(select/poll/epoll)可以同时监控多个fd的操作,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,如果select监听的fd都没有可读数据,select调用进程会被阻塞;而只要有任何一个fd准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应的线程此时再发起请求去读取内核中准备好的数据

select

使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常

int select(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);

maxfdp1:监视对象文件描述符数量。
readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值
writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值
exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值
timeout:等待所指定的描述符中的任何一个就绪可花多长时间

返回值 错误返回-1,超时返回0

pselect

int pselect(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout , const sigset_t *sigmask);

与select的区别
1.timeout可以到纳秒
2.sigmask : 一个指向信号掩码的指针,该参数允许程序先禁止递交某些信号

poll

与select类似,但是在处理流设备时可以提供额外信息

epoll

声明足够大的epoll_event结构体数组后,传递给epoll_wait函数,发生变化的文件描述符信息将被填入该数组。epoll_event结构体也可以在epoll例程中注册文件描述符,用于注册关注的事件

epoll用到的结构体

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
}

typedef union epoll_data
{
    void* ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
}epoll_data_t;
条件触发和边缘触发
epoll进行IO复用的基本原理我们可以概括如下:
1、 使用epoll_create()创建用来保存epoll文件的内存空间。
2、 使用epoll_ctl()在上文的内存空间中注册需要监视的文件描述符。
3、 使用epoll_wait()监视已经注册好的文件描述符,只要有描述符触发要读或写的epool_event,epoll_wait()就会返回,我们就可以进行后续的读写操作了。
条件触发和边缘触发就发生在第三步,是两种不同的触发epoll_event的方式

通过设置这个变量的events字段值,可以选择使用条件触发方式还是边缘触发方式

当服务端的socket接收到客户端传过来的数据时,数据先被缓存到socket的输入缓冲区。如果在条件触发的方式下只要数据缓冲区有数据,操作系统就会一直触发读的epool_event,直到我们把所有数据都读上来,缓冲区的数据为空为止。而在边缘触发的方式下,在socket的输入缓冲区的数据从0到有的这个时机才会触发一次读的epoll_event(或者说是每接收到客户端一次数据就触发一次),这就要求我们需要把输入缓冲区的所有数据一次性读完,直到下一次缓冲区数据从0到有再次触发epoll_event。

epoll_create

创建保存epoll文件描述符的空间

#include <sys/epoll.h>

int epoll_create(int size);

成功时返回epoll文件描述符,失败时返回-1。调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”,该文件描述符主要用于区分epoll例程,需要终止时,也要调用close函数;通过参数size传递的值并非用来决定epoll例程的大小,而仅供操作系统参考。
Linux 2.6.8之后的内核将完全忽略传入epoll_create函数的size参数,因为内核会根据情况调整epoll例程的大小。

epoll_ctl

利用epoll_ctl在epoll例程内注册监视对象文件描述符

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

成功时返回0,失败时返回-1。其中,epfd为用于注册监视对象的epoll例程的文件描述符;op用于指定监视对象的添加、删除或更改等操作;fd为需要注册的监视对象的文件描述符;event为监视对象的事件类型。

第二个参数传递的常量及含义:
EPOLL_CTL_ADD:将文件描述符注册到epoll例程
EPOLL_CTL_DEL:从epoll例程中删除文件描述符(此时第四个参数传递NULL,但Linux 2.6.9之前不行)
EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况
epoll_event中的成员events中可以保存的常量及其所指的事件类型:
EPOLLIN:需要读取数据的情况
EPOLLOUT:输出缓冲为空,可以立即发送数据的情况
EPOLLPRI:收到OOB数据的情况
EPOLLDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用
EPOLLERR:发生错误的情况
EPOLLET:以边缘触发的方式得到事件通知
EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件
epoll_wait

与select函数类似,等待文件描述符发生变化

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

成功时返回发生事件的文件描述符数,失败时返回-1。其中epfd为epoll例程的文件描述符;events为保存发生事件的文件描述符集合的结构体地址值,其所指缓冲需要动态分配;maxevents为第二个参数中可以保存的最大事件数;timeout是以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件

信号驱动式IO

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册信号函数,然后用户线程会继续执行,当内核数据就绪时会发送信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生什么事情

针对一个套接字使用信号驱动时式IO步骤:

  1. 建立SIGIO信号的信号处理函数
  2. 设置该套接字的属主(fcntl的F_SETOWN)
  3. 开启该套接字的信号驱动式IO(fcntl的F_SETFL命令打开O_ASYNC标志)

异步IO

进程通过系统调用告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行程序,当IO操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程操作结果

参考1
参考2

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值