I/O多路转接之select服务器

五种I/O模型

Unix共有五种I/O模型:

  1. 阻塞式I/O
  2. 非阻塞I/O
  3. I/O复用(select和(e)poll)
  4. 信号驱动I/O(SIGIO)
  5. 异步I/O(Posix.1的aio_系列函数)

阻塞I/O模型

应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。如果数据没有准备好,一直等待。数据准备好了,从内核拷贝到用户空间,表示IO结束,IO函数返回成功指示。

非阻塞I/O模型

我们把一个套接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试 数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。

I/O复用模型

该种模型又被称作是多路转接,I/O复用模型会用到select或者poll函数,这两个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。

信号驱动I/O模型

首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

异步I/O模型

调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核将数据拷贝到缓冲区后,再通知应用程序。也就是它只需要发起这个读写事件,不要等待与数据搬迁,只需要在结束之后得到成果。

I/O多路转接之select

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。 程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。 关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数示的,对应的FILE *结构的表示就是stdin、stdout、stderr。

select的优缺点

优点:
1、不需要建立多个线程、进程就可以实现一对多的通信。
2、可以同时等待多个文件描述符,效率比起多进程多线程来说要高很多

select高效的原因
首先要知道一个概念,一次I/O分两个部分(①等待数据就绪 ②进行I/O),减少等的比重,增加I/O的比重就可以达到高效服务器的目的。select工作原理就是这个,同时监控多个文件描述符(或者说文件句柄),一旦其中某一个进入就绪状态,就进行I/O操作。监控多个文件句柄可以达到提高就绪状态出现的概率,就可以使CPU在大多数时间下都处于忙碌状态,大大提高CPU的性能。达到高效服务器的目的。 可以理解为select轮询监控多个文件句柄或套接字。

缺点:
1、每次进行select都要把文件描述符集fd由用户态拷贝到内核态,这样的开销会很大。
2、实现select服务器,内部要不断对文件描述符集fd进行循环遍历,当fd很多时,开销也很大。
3、select能监控文件描述符的数量有限,一般为1024。(sizeof(fd_set) * 8 = 1024(fd_set内部是以位图表示文件描述符))

操作函数

1.重定向的dup函数

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

函数功能:
dup:将oldfd文件描述符进行一份拷贝,返回值为最新拷贝生成的文件描述符
dup2:使用newfd对oldfd文件描述符做一份拷贝,必要是可以先关闭newfd文件描述符
调用dup2之后,oldfd文件描述符不变,newfd和oldfd相等。
2.文件描述符集的四个函数:

#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

void FD_CLR(int fd,fd_set *set); ---- 将文件描述符fd从文件描述符集指针set指向的文件描述符集中清除掉
int FD_ISSET(int fd,fd_set *set); ---- 用来判断文件描述符fd是否在文件描述符集指针set指向的文件描述符集中。
                                         - 存在 返回1;
                                         - 不存在 返回0void FD_SET(int fd,fd_set*set); ---- 将文件描述符fd设置进文件描述符集指针set指向的文件描述符集中。
void FD_ZERO(int fd,fd_set*set); ----将文件描述符集指针set指向的文件描述符集清0

3.select函数

#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *execptfds,struct timeval *timeout)

函数功能:通过参数readfds,writefds,execptfds将要监控的文件描述符写入,就可以监控这些文件描述符。
参数:
(1) nfds:表示最大文件描述符+1,用来表示文件描述符的范围。
(2) readfds:表示指向读文件描述符集的指针
(3)writefds:表示指向写文件描述符集的指针.
(4)execptfds:表示指向错误输出文件描述符集的指针。
参数readfds,writefds,execptfds既是输入参数,又是输出参数。
输入:将要监控的文件描述符传给select
输出:将处于就绪状态的文件描述符返回。 (所以要在每次处理完一就绪事件后要将readfds,writefds,execptfd三个参数重置)
(4)timeout:表示超时时间限制。(有三种情况)
timeout也是一种输入输出两用参数,输入表示设定超时时间,输出表示超时时间还剩多少。

timeout是一个timeval结构体类型的指针。 结构如下:

struct timeval {
    long    tv_sec;         /* seconds */  //表示秒
    long    tv_usec;        /* microseconds */ //表示微秒
};

我们可以通过设置上面结构体内两个元素的值来设定select的超时时间。
设置timeout的方法:

struct timeval time = {5,0};//第一个值表示秒,第二个值表示微秒。   
select(...,&time);//超时时间为5

