I/O多路转接之select

在完成I/O操作时,程序中完成真正I/O的时间可能只有少的一部分,而大部分时间都处于一个等的时间。比如,此时需要从一个套接字中读取数据read(socket, buf, BUFSIZE); 这个操作可能会一直阻塞,直到有数据从网络的另一端发送过来。等的时间过于长,这是I/O效率低下的真正原因。可能有人会提出让代码不要阻塞的等,可以进行非阻塞的等待,比如当read一个socket发现没有数据时,就不在等待,而去read其他的socket,进行一种轮询式的read。可是这种模式还是会进行read的这个操作,不过这时进行操作时不成功的话就去read其他socket,效率还是低下的。

为了解决上述问题,提出了I/O多路转接。它的做法是这样的,一次等多个文件描述符,当有一个或者多个文件描述符就绪,可以进行I/O操作时,便返回通知有哪些那些文件描述符可以I/O。

系统提供了select函数实现多路复用输入/输出模型,select系统调用可以监视多个文件描述符的状态变化。程序会停在select这里等待,直到被监视的文件描述符至少有一个的状态发生了变化。

函数的定义:

参数描述:
  • nfds:要关心的文件描述符
  • readfds:表示要监视文件描述符集中,所有文件描述符的读状态
  • writefds:表示要监视文件描述符集中,所有文件描述符的写状态
  • exceptfds:表示要监视文件描述符集中,所有文件描述符的异常状态
  • timeout:监视多长时间,
  1. 当timeout被设置为0,表示以非阻塞方式等待;
  2. 当timeout被设置为大于0的数字,则表示其等待时间,有秒和毫秒的区分
                     struct timeval
                    {
                         long tv_sec;//秒数
                         long tv_usec;//微秒数
                    }
  1. 当timeout设置为NULL时,表示已阻塞方式等待。 

返回值:
  1. 当监视的文件描述符集中有文件描述符就绪,则会返回一个大于0的数
  2. 当监视的文件描述符没有任何一个就绪时,并且指定的时间已到,返回0
  3. 当函数调用出错时,返回-1        

fd_set:
select()机制中提供一fd_set的 数据结构,可以理解为一个集合,实际上是一个位图,每一个特定位来标志相应大小文件描述符,这个集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成。
系统提供了四个宏函数对fd_set进行操作

FD_CLR用于将fd_set中fd对应的位关闭。
FD_ISSET判断fd是否在fd_set中
FD_SET将fd添加进fd_set中
FD_ZERO将fd_set清空

基于select实现的网络服务器和客户端
server:

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/select.h>
#include<error.h>
#include<unistd.h>
#include<sys/types.h>
#define NUMS 1024
static void Usage(char* proc)
{
    printf("Usage: %s [local_ip] [local_port]\n", proc);
}

int startup(char* ip, int port)
{
    //创建文件特性
    //AF_INET ipv4,SOCK_STREAM,基于字节流服务
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return 2;
    }
    printf("sock =  %d\n", sock);

    struct sockaddr_in local;
    //确定地址协议类型
    local.sin_family = AF_INET;
    //绑定端口
    local.sin_port = htons(port);
    //绑定ip
    local.sin_addr.s_addr = inet_addr(ip);
    //绑定网络特性
    if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind");
        return 3;
    }
    //监听套接字
    if (listen(sock, 10) < 0)
    {
        perror("listen");
        return 4;
    }
    return sock;
}

//初始化文件描述符数组 
void Init(int *fds)
{
    int i = 0;
    for (; i < NUMS; i++)
    {
        fds[i] = -1;
    }
}

