浅谈I/O模型

发表于 3年以前  | 总阅读数:322 次

作为程序员,在日常工作中,都或多或少的接触过网络I/O这个概念,接触过网络编程,听说过socket等等,但是对于更深层次的理解,多少还是有点欠缺,通过本文,可以了解网络中最重要的模块I/O,以及对几种网络模型的介绍,在我们日常工作开发过程中,可以针对特定需求,选择特定的网络模型,达到事半功倍的效果。


0

什么是I/O

通常指数据,在内部存储器和外部存储器或其他周边设备之间的输入和输出。

是信息处理系统(例如计算器)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。该术语也可以用作行动的一部分;到“运行I/O”是运行输入或输出的操作。

Unix 系统下,不论是标准输入还是借助套接字接受网络输入,都有两个步骤:

  1. 等待数据准备好(Waiting for the data to be ready)
  2. 从内核向进程复制数据(Copying the data from the kernel to the process)

输入/出设备是硬件中由人(或其他系统)使用与计算器进行通信的部件。例如,键盘或鼠标是计算器的输入设备,而监视器和打印机是输出设备。计算器之间的通信设备(如电信调制解调器和网卡)通常运行输入和输出操作。 简单来说,就是用户进程与内核交互,而内核与硬件进行交互

1

阻塞式I/O模型

应用程序发起I/O系统调用,在获得结果之前,应用程序进程会一直阻塞,直到获得结果(有数据返回或者操作超时)。

默认情况下,Unix系统上的所有文件描述符都以“阻塞模式”开始。这意味着read、write或connect之类的I/O系统调用在默认情况下,都是阻塞的。

为了理解这一点,我们假如有个程序,在终端上等待标准输入(stdin),此时,假如通过调用read函数来实现该功能,那么该程序将被阻塞,直到有实际的数据可用(例如当用户在键盘上敲入字符时)。具体来说,内核将把进程置于“休眠”状态,直到数据在stdin上可用。其他类型的文件描述符也是如此。例如,如果您尝试从TCP套接字读取数据,那么read调用将阻塞,直到连接的另一端实际发送数据为止。

int main(int argc, char *argv[]) {
    char buf[ MAX_BUFFER_LENGTH ];
    int length = 0;
    if( (length = read( 0, buf,  MAX_BUFFER_LENGTH )) < 0 ) {
        return -1;
    }
    buf[length] = '\0';
    printf("input: \n%s\n", buf);
    return 0;
}

当在我们执行了上述代码,那么,在该执行代码的进程内,会调用read函数,最终会进入kernel态,此时,会进入kernel态的第一个步骤即I/O等待数据状态。

从用户进程的角度来说,会被阻塞。直到超时或者键盘输入了数据,从kernel态将数据拷贝到了用户态的内存,此时用户进程才接触阻塞,程序开始执行下面其他步骤。

特点:

用户进程会一直阻塞等待kernel,直到kernel将数据返回

2

非阻塞式I/O模型

通常通过将socket描述符设置为O_NONBLOCK模式。

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

如果一个socket描述符被设置为非阻塞的,那么在数据准备好之前,调用read函数,会返回-1,而errno会被设置为EWOULDBLOCK。

从上图可以看出,当在用户进程调用read系统调用之后,如果kernel态没有数据,那么read调用会马上返回,而不会阻塞用户进程。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。

整个过程,可以概括为,用户进程不断的调用read系统调用,询问kernel数据是否准备好,所以,非阻塞式I/O模式可以理解为是一个不断循环询问kernel的模式。

struct timespec sleep_interval{.tv_sec = 0, .tv_nsec = 1000};
ssize_t nbytes;
for (;;) {
    /* try fd1 */
    if ((nbytes = read(fd1, buf, sizeof(buf))) < 0) {
        if (errno != EWOULDBLOCK) {
            perror("read/fd1");
        }
    } else {
        handle_data(buf, nbytes);
    }
    /* try fd2 */
    if ((nbytes = read(fd2, buf, sizeof(buf))) < 0) {
        if (errno != EWOULDBLOCK) {
            perror("read/fd2");
        }
    } else {
        handle_data(buf, nbytes);
    }
    /* 处理其他事情 */
    // do other
}

非阻塞式I/O较阻塞式I/O来说,性能提升了很多,但仍然存在很多问题,比如:

1、当数据输入非常慢时,程序会频繁而不必要地唤醒,从而浪费CPU资源。

