多路选择i/o

杨静远 杨静远     2022-08-29     596

关键词:

 

多路选择I/O提供另一种处理I/O的方法,相比于传统的I/O方法,这种方法更好,更具有效率。多路选择是一种充分利用系统时间的典型。

1、多路选择I/O的概念

当用户需要从网络设备上读数据时,会发生的读操作一般分为两步。

(1)等待数据准备好,等待数据的到达,并且将其复制到内核的缓冲区,该缓冲区在系统态。

(2)复制数据,将数据从内核缓冲区中复制到用户指定的缓冲区中。

一般的读操作形式为:

 Int nbytes = read(sfd, buf, MAX); 

如果需要的数据没有准备好,例如,数据尚未到达时,read函数发生阻塞,直到所有的数据到达,read函数才将其复制到用户指定的缓冲区,并且返回。如果数据一直未到达,那么read函数将一直阻塞下去,该进程会陷入僵尸状态、这种I/O模型称为阻塞I/O。

为了防止I/O阻塞使进程进入僵死状态,可以使用多路选择I/O。

这种方法的思想是先构造一张需要读取文件描述符的表,调用一个函数轮循这个表中的文件描述符,知道有一个文件描述符可以读写该函数才返回,多路选择I/O需要使用两个系统调用,一个负责检查并返回可用的文件描述符;另一个负责对该文件描述符进行读写。

2、实现多路选择I/O

Linux环境下使用select函数实现多路选择I/O,函数原型如下:

 Int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr); 

头文件: #include <sys/select.h> 

参数说明:

第一个参数maxfdp1表示所关心状态的描述符的个数,正确解释是最大描述符加1。如果maxfdp1的值是2,表示用户关心的描述符数为2;最大的文件描述符为1时,描述符0,和1都会被该函数茶韵,大于1则不关心。一个进程最多可以用于1024个文件描述符,因此maxfdp1的值为(0~1023);

第2、3、4这3个参数是readfds、writefds和exceptfds,分别表示用户关心的可读、可写和异常的各个描述符,这3个参数是3个位向量,每一位对应一个文件描述符的状态,每一位对应一个文件描述符的状态。

第5个参数表示用户期望等待的时间。如果tvptr==NULL表示一直等。

返回值:出错返回-1,返回0表示没有设备准备好,返回值大于0表示准备好的设备数目。

在这个函数中第2、3、4这三个参数是特殊的参数,这三个参数是fd_set数据类型的,fd_set本质上市一个位向量,是一个无符号的整型。其中每一位代表一个设备的状态,如果是1表示被设置,如果是0表示没有被设置。上边的的三个参数分别代表的是可读、可写和异常三种状态,这三个位向量中的每一位代表一个状态。比如readfds是“111000”表示前3个文件可读,后三个文件不可读。

最后通过一个实例来演示使用select函数同时处理多个连接请求的服务器端程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "iolib.h"
#define MAX_LINE 80
int port = 8000;

void my_fun(char *p)
{
       if(p == NULL)
              exit(1);
       for(; *p != ; p++)
              if(*p >= A && *p <= Z)
                     *p = *p - A + a;
}
 