//将文件描述符数组所存储的文件描述符设置进文件描述符集
int Addfd(int *fds, fd_set *set)
{
    int i = 0;
    int maxfd = fds[0];
    for (; i < NUMS; i++)
    {
        if (fds[i] != -1)
        {
            FD_SET(fds[i], set);
            if (maxfd < fds[i])
            {
                maxfd = fds[i];
            }
        }
    }
    //返回最大的文件描述符
    return maxfd;
}
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 5;
    }

    //获取监听到的套接字
    int listen_sock = startup(argv[1], atoi(argv[2]));

    //创建一个辅助的空间(一个数组)用于保存 文件描述符信息
    //fds中保存的文件描述符将会被监听读状态
    int fds[NUMS];
    //wfd中保存的文件描述符将会被监听写状态
    int wfd[NUMS];
    //创建文件描述符集
    fd_set set;
    fd_set wset;
    //将数组初始化
    Init(fds);
    Init(wfd);
    //监听listen_sock添加进数组
    fds[0] = listen_sock;
    printf("listen_sock:%d\n", fds[0]);

    while (1)
    {
        //初始化文件描述符集
        FD_ZERO(&set);
        FD_ZERO(&wset);
        int maxrfd = -1;
        int maxwfd = -1;
        //将存于数组中的文件描述符添加至文件描述符集
        maxrfd = Addfd(fds, &set);
        maxwfd = Addfd(wfd, &wset);
        struct timeval timeread = { 2, 0 };
        struct timeval timewrite = { 0, 0 };
        if (maxrfd < maxwfd)
            maxrfd = maxwfd;
        //监视set中的读状态信息,监视wset中的写状态信息
        int rres = select(maxrfd + 1, &set, &wset, NULL, &timeread);
        switch (rres)
        {
            case -1:
            {
                   perror("selete");
                   break;
            }
            case 0:
            {
                  printf("Timeout...\n");
                  break;
            }
            default:
            {//至少有一个文件描述符状态就绪
                   int i = 0;
                   for (; i < NUMS; i++)
                   {
                       //此条件满足,说明客户端的连接已经就绪
                       if (i == 0 && fds[i] != -1 && \
                           FD_ISSET(fds[i], &set))
                       {
                           struct sockaddr_in client;
                           socklen_t len = sizeof(client);
                           //接受监听到的套接字
                           int new_sock = accept(listen_sock, \
                               (struct sockaddr*)&client, &len);

                           printf("client [%s] [%d]\n", \
                               inet_ntoa(client.sin_addr), \
                               ntohs(client.sin_port));
                           if (new_sock < 0)
                           {
                               perror("accept");
                               continue;
                           }
                           //连接套接字后,将其添加到数组中,他将被监听读状态就绪
                           for (i; i < NUMS; i++)
                           {
                               if (fds[i] == -1)
                               {
                                   fds[i] = new_sock;
                                   break;
                               }
                           }
                       }
                       //监视到有文件描述符的读状态就绪,就可以执行读操作
                       else if (i != 0 && fds[i] != -1 && FD_ISSET(fds[i], &set))
                       {
                           char buf[1024];
                           //从套接字读取信息到buf中
                           ssize_t s = read(fds[i], buf, sizeof(buf)-1);
                           if (s > 0)
                           {
                               buf[s] = 0;
                               printf("client#  %s\n", buf);
                               int j = 1;
                               //从这个文件描述符中读取数据后,就可以监视它的写状态
                               for (; j < NUMS; j++)
                               {
                                   if (wfd[j] == -1)
                                   {
                                       wfd[j] = fds[i];
                                       fds[i] = -1;
                                       break;
                                   }
                               }
                           }
                           else if (s == 0)
                           {
                               close(fds[i]);
                               printf("客户端已经退出!\n");
                               fds[i] = -1;
                           }
                           else
                           {
                               perror("read");
                               close(fds[i]);
                               fds[i] = -1;
                           }
                       }
                       //监视到有文件描述符的写状态就绪,就可以进行写操作
                       if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset))
                       {
                           char buf[1024];
                           printf("Please Enter# ");
                           fflush(stdout);
                           //从键盘输入信息到buf中
                           ssize_t _s = read(0, buf, sizeof(buf)-1);
                           if (_s > 0)
                           {
                               buf[_s - 1] = 0;
                               //发送信息到套接字
                               write(wfd[i], buf, strlen(buf));
                               //写完之后,就可等待其回复,监视其读状态
                               int j = 0;
                               for (; j < NUMS; j++)
                               {
                                   if (fds[j] == -1)
                                   {
                                       fds[j] = wfd[i];
                                       wfd[i] = -1;
                                       break;
                                   }
                               }
                           }
                           else
                           {
                               perror("read");
                               close(wfd[i]);
                               wfd[i] = -1;
                               break;
                           }

                       }
                   }
            }
        }
    }
    return 0;
}
client:

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<sys/types.h>
#define NUMS 1024
static void Usage(char *proc)
{
    printf("Usage %s [server_ip] [server_port]\n", proc);
}