2、当数据进入时,如果程序处于睡眠状态或者正在处理其他逻辑,它可能不会立即读取数据,因此程序的延迟将很差。

3、用这种模式处理大量的文件描述符将变得很麻烦。

特点:

1、用户进程会不断的询问kernel数据是否已经准备好

2、抽象的讲,非阻塞I/O与异步I/O类似,区别是一个不断的去轮询kernel,一个是通过被动通知的方式。

3

信号驱动式I/O模型

当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

信号驱动式I/O在TCP中用处不大,这是因为该信号在TCP套接字中产生的过于频繁。

以下条件均会导致对一个TCP套接字产生SIGIO信号:

  • 监听套接字上某个连接请求已经完成;
  • 某个断连请求已经发起;
  • 某个断连请求已经完成;
  • 某个连接对端已经关闭;
  • 数据到达套接字;
  • 数据已经从套接字发送走;
  • 发生某个异步错误。

当然,我们可以对TCP监听套接字可以使用SIGIO,这样我们就可以在信号处理函数中处理新连接了。

对于UDP,只有以下两个条件才会产生SIGIO信号:

  • 数据报到达套接字;
  • 套接字上发生异步错误。

所以,针对UDP套接字产生的SIGIO信号,我们只要调用recvfrom读入到达的数据,或者获取发生的异步错误就可以了。

void io_handler(int signal) {
  int       numbytes;  /* Number of bytes recieved from client */
  int       addr_len;  /* Address size of the sender    */
  struct sockaddr_in   their_addr;  /* connector's address information  */

  if ((numbytes=recvfrom(sock, buf, MAXBUFLEN, 0, \
                    (struct sockaddr *)&their_addr, &addr_len)) == -1) {
                perror("recvfrom");
                exit(1);
   }

  buf[numbytes]='\0';
  printf("got from %s --->%s \n  ",inet_ntoa(their_addr.sin_addr),buf);
  return;
}
int main() {
  int length;
  struct sockaddr_in server;

  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if (sock < 0) {
    perror("opening datagram socket");
    exit(1);
  }
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = INADDR_ANY;
  server.sin_port = htons(MYPORT);
  if (bind(sock, (struct sockaddr *)&server, sizeof server) <0 ){
    perror("binding datagram socket");
    exit(1);
  }
  length = sizeof(server);
  if (getsockname(sock, (struct sockaddr *)&server, &length) < 0){
    perror("getting socket name");
    exit(1);
  }
  printf("Socket port #%d\n", ntohs(server.sin_port));
  // 第一步,注册事件函数
  signal(SIGIO,io_handler);
  // 第二步 设置要接收的进程id或进程组id,通知其自己的进程id或进程的挂起输入组id
  if (fcntl(sock,F_SETOWN, getpid()) < 0){
    perror("fcntl F_SETOWN");
    exit(1);
  }
  // 第三步,允许接收异步I/O信号
  if (fcntl(sock,F_SETFL,FASYNC) <0 ){
    perror("fcntl F_SETFL, FASYNC");
    exit(1);
  }
  for(;;)
  ;
  // .......
  }

该模式比较复杂,在实际使用中不是很多,在内核2.6中才开始引入。

4

异步I/O模型

同步I/O意味着当您想读或写某个东西时,可能需要调用一个名为read()或write()的函数,函数会阻塞,阻止执行进一步移动,直到读或写完成。这就是普通文件读写的典型工作方式。打开一个文件,然后调用read(),它用所需的数据填充一个缓冲区,并在完成所有操作后返回,这样就可以用所需的数据填充一个缓冲区。

异步I/O恰恰相反。与读写函数等待请求的操作完成后再返回不同,异步I/O操作将立即返回到程序,而读写操作将在后台继续。

这有什么好处?这意味着你的程序或游戏可以继续扔东西在屏幕上,更新输入,滚动进度条,无论什么,而所有的硬盘驱动器的数据处理你想要的。您还可以向系统发送多个IO请求,这样操作系统就可以找到访问所有所需数据的最有效方法。

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

在异步IO中,以下几个概念非常重要:

struct aiocb {
  int             aio_fildes;     /* 文件描述符 */
  off_t           aio_offset;     /* 文件便宜 */
  volatile void  *aio_buf;        /* buffer位置 */
  size_t          aio_nbytes;     /* 传输数据大小 */
  int             aio_reqprio;    /* 请求优先级 */
  struct sigevent aio_sigevent;   /* 通知的方式 */
  int             aio_lio_opcode; 
};

