select服务器的实现

来源:互联网 发布:java float和Float 编辑:程序博客网 时间:2024/06/05 10:21

select函数是来实现多路I/O复用输入/输出模型。比纯粹的阻塞I / O模型更具有实用性,因为程序句柄会停在select这里等待,直到被监视的文件句柄至少有一个发生了状态改变。

这里写图片描述

select函数的功能和调用顺序

使用select函数时可以将多个文件描述符集中到一起统一监视,项目如下:

1、是否存在套接字接受数据?2、无需阻塞传输数据的套接字有那些?3、那些套接字发生了异常?

select的函数调用方法和顺序如下:

这里写图片描述

测试代码

server:

// I/O多路转接之select实现#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<string.h>#include<stdlib.h>#include<fcntl.h>#include<sys/select.h>#define SIZE sizeof(fd_set)*8  //定义fds大小int fds[SIZE]; int startup(const char *ip,int port){    int sock = socket(AF_INET,SOCK_STREAM,0);  //调用socket创建监听socket    if(sock<0)    {        perror("socket");        exit(2);    }    struct sockaddr_in local;  //本地地址信息结构体    local.sin_family = AF_INET; //使用IPV4进行通信    local.sin_port = htons(port);//获取端口    local.sin_addr.s_addr = inet_addr(ip);//获取ip地址    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)    {//调用bind绑定地址        perror("bind");        exit(3);    }    if(listen(sock,10)<0)    { //调用listen开始监听   10代表listen队列中等待的连接数        perror("listen");        exit(4);    }    return sock;}static void usage (const char* proc){    printf("Usage:%s[local_ip] [local_port]\n",proc);}int main(int argc,char* argv[]){    if(argc!=3)    {        usage(argv[0]);        return 1;    }    int listen_sock = startup(argv[1],atoi(argv[2]));    int nums = sizeof(fds)/sizeof(fds[0]);    int i=0;    for(;i<nums;i++)    {        fds[i]=-1;    }    fds[0]=listen_sock;    while(1)    {        int max_fd=-1;        FD_ZERO(&rfds);//清空        fd_set rfds;        for(i=0;i<nums;i++)        {            if(fds[i]>0)            {                FD_SET(fds[i],&rfds);                if(max_fd<fds[i])                {                    max_fd = fds[i];                }            }        }        struct timeval timeout = {1,0};   //设置为{0,0} 非阻塞状态        switch(select(max_fd+1,&rfds,NULL,NULL,&timeout))// 调用select函数        {          case 0:            printf("timeout...\n");            break;          case -1:            perror("select");            break;          default:            {                //at least one fd ready!               for(i=0;i<nums;i++)               {                   if(fds[i]==-1){                       continue;                   }                   if(i==0&&FD_ISSET(listen_sock,&rfds)) //检测是否有新的请求                   {//listen_sock               struct sockaddr_in client;               socklen_t len = sizeof(client);                      //调用accept,返回服务器与客户端连接的new_sock描述符                      int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);                       if(new_sock<0)                      {                          perror("accept");                          continue;                      }                      printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));                   int j=1;                   //将新的客户端加入数组                   for(;j<nums;j++)                   {                       if(fds[j]==-1)                       {                           break;                       }                   }                   if(j==nums)                   {                       printf("client is full\n");                       close(new_sock);                       continue;                   }else                   {                       fds[j]=new_sock;                   }                   }else if(i!=0&&FD_ISSET(fds[i],&rfds))                   {//nomal fd ready!                      char buf[1024];                       ssize_t s = read(fds[i],buf,sizeof(buf)-1);                       if(s>0)                       {                           buf[s]=0;                           printf("client# %s\n",buf);                       }else if (s==0)                       {                           close(fds[i]);                           fds[i]=-1;                           printf("client is quit!\n");                       }else{                           perror("read");                           close(fds[i]);                           fds[i]=-1;                       }                   }else                    {                   }               }            }            break;        }    }    return 0;}

client:

#include <stdio.h>#include <sys/types.h>        #include <sys/socket.h>#include <string.h>#include <stdlib.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, const char *argv[]){    int sockfd;    struct sockaddr_in server_addr;    char buf[100] = {0};    int n = 0;    int len = sizeof(struct sockaddr_in);    if(argc != 3)  // ./a.out  ip  port    {        fprintf(stderr,"Usage : %s ip port!\n",argv[0]);            return -1;    }    //1.创建用于通信的文件描述符    sockfd = socket(AF_INET,SOCK_STREAM,0);    if(sockfd < 0)    {        perror("Fail to socket");           return -1;    }    //2.填充服务器ip和port    server_addr.sin_family = AF_INET;    server_addr.sin_port = htons(atoi(argv[2])); //port    server_addr.sin_addr.s_addr = inet_addr(argv[1]); //ip    //3.向服务器发起连接请求    if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)    {        perror("Fail to connect");          return -1;    }    //4.发送消息    while(1)    {        bzero(buf,sizeof(buf));        putchar('>');        fgets(buf,sizeof(buf),stdin);        buf[strlen(buf) - 1] = '\0'; //'\n'--->'\0'        n = send(sockfd,buf,strlen(buf),0);        if(n < 0)        {            printf("Fail to send");             return -1;        }        if(strncmp(buf,"quit",4) == 0)            break;        printf("send %d bytes : %s\n",n,buf);    }    return 0;}

测试结果

这里写图片描述

select服务器优缺点

优点

(1)select()的可移植性更好;
(2)select()对于超时值提供了更好的精度。
(3)不需要建立多个线程、进程就可以实现一对多的通信。
(4)可以同时等待多个文件描述符,效率比起多进程多线程来说要高很多。

缺点

(1)每次调用选择,都需要把FD集合从用户态拷贝到内核态,这个开销在FD很多时会很大 ;
(2)同时每次调用选择都需要在内核遍历传递进来的所有的FD,这个开销在FD很多时也很大 ;
(3)select支持的文件描述符数量有上限,默认是1024。