select网络服务器和五种IO模型

在冯诺依曼体系结构下,输入输出是重要的两部分。当然,在网络中,输入输出也是重要的两环。通常,在网络通信的效率中,IO的比重占的很大。

通常情况下,IO分为两步,第一步是等,第二步是搬迁数据。搬迁数据的时间是一定的,但是,等的时间是可以改变的。我们要想提高IO的效率,就必须提高等的效率,即降低等的比重。

1. IO模型的分类

首先IO模型分为两大类,一类是同步IO,另一类是异步IO;

同步IO包括四类:阻塞IO,非阻塞IO,信号驱动IO,多路转接IO

(1)阻塞IO

在内核将数据准备好之前,系统调用会一直等待,即操作系统将进程挂起来。所有的套接字默认方式下都是阻塞方式。

(2)非阻塞IO

如果内核没有将数据准备好,系统调用仍然会直接返回去做别的事情,并且返回EWOULDBLOCK错误码。

非阻塞IO往往需要程序员循环的方式反复去读写文件描述符,不断去检测是否有事件就绪。

(3)信号驱动IO

用户在代码中设置信号处理程序,在内核中有数据来时,内核发送SIGIO信号通知应用程序进行IO操作。

(4)多路转接IO

多路转接IO最本质的地方在于能同时等待多个文件描述符就绪。

异步IO

在内核中有数据来临时,直接将数据在内核拷贝,然后交给应用程序。

2. 认识几个函数

(1)fcntl 函数

函数原型:

                      

fcntl函数的5种功能:

复制一个现有的描述符(cmd=F_DUPFD);

获得/设置文件描述符标记(cmd=FGETFD或FSETFD);

获得/设置文件状态标记(cmd=FGETFL或FSETFL);

获得/设置异步IO所有权(cmd=FGETOWN或FSETOWN);

获得/设置记录锁(cmd=FGETLK或FSETLKW);

(2)重定向dup/dup2函数

函数原型:利用重定向这个特点,可以改变文件描述符的指向及复制文件描述符。

           

注:dup分配文件描述符是从最小的开始分配。dup2是将新文件描述符拷贝到旧文件描述符上去。

3. I/O多路转接之select

(1)select函数

函数原型:

        

函数功能:监视多个文件描述符的状态变化,在IO中负责IO的第一步——等,等待事件的就绪。

函数参数:参数nfds表示要监视的文件描述符的最大值+1;

                  中间三个参数表示三个事件集,分别是读事件集,写事件集,异常事件集。

                  timeout的取值来表示select等的方式。

函数返回值:

执行成功返回文件描述符状态改变(事件就绪)的个数。

执行失败返回-1,错误原因存于错误码。

如果返回0,表示timeout时间一到,超时返回。

注意:使用select多路转接时,我们需要一个数组来保存我们所要关心的文件描述符,在调用select之前,需要对fd_set进行重新设置,依据就是数组。

(2)关于fd_set

fd_set是一个输入输出型参数,当输入参数时,表示我要告诉操作系统我所关心哪些文件描述符的哪个事件;当输出参数时,表示操作系统告诉我我所关心的文件描述符的什么事件就绪了。

关心哪个事件,就将对应的参数设置为事件集,如果不关心,直接置NULL。

fd_set底层是一个位图,此位图对应的下标表示文件描述符,内容表示是否关心此文件描述符。

fd_set的大小由系统决定,是有上限的,所以表示的文件描述符也是有上限的

由于fd_set也是系统维护,所以要将文件描述符的状态设置进位图,需要系统调用:

                          

(3)关于timeval


timeval结构用于设置时间长度,有两个数量级——秒和毫秒。

(4)关于timeout取值

timeout取值有三种:

NULL,表示select阻塞式的等待;

0,仅检测文件描述符的状态,然后立即返回,并不等待事件发生;

特定时间值,如果在时间段里没有事件发生,则超时返回。

4. 套接字socket就绪条件

读就绪

(1)socket内核中,接收缓冲区的字节数大于等于低水位标记(数据数量一定时),此时可以无阻塞的读,并且返回值大于0。

(2)socketTCP通信中,对端关闭连接,此时读,返回0.

(3)  监听socket上有新的连接请求。

  (4)  socket上有未处理的错误。

写就绪

(1) socket内核中,发送缓冲区的空闲位置大于等于低水位时,可以无阻塞的写,并且返回值大于0;

(2)socket的写操作被关闭,触发SIGPIPE信号。

(3)socket使用connect连接时。

(4)socket上有未读取的错误。

异常就绪

socket上收到带外数据。

5. select网络服务器的实现

1 #include <stdio.h>                                                          
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <unistd.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 #include <stdlib.h>
  8 #include <sys/select.h>
  9 #include <string.h>
 10 
 11 #define MAX_FD 1024
 12 #define INIT_DATA -1
 13 void Initfdarray(int fd_array[],int num)         //数组初始化
 14 {
 15     int i=0;
 16     for(i=0;i<num;i++)
 17     {
 18         fd_array[i]=INIT_DATA;
 19     }
 20 }
