多路選擇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;
}
相關文章
相關標籤/搜索