timeout的时间设定有三种情况:
① 上面示例的正常设定,设定一个正常的时间。等待一段时间,返回一次
②将时间设为0,—-表示一直轮询等待。或者说根本不等待,检查完描述符后不管有没有就绪立即返回
③传参时传为NULL,—-表示一直阻塞等待
返回值
select的返回值有三种,-1 —– 执行错误,0 —– timeout时间到达,其他 —– 正确执行,并且有就绪事件到达

代码实现

server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>

#define SIZE sizeof(fd_set)*8

int readfds[SIZE];  //保存所有文件描述符的数组

void Usage(const char* name)
{
    printf("Usage:%s [IP] [port]\n",name);
}

int StartUp(const char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        return 1;
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        perror("bind");
        return 2;
    }

    if(listen(sock,5) < 0)
    {
        perror("listen");
        return 3;
    }
    return sock;
}

int main(int argc,char* argv[])
{

    //printf("hello\n");
    if(argc != 3)
    {
        Usage(argv[0]);
        return 4;
    }

    int sock = StartUp(argv[1],atoi(argv[2]));

    int i = 0;
    for(; i<SIZE; ++i)
    {
        readfds[i] = -1;//全部初始化为-1
    }
    readfds[0] = sock;
    int max_fd = readfds[0]; //保存最大的文件描述符做select函数的参数

    while(1)
    {
        fd_set rfds,wfds;
        char buf[1024];
        FD_ZERO(&rfds); //将文件描述符集指针指向的文件描述符集清零
        int j = 0;
        for(; j<SIZE; ++j)
        {
            if(readfds[j] != -1)
                FD_SET(readfds[j],&rfds);//将文件描述符设置进文件描述符集指针指向的文件描述符集中
            if(max_fd < readfds[j])
                max_fd = readfds[j];
        }

        struct timeval timeout = {5,0};
        switch (select(max_fd+1,&rfds,&wfds,NULL,&timeout))
        {
            case -1:
                {
                    perror("select");
                    break;
                }
            case 0:
                {
                    printf("timeout...\n");
                    break;
                }
            default:

                {
                    int k = 0;
                    for(; k<SIZE; ++k)
                    {
                        if(readfds[k] == sock && FD_ISSET(readfds[k],&rfds))
                        {
                            struct sockaddr_in peer;
                            socklen_t len = sizeof(peer);
                            int newsock = accept(sock,(struct sockaddr*)&peer,&len);
                            if(newsock < 0)
                            {
                                perror("accept");
                                continue;
                            }

                            int l = 0;
                            for(; l<SIZE; ++l)
                            {
                                if(readfds[l] == -1)
                                {
                                    readfds[l] = newsock;
                                    break;
                                }
                            }
                            if( l == SIZE)
                            {
                                printf("readfds is full\n");
                                return 5;
                            }
                        }
                        else if(readfds[k] > 0 && FD_ISSET(readfds[k],&rfds))
                        {
                            ssize_t s = read(readfds[k],buf,sizeof(buf)-1);
                            if(s < 0)
                            {
                                perror("read");
                                return 6;
                            }
                            else if(s == 0)
                            {
                                printf("client quit\n");
                                readfds[k] = -1;
                                close(readfds[k]);
                                continue;
                            }
                            else
                            {
                                buf[s] = 0;
                                printf("client # %s\n",buf);
                                fflush(stdout);
                                write(readfds[k],buf,strlen(buf));
                            }
                        }
                    }
                }
                break;
        }

    }
    close(sock);
    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>

void Usage(const char* name)
{
    printf("Usage:%s [IP] [port]\n");
}

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 local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[2]));
    local.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
    {
        perror("connecct");
        return 3;
    }
    printf("connect success\n");

    char buf[1024];
    while(1)
    {
        printf("client # ");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s <= 0)
        {
            perror("read");
            return 4;
        }
        else
        {
            buf[s-1] = 0;
            int fd = dup(1);
            dup2(sock,1);
            printf("%s",buf);
            fflush(stdout);
            dup2(fd,1);
        }
        s = read(sock,buf,sizeof(buf)-1);
        if(s == 0)
        {
            printf("server quit\n");
            break;
        }
        else if(s < 0)
        {
            perror("read");
            return 5;
        }
        else
        {
            buf[s] = 0;

            printf("server #%s\n",buf);
        }
    }
    close(sock);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章