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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章