22 int startup(int port)//创建监听套接字
 23 {
 24     int sock=socket(AF_INET,SOCK_STREAM,0);
 25     if(sock<0)
 26     {
 27         perror("socket");
 28         exit(2);
 29     }
 30     int opt=1;
 31     setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
 32 
 33     struct sockaddr_in local;
 34     local.sin_family=AF_INET;
 35     local.sin_addr.s_addr=htonl(INADDR_ANY);
 36     local.sin_port=htons(port);
 37 
 38     if(bind(sock,(struct sockaddr*)&local,sizeof(struct sockaddr_in))<0)
 39     {
 40         perror("bind");
 41         exit(3);
                             
 42     }
 43     if(listen(sock,5)<0)
 44     {
 45         perror("listen");
 46         exit(4);
 47     }
 48     return sock;
 49 }
50 int AddsockToArray(int fd,int array[],int num)     //将要关心的文件描述符保存在数组中
 51 {
 52     int i=0;
 53     for(;i<num;i++)
 54     {
 55         if(array[i]==INIT_DATA)
 56         {
 57             array[i]=fd;
 58             return i;
 59         }
 60     }
 61     return -1; //full
 62 }
63 int AddfdArrayTofdset(int array[],int num,fd_set* rfds)      //将所要关心的文件描述符添加到fd_set里面
 64 {
 65     int i=0;
 66     int Maxfd=INIT_DATA;
 67     for( i=0;i<num;i++)
 68     {
 69         if(array[i]>INIT_DATA)
 70         {
 71             FD_SET(array[i],rfds);
 72             if(Maxfd<array[i])
 73                 Maxfd=array[i];
 74         }
 75     }
 76     return Maxfd; 
 77 }
 78 void service_select(int arr[],int num,fd_set *rfds)               //IO服务函数
 79 {
 80     int i=0;
 81     for(i=0;i<num;i++)
 82     {
 83         if(arr[i]>INIT_DATA)
 84         {
 85             int fd=arr[i];
 86                  if(i==0&&FD_ISSET(arr[i],rfds))                //监听套接字绪
 87           {
 88               //listen_sock
 89               struct sockaddr_in client;                        //建立连接
 90               int size=sizeof(client);
 91     
 92               int sock=accept(fd,(struct sockaddr*)&client,&size);
 93               if(sock<0)
 94               {
 95                   perror("accept");
 96                   continue;
 97               }              
 98               printf("get a connect:%d",ntohs(client.sin_port));
 99              int ret= AddsockToArray(sock,arr,num);
100                 if(ret==-1)
101                  close(sock);
102 
103           }
104           else if(i!=0&&FD_ISSET(arr[i],rfds))       //其他套接字读写数据
105           {
106               //nomal fd
107 
108                   printf("please read!\n");
109               char buf[1024];
110               ssize_t s=read(fd,buf,sizeof(buf)-1);
111               if(s>0)
112               {
113                   printf("please read!!\n");
114                   buf[s]=0;
115                   printf("client> %s\n",buf);
116               }
117               else if(s==0)
118               {
119                   printf("read finish!");
120                   close(fd);
121                   arr[i]=INIT_DATA;
122               }
123               else
124               {
125                   perror("read");
126                   close(fd);
127                   arr[i]=INIT_DATA;
128 
129               }
130 
131           }
132 
133           else
134           {
135               //perror FD_ISSET
136               //do nothing
137           }
138 
139 
140         }
141 
142     }
143 }
144 int main(int argc,char*argv[])
145 {
146     if(argc!=2){
147         printf("Usage:%s[port]\n",argv[0]);
148         return 1;
149     }
150     int listen_sock=startup(atoi(argv[1]));
151 
152     int fd_list[MAX_FD];
153     int size=sizeof(fd_list)/sizeof(fd_list[0]);
154     Initfdarray(fd_list,size);
155     AddsockToArray(listen_sock,fd_list,size);
156 
157     fd_set rfds;
158     for(;;){
159 
160     FD_ZERO(&rfds); 
161     int maxfd=AddfdArrayTofdset(fd_list,size,&rfds);
162     int ret=select(maxfd+1,&rfds,NULL,NULL,NULL);
163     switch(ret)
164     {
165         case -1:
166           {
167               perror("select");
168               break;
169           }
170         case 0:
171           
172          {
173           printf("timeout");
174           break;
175          }
176 
177         default:
178          {
179              service_select(fd_list,size,&rfds);
180              break;
181          }
182         }
183     
184   }

6. 总结select的特点

(1)select上可监控的文件描述符个数取决于sizeof(fd_set)的大小,是有上限的。

(2)将要关心的fd加入到select监控集的同时,还需要一个数组来保存fd,并且数组需要自己维护。

       一是用于select返回后,数组作为原数据和fd_set进行FD_ISSET 判断,看是否是自己关心的文件描述符或者是哪个。

      二是select返回后,会把以前加入的但是没有事件就绪的fd清空,则每次开始select之前都要重新从array中取得fd加入到fd_set中。

7. select的优缺点

优点:

select的优点是相对于多线程多进程来讲的,效率较高。

缺点:

select的缺点是相对于poll和epoll来讲的:

(1)每次调用select,都需要手动设置fd集合,使用不便。

(2)每次调用select,都需要把fd集合从用户态拷贝到内核态,开销较大。

(3)每次调用select,都需要在内核遍历传递进去的fd,开销较大。

(4)select支持的文件描述符数量有上限。


猜你喜欢

转载自blog.csdn.net/qq_37954088/article/details/80571511