IO模块(同步异步,多路复用)

一、IO分类:

  1. 阻塞和非阻塞IO

在发起读取文件时,应用层会调用系统内核IO接口。

 

阻塞型IO,系统调用之后,应用层则被挂起,直到等到系统内核从磁盘读取完数据并返回给应用层之后,应用层才继续其他操作;

非阻塞型IO,系统调用之后,系统内核会立即返回,应用层并不会被挂起,它可以做其他任意想做的操作。

  1. 同步IO/异步IO

 

同步和异步的区别在于,系统读取完数据之后如何返回给应用层。

对于同步型IO,应用层需要不断向系统内核询问,如果数据没有读取完毕,应用层根据阻塞和非阻塞类型,要么挂起要么去做其他任意的事;如果读取完毕,当应用层再次询问时,系统内核则将数据返回给应用层,应用层获得数据。(系统内核被动返回读取信息)

对于异步型IO,应用层不需要向系统内核进行询问,在系统内核读取完成之后,会主动通知应用层数据读取完毕,应用层即可接收系统内核返回的数据。(系统内核主动返回读取信息)

 

二、Reactor模式和Proactor模式

Reactor模式(基于同步IO)

  1. 线程等待读取socket数据,将socketfd添加到事件分派器中,如添加到epoll
  2. 事件分派器阻塞等待socketfd可读事件发生
  3. 若数据到达,socketfd变成可读状态,事件分派器通知线程处理
  4. 线程阻塞完成socket读数据

 

Proactor模式(基于异步IO)

  1. 线程等待读取socket数据,将存储事件的缓冲区和读事件请求交给事件分派器;
  2. 事件分派器等待socket数据到达;
  3. 若数据到达,事件分派器不通知线程读取,二是直接完成数据的读取;
  4. 通知线程数据读取完成,并已经存入提供的缓冲区中。

    区别:Reactor是数据可读的时候通知线程,Proactor是数据读取完成之后通知线程

 

三、三种IO复用类型

  1. Select系统调用

#include<sys/select.h>

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds,struct timeval* timeout);

#nfds表示监听的文件描述符总数;

#readfds,writefds,execptfds分别表示对应的fd_set类型的集合

可以如下定义:fd_set readfds,writefds,execptfds

#timeout表示select函数的超时时间

Struct timeval

{

Long tv_sec;

Long tv_usec;

}

如果timeval成员变量均为0,则select立即返回;如果timeout设置为NULL,则select将一直阻塞,直到某个文件描述符就绪。

# FD_ZERO(fd_set *fdset);清楚fdset的所有位,如FD_ZERO(&readfds);

#FD_SET(int fd,fd_set* fdset);设置fdset的位,也就是将某个文件描述符加入到fdset中,如FD_SET(0,&readfds),将标准输入加入到fdset中

#int FD_ISSET(int fd,fd_set * fdset);测试fdset的某个位是否被设置,也就是测试文件描述符中的fd是否有事件发生

   #select成功时返回就绪(可读,可写和异常事件)文件

  1. Poll系统调用

#include<poll.h>

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

#fds是pollfd类型的数组

 Struct pollfd

{

int fd;//文件描述符

int events;//注册该文件描述符要监听的事件

short revents;//由内核修改,判断该文件描述符实际发生的事件

}

#nfds表示文件描述符数组的大小

#timeout指定poll的超时值;当timeout位-1时,poll调用将永远阻塞;当timeout为0时,poll调用将立即返回。

例:

pollfd fds[2];//pollfd的数组

fds[0].fd=0;//注册标注输入

fds[0].events=POLLIN;//监听输入事件

fds[0].revents=0;//初始化

fds[1].fd=sockfd;//注册网络socket文件描述符

fds[1].events=POLLIN | POLLRFHUP;//监听

fds[1].revents=0;

int ret=poll(fds,2,-1);

#poll调用成功时返回就绪(可读,可写和异常事件)文件

  1. Epoll系统调用

#include<sys/epoll.h>

int epoll_create(int size);

#epoll把文件描述符放在内核的一个时间表中

#改函数注册内核事件表需要创建多大size

#返回值作为内核事件表的索引

 

#include<sys/epoll.h>

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

#在一段时间内timeout等待一组文件描述符上的事件

#epfd表示上面注册的内核事件表的索引

#events用于存储内核事件表中就绪的事件,将内核事件表中就绪的事件复制到events中,events只用来输出epoll_wait检测到的就绪事件

#maxevents指定epoll_wait最大监听多少个事件

#timeout

#返回值表示就绪的文件描述符的个数,且就绪的文件描述符就存储于events中,直接遍历events[0,ret-1]

# struct epoll_event

{

_uint32_t events;

//表示注册的文件描述符索要监听的事件,与pollfd结构体中events类似(E)

epoll_data_t data;

}

#typedef union epoll_data//联合体

{

void *ptr;

int fd;//最常用fd,指向目标文件描述符

unint32_t u32;

uint64 u64;

}

#include<sys/epoll.h>

int epoll_ctl(int epfd,int op,struct epoll_event *event);//操作内核事件表

  • op选项:EPOLL_CTL_ADD,往事件表中注册fd事件

       EPOLL_CTL_MOD,修改fd上的注册事件

       EPOLL_CTL_DEL,删除fd上注册的事件

event表示对内核事件表进行操作的具体情况

   #向内核事件表中注册事件

void addfd(int epollfd,int fd)//epollfd表示内核事件表索引fd表示事件描述符

{

epoll_event event;

event.events=EPOLLIN;

event.data.fd=fd;//将文件描述符添加到内核事件表中

epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);

}

例:

epoll_event events[MAX_EVENT_NUMBER];

int epollfd=epoll_create(5);//注册大小为5的内核事件表

assert(epollfd!=-1);

addfd(epollfd,int fd);//将文件描述符fd添加到内核事件表

while(1)

{

int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1)

for(int i=0;i<number;i++)

{

//遍历监听到的事件

int sockfd=events[i].data.fd;

if(sockfd==listenfd)

//对于socket,如果是监听端口有事件到来,则创建新的连接

{

  struct sockaddr_in client_address;

  socklen_t client_addrlength=sizeof(client_address);

int connfd=accept(listenfd,(struct sockaddr*) &client_address,&client_addrlength) ;//从监听端口中取出事件

addfd(epollfd,connfd);//将事件注册到内核事件表中

}

else if(events[i].events & EPOLLIN)//表示客户端有数据发送到服务端

{

//接收客户端的数据

}

 

}

}

 

posted @ 2019-10-26 12:04  玉雪纷飞  阅读(723)  评论(0编辑  收藏  举报