多路选择I/O

 

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

一、多路选择I/O的概念服务器

当用户须要从网络设备上读数据时,会发生的读操做通常分为两步。网络

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

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

通常的读操做形式为:函数

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

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

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

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

二、实现多路选择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);

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

第5个参数表示用户指望等待的时间。若是tvptr==NULL表示一直等。

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

在这个函数中第二、三、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;
}
相关文章
相关标签/搜索