多路选择I/O

多路选择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 != ‘\0‘; 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 ...\n");
       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\n");
                            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\n");
                                   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\n", 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;
}
时间: 2024-10-25 05:16:12

多路选择I/O的相关文章

屏蔽信号的多路选择I/O

前边提到了多路I/O的方法,这一章屏蔽信号的多路选择与之前的多路I/O一致,只是增加了屏蔽信号的作用.多路选择I/O中我们使用的是select函数,屏蔽信号的多路选择I/O使用的是pselect函数,与之前的函数相比,增加了一个参数可以用来屏蔽信号.具体函数如下所示: int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, cons

channler多路选择和超时控制

一.多渠道的选择和超时控制select{}1.多需渠道选择: 当任何一个case不处于阻塞情况会输出case内的逻辑, 当所有的都没有准备好都处于阻塞情况的话select出现defalt就会执行defalut内代码. select { case ret := <-retCh1: fmt.Println(ret) case ret := <--retCh2: fmt.Println(ret) default: t.Errorf("No one returned") }2.超时

php socket函数详解

转自:http://blog.163.com/[email protected]/blog/static/2889641420138213514298/ 最近在用socket实现服务端向客户端主动推送消息函数名 描述socket_accept() 接受一个Socket连接socket_bind() 把socket绑定在一个IP地址和端口上socket_clear_error() 清除socket的错误或最后的错误代码socket_close() 关闭一个socket资源socket_connec

很全的linux网络编程技巧

注:作者王晓,本人认为总结得很好,故记之,绝无侵权之意. 1. LINUX网络编程基础知识 1 1.1. TCP/IP协议概述 1 1.2. OSI参考模型及TCP/IP参考模型 1 1.3. TCP协议 3 1.4. UDP协议 5 1.5. 协议的选择 6 2. 网络相关概念 6 2.1. socket概念 7 2.2. socket类型 8 2.3. socket信息数据结构 8 2.4. 数据存储优先顺序的转换 8 2.5. 地址格式转化 9 2.6. 名字地址转化 10 3. sock

php的socket通信

对TCP/IP.UDP.Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵.那么我想问: 1.         什么是TCP/IP.UDP?2.         Socket在哪里呢?3.         Socket是什么呢?4.         你会使用它们吗? 什么是TCP/IP.UDP? TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网

HTTP协议及其请求头分析

众所周知,Internet的基本协议是TCP/IP协议,目前广泛采用的FTP.Archie Gopher等是建立在TCP/IP协议之上的应用层协议,不同的协议对应着不同的应用.  WWW服务器使用的主要协议是HTTP协议,即超文体传输协议.由于HTTP协议支持的服务不限于WWW,还可以是其它服务,因而HTTP协议允许用 户在统一的界面下,采用不同的协议访问不同的服务,如FTP.Archie.SMTP.NNTP等.另外,HTTP协议还可用于名字服务器和分布式对象管 理.  HTTP的早期版本为HT

二级C考点汇总

1.变量命名的合法性2.数据类型的转换,分为强类型和隐式类型3.字符串:字符串的声明.定义和使用,通常结合数组和指针 4.数组:下标的转换及数组的顺序存储5.函数:声明.定义.调用,递归函数(如菲薄纳西数列).函数指针(如回调函数)6.变量:局部.全局.静态变量,结合函数考察值传递和地址传递及其两种参数的传递方式 7.指针:结合函数.字符串.数组综合考察 切记指针必须赋值后才能使用而不像数组声明就可以使用了8.运算符的考察:运算符优先级.结合性和位运算符.逗号运算符及其他特殊运算符9.三种分支语

Savelog项目总结回忆

Savelog项目的细节已经不太记得,感觉有些遥远,需要翻回旧的笔记本电脑或者是旧的笔记本. 概述: 本项目采用的Linux C,监听一个或多个特殊的端口,当其中一个端口有发起连接时就产生一个新的线程,并将端口发来的数据进行检验,然后保存到日志,日志只保留最近7天的内容,连接的端口会不断的发送数据,大概每秒钟发送一个,发送成功或失败都要写入日志,成功用info标签,记录端口号,IP等,失败根据失败的内容检查,并给出相应的原因. 下面讲一些笔记记下来,是从刚开始学Linux开始的那段. 杂锦: l

springboot-thymeleaf

在上篇文章springboot(二):web综合开发中简单介绍了一下thymeleaf,这篇文章将更加全面详细的介绍thymeleaf的使用.thymeleaf 是新一代的模板引擎,在spring4.0中推荐使用thymeleaf来做前端模版引擎. thymeleaf介绍 简单说, Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完全替代 JSP .相较与其他的模板引擎,它有如下三个极吸引人的特点: 1.Thymeleaf 在有网络和无网络的环境下皆可