void Init(int *fds)
{
    int i = 0;
    for (; i < NUMS; i++)
    {
        fds[i] = -1;
    }
}

int Addfd(int *fds, fd_set *set)
{
    int i = 0;
    int maxfd = fds[0];
    for (; i < NUMS; i++)
    {
        if (fds[i] != -1)
        {
            FD_SET(fds[i], set);
            if (maxfd < fds[i])
            {
                maxfd = fds[i];
            }
        }
    }
    return maxfd;
}
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return 2;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    //将创建的套接字,连入指定的网络服务中,这里连到了服务器
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        perror("connect111");
        return 3;
    }
    //创建一个辅助的空间(一个数组)用于保存 文件描述符信息
    //wfd中保存的文件描述符将会被监听写状态
    int wfd[NUMS];
    //rfd中保存的文件描述符将会被监听读状态
    int rfd[NUMS];
    //初始化数组
    Init(wfd);
    Init(rfd);
    //客户端先写,所以将sock添加进wfd中
    wfd[0] = sock;
    //创建文件描述符集
    fd_set rset;
    fd_set wset;
    char buf[1024];
    while (1)
    {
        //清空文件文件描述符集
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        int maxrfd = -1;
        int maxwfd = -1;
        //将存于数组中的文件描述符添加至文件描述符集
        maxrfd = Addfd(rfd, &rset);
        maxwfd = Addfd(wfd, &wset);
        struct timeval timeread = { 2, 0 };
        struct timeval timewrite = { 0, 0 };
        if (maxrfd < maxwfd)
            maxrfd = maxwfd;
        
        //监视rset中的读状态信息,监听wset中的写状态信息
        int res = select(maxrfd + 1, &rset, &wset, NULL, &timeread);
        switch (res)
        {
        case -1:
        {
                   perror("selete");
                   break;
        }
        case 0:
        {
                  printf("Timeout...\n");
                  break;
        }
        default:
        {
                   int i = 0;
                   for (; i < NUMS; i++)
                   {
                       //写状态就绪
                       if (wfd[i] != -1 && FD_ISSET(wfd[i], &wset))
                       {
                           printf("Please Enter# ");
                           fflush(stdout);
                           //从键盘写入内容到缓冲区
                           ssize_t s = read(0, buf, sizeof(buf)-1);
                           if (s >0)
                           {
                               buf[s - 1] = 0;
                               printf("server# ");
                               fflush(stdout);
                               //将缓冲区内容通过套接字发送到服务器
                               write(wfd[i], buf, strlen(buf));
                               int j = 0;
                               //写操作完成,应该进行读操作,监视其读操作
                               for (; j < NUMS; j++)
                               {
                                   if (rfd[j] == -1)
                                   {
                                       rfd[j] = wfd[i];
                                       wfd[i] = -1;
                                       break;
                                   }
                               }
                           }
                           else
                           {
                               FD_CLR(wfd[i], &wset);
                               close(wfd[i]);
                               return 5;
                           }
                       }
                       if (rfd[i] != -1 && FD_ISSET(rfd[i], &rset))
                       {

                           //在从套接字中读取服务器的回应信息
                           ssize_t _s = read(sock, buf, sizeof(buf)-1);
                           if (_s > 0)
                           {
                               buf[_s] = 0;
                               printf("%s\n", buf);
                               int j = 0;
                               //读操作完成,应该进行写操作,监视其写操作
                               for (; j < NUMS; j++)
                               {
                                   if (wfd[j] == -1)
                                   {
                                       wfd[j] = rfd[i];
                                       rfd[i] = -1;
                                       break;
                                   }
                               }
                           }
                           if (_s < 0)
                           {
                               FD_CLR(rfd[i], &rset);
                               close(rfd[i]);
                               perror("read");
                               return 4;
                           }
                       }
                   }
        }
        }
    }
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值