aio_read()

函数告诉系统要读取的文件、开始读取的偏移量、要读取的字节数以及要将要读取的字节放在何处。

aio_error()

检查IO请求的当前状态。使用这个函数你可以查出请求是否成功。你所要做的就是给它一个地址,地址和你给aio\u read()的地址相同。如果请求成功完成,则函数返回0;如果请求仍在工作,则返回EINPROGRESS;如果发生错误,则返回其他错误代码。

aio_return()

检查IO请求的结果,一旦您发现请求已经完成。如果请求成功,此函数返回读取的字节数。如果失败,那么函数返回-1。

下面是异步I/O模型的一个简单例子,通过本例,可以简单的了解该模型的大致流程。

int main(){
  int file = open("blah.txt", O_RDONLY, 0);

  if (file == -1)
  {
    cout << "Unable to open file!" << endl;
    return 1;
  }

  char* buffer = new char[SIZE_TO_READ];

  // 定义控制块变量
  aiocb cb;

  memset(&cb, 0, sizeof(aiocb));
  cb.aio_nbytes = SIZE_TO_READ;
  cb.aio_fildes = file;
  cb.aio_offset = 0;
  cb.aio_buf = buffer;

  // 读取数据
  if (aio_read(&cb) == -1)
  {
    cout << "Unable to create request!" << endl;
    close(file);
  }

  cout << "Request enqueued!" << endl;

  // 等待,知道请求处理完成
  while(aio_error(&cb) == EINPROGRESS)
  {
    cout << "Working..." << endl;
  }

  // 判断读取的字节数
  int numBytes = aio_return(&cb);

  if (numBytes != -1)
    cout << "Success!" << endl;
  else
    cout << "Error!" << endl;

  // 释放资源
  delete[] buffer;
  close(file);

  return 0;
}

特点:

1、用户程序告诉kernel其要执行某个操作,不等kernel回复就立即返回

2、kernel完成整个操作,包括将获取的数据拷贝到用户的buffer之后,再通知用户。

5

I/O多路复用

I/O多路复用是这样一种能力,它告诉内核,如果一个或多个I/O条件已经就绪,比如输入已经准备好被读取,或者描述符能够获取更多的输出,我们就需要得到通知。

I/O复用模型使用select、poll、epoll函数,这些函数也会阻塞进程,但与阻塞I/O不同的是,这两个函数可以同时阻塞多个I/O操作。对于多个读操作、多个写操作,可以同时检测I/O函数,直到有数据可读或可写时,才实际调用I/O操作函数。

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

int         maxfdp1, stdineof;
fd_set      rset;
char        buf[MAXLINE];
int     n;
stdineof = 0;
FD_ZERO(&rset);
for ( ; ; ) {
    if (stdineof == 0)
        FD_SET(fileno(fp), &rset);
    FD_SET(sockfd, &rset);
    maxfdp1 = max(fileno(fp), sockfd) + 1;
    select(maxfdp1, &rset, NULL, NULL, NULL);
    if (FD_ISSET(sockfd, &rset)) {  /* socket is readable */
        if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
            if (stdineof == 1)
                return;     /* normal termination */
            else
                err_quit("str_cli: server terminated prematurely");
        }
        write(fileno(stdout), buf, n);
    }
    if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
        if ( (n = read(fileno(fp), buf, MAXLINE)) == 0) {
            stdineof = 1;
            shutdown(sockfd, SHUT_WR);  /* send FIN */
            FD_CLR(fileno(fp), &rset);
            continue;
            }
        writen(sockfd, buf, n);
    }
}

IO多路复用适用如下场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口)

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4)如果一个服务器即要处理TCP,又要处理UDP

(5)如果一个服务器要处理多个服务或多个协议

1、多路复用模式有select、poll以及epoll函数,每个函数的性能特点以及开发难以程度各不同,需要根据实际需求,择优选择。

2、现在基本上所有的商用或者大型程序,都是用的多路复用与非阻塞两个模式相结合的方式

参考资料

https://fwheel.net/aio.html

https://www.itzhai.com/articles/it-seems-not-so-perfect-signal-driven-io.html

https://eklitzke.org/blocking-io-nonblocking-io-and-epoll

https://notes.shichao.io/unp/ch6/

END

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Y-sIjpopxbr8rblUQ5Xmfw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237229次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录