int main(void)
{
       struct sockaddr_in sin;
       struct sockaddr_in cin;
       int lfd;
       int cfd;
       int sfd;
       int rdy;
       int client[FD_SETSIZE];          ///客户端连接的套接字描述符数组
       int maxi;
       int maxfd;                                  ///最大连接数
       fd_set rest;
       fd_set allset;
       socklen_t addr_len;                     ///地址结构的长度
       char buf[MAX_LINE];
       char addr_p[INET_ADDRSTRLEN];
       int i, n;
       int len;
       int opt = 1;                         ///套接字选项
       bzero(&sin, sizeof(sin)); ///填充地址结构
       sin.sin_family = AF_INET;
       sin.sin_addr.s_addr = INADDR_ANY;
       sin.sin_port = hton(port);
        /*创建一个面向连接的套接字*/
       lfd = socket(AF_INET, SOCK_STREAM, 0);
       if(lfd == -1)
       {
              perror("call to sock");
              exit(1);
       }
 
       /*设置套接字选项,使用默认选项*/
       setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 
       /*绑定套接字到地址结构*/
       n = bind(lfd, (struct sockaddr_in *) &sin, sizeof(opt));
       if(n == -1)
       {
              perror("call to bind");
              exit(1);
       }
 
       /*开始监听连续请求*/
       n = listern(lfd, 20);
       if(n == -1)
       {
              perror("call to listern");
              exit(1);
       }
       printf("Accepting connecting ...
");
       maxfd = lfd;          ///对最大文件描述符进行初始化
       maxi = -1;
       for(i = 0; i < FD_SETSIZE; i++)     ///初始化客户端连接描述符集合
              client[i] = -1;
       FD_ZERO(&allset);               ///清空文件描述符集合
       FD_SET(lfd, &allset);             ///将监听接字设置在集合内
      
       /*开始服务器程序的死循环*/
       while(1)
       {
              rset = allset;
              /*得到当前可以读的文件描述符*/
              rdy = select(maxfd + 1, &rset, NULL, NULL, NULL);
              if(FD_ISSET(lfd, &rest))
              {
                     addr_len = sizeof(cin);
                     /*创建一个链接描述符*/
                     cfd = accept(lfd, (struct sockaddr_in *) &cin, &addr_len);
                     if(cfd == -1)
                     {
                            perror("fail to accept");
                            exit(1);
                     }
                     /*查找一个空闲的位置*/
                     for(i = 0; i < FD_SETSIZE; i++)
                     {
                            if(client[i] < 0)
                            {
                                   client[i] = cfd;
                                   break;
                            }
                     }
                     /*太多的客户端连接,服务器拒绝连接,跳出循环*/
                     if(i == FD_SETSIZE)
                     {
                            printf("too many clients
");
                            exit(1);
                     }
                     FD_SET(cfd, &allset);            ///设置连接集合
                     if(cfd > max_fd)
                            maxfd = cfd;
                     if(i > maxi)
                            maxi = i;
                     if(--rdy <= 0)
                            continue;
              }
 
              for(i = 0; i < maxi; i++)        ///对每一个连接描述符做处理

              {

                     if((sfd = client[i] < 0))

                            continue;

                     if(FD_ISSET(sfd, &rset))
                     {
                            n = my_read(sfd, buf, MAX_LINE);      ///读取数据
                            if(n == 0)
                            {
                                   printf("the other side has been closed
");
                                   fflush(stdout);        ///刷新到输出终端
                                   close(sfd);
                                   FD_CLR(sfd, &allset);    ///清空连接描述符数组
                                   client[i] = -1;
                            }
                           else
                            {
                                   inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p));
                                   printf("client IP is %s, port is %d
", addr_p, ntohs(cin.sin_port));
                                   my_fun(buf);
                                   n = my_write(sfd, buf, len + 1);
                                   if(n == -1)
                                          exit(1);

                            }
                            if(--rdy <= 0)
                                   break;
                     }
              }
       }
       close(lfd);
       return 0;
}

 

一文读懂i/o多路复用技术(代码片段)

...响应任何连接请求。针对这种困境的一个解决办法就是I/O多路复用技术。基本思路就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。–《UNIX网络编程》我们以书中... 查看详情

i/o多路复用-epoll探索

什么是I/O多路复用I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个IO能够读写,通知程序进行相应的读写操作。I/O多路复用的场合1、当客户处理多个描述字时(通常是交互式输入和网络套接字),必须使用I/O复用... 查看详情

网络i/o模型--05多路复用i/o(代码片段)

多路复用I/O模型在应用层工作效率比我们俗称的BIO模型快的本质原因是,前者不再使用操作系统级别的“同步I/O。”模型。在Linux操作系统环境下,多路复用I/O模型就是技术人员通常简称的NIO技术。多路复用I/O目前具体的实现... 查看详情

i/o多路复用是什么?(i/omultiplexing)

看eventloop循环看到个I/O多路复用,不知道是什么GMainLoop的实现原理和代码模型对Sock(I/O流)的处理:第一种方法就是最传统的多进程并发模型(每进来一个新的I/O流会分配一个新的进程管理。)第二种方法就是I/O多路复用(单... 查看详情

i/o多路复用

Linux下实现I/O复用的系统调用方式主要:select、poll、epoll。Linux下实现I/O复用的系统调用方式主要:select、poll、epoll。 查看详情

五种i/o模式——阻塞(默认io模式),非阻塞(常用语管道),i/o多路复用(io多路复用的应用场景),信号i/o,异步i/o

五种I/O模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O五种I/O模式:【1】        阻塞I/O          ( 查看详情

i/o多路复用-select(代码片段)

目录I/O的分类阻塞I/O非阻塞I/O信号驱动I/O多路复用I/Oselectselect的参数select的返回值select版本的服务器select的特点缺点优点基础的I/O可以看看之前的一篇文章Linux操作系统-系统级IO_TangguTae的博客-CSDN博客一次I/O的过程可以分为等待&... 查看详情

一文读懂i/o多路复用技术(代码片段)

前言当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在键盘上... 查看详情

超详细的i/o多路复用概念常用i/o模型系统调用等介绍(代码片段)

概述当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在键盘上... 查看详情

java基础:i/o多路复用模型及linux中的应用(代码片段)

IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么、其核心设计思想又是什么、其在Linux中是如何实现的?I/O模型I/O模型主要有以下五种:同步阻塞I/O:I/O操作将同步阻塞用户线程同步非... 查看详情

i/o多路复用——poll

 本文出自“zgw285763054”博客,请务必保留此出处http://zgw285763054.blog.51cto.com/11591804/1857072 查看详情

i/o多路复用和异步i/o

一、I/O模式  对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:等待数据准备(Wait... 查看详情

i/o多路转接复用机制---select,poll,epoll

...         阻塞I/O         非阻塞I/O         多路复用I/O         信号驱动I/O          异步I 查看详情

i/o多路复用之select

  在Linux下有五种I/O模型,分别为:阻塞、非阻塞、信号驱动、复用I/O和异步I/O.  而在复用I/O中,比较常见的就是select、poll和epoll.  本文主要介绍select模型.一、select用法  #include<sys/select.h>intselect(intnfds,fd_set*readfds,fd_set... 查看详情

阻塞i/o非阻塞i/o和i/o多路复用

一、阻塞I/O首先,要从你常用的IO操作谈起,比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。阻塞的意思,就是一直等着。阻塞I/O就是等着... 查看详情

什么是redisi/o多路复用?

介绍Redis的源代码十分适合阅读和分析,其中I/O多路复用(mutiplexing)部分的实现非常干净和优雅,在这里想对这部分的内容进行简单的整理。几种I/O模型为什么Redis中要使用I/O多路复用这种技术呢?首先,Redis是跑在单线程中的... 查看详情

i/o多路复用——select

系统提供select函数来实现多路复用I/O模型,select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。selectAPI:650)this.width=650;"src="http://s2.... 查看详情

动图了解i/o多路复用

啥叫I/O多路复用?epoll又是个什么东西?你或许看过很多文章,但是还是感觉云里雾里的,今天,我们抛开文字,释放动图,或许你就理解了。I/O多路复用通常的一次的请求过程如下图所示:但是,服务器往往不会只处理一次请... 查看详情