1. 程式人生 > >Linux網路程式設計學習筆記(7)---5種I/O模型及select輪詢

Linux網路程式設計學習筆記(7)---5種I/O模型及select輪詢

本文主要介紹5種I/O模型,select函式以及利用select實現C/S模型。

1、5種I/O模型

(1)阻塞I/O:
一直等到資料到來,才會將資料從核心中拷貝到使用者空間中。
(2)非阻塞I/O:
每過一段時間就詢問是否有資料到來(輪詢),呼叫recv()函式,若沒有資料到來會返回錯誤。接著繼續詢問。
(3)I/O多路複用:
一個程序可以輪詢多個I/O(檔案描述符),將阻塞過程提前到select/poll/epoll中。
(4)訊號驅動I/O:
當資料到達時,程序會收到訊號SIGIO,在訊號處理函式中,實現資料的讀取。
(5)非同步I/O:
以上四種方式都是同步方式,都是順序執行的。而非同步I/O不一定是順序執行。通過呼叫aio_read函式,無論核心是否準備好資料,都會給使用者程序返回一個值,因此不會使程式產生阻塞。同時,使用者程序可以做其他的事情。若資料準備好了,核心會將資料複製給使用者程序。當一切完成之後,核心會發送完成通知。

2、select函式

select函式原型:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

select函式作用:

select函式用來管理多個I/O,當其中的一個或多個I/O檢測到了我們感興趣的事件,select就返回檢測到的事件個數,並返回哪些I/O產生了事件。

引數介紹:
(1)maxfdp:一個整數,在Linux中,為所有檔案描述符的最大值加1;
(2)readfds:如果I/O(檔案描述符)可讀,就把它放入readfds集合中;
(3)writefds:如果I.O(檔案描述符)可寫,就把它放入writefds集合中;
(4)exceptfds:如果想檢測檔案描述符是否有異常,就把它放入exceptfdsz集合中;
(5)timeout:結構體timeval,select的超時時間。若設定為NULL,則將select置於阻塞狀態;若設定為0,則設定select為純非阻塞函式,不管檔案描述符是否有變換,會立即返回;若設定為大於0,則在timeout時間內,select一直阻塞。

3、利用select的C/S模型

客戶端:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"common.h"
int main()
{
    int sock ;
    sock = socket(PF_INET,SOCK_STREAM,0
); if(sock <0) { err_exit("socket"); } struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(5888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret ; ret = connect (sock , (struct sockaddr*)&addr,sizeof(addr)); if(ret < 0) { err_exit("connect"); } fd_set fds; FD_ZERO(&fds); //清空集合 int maxfds; if(STDIN_FILENO > sock) maxfds=STDIN_FILENO; else maxfds=sock; while(1) { FD_SET(STDIN_FILENO,&fds); FD_SET(sock,&fds); int nready=select(maxfds+1,&fds,NULL,NULL,NULL); if(nready==-1) //出錯 err_exit("select"); else if(nready == 0) //無資料 continue; //鍵盤有輸入 if(FD_ISSET(STDIN_FILENO,&fds)) { struct packet writebuf; memset(&writebuf,0,sizeof(writebuf)); //獲取鍵盤輸入 fgets(writebuf.data,sizeof(writebuf.data),stdin); int n=strlen(writebuf.data); writebuf.msgLen=htonl(n); writen(sock,&writebuf,4+n); //傳送 memset(&writebuf,0,sizeof(writebuf)); } if(FD_ISSET(sock,&fds)) { struct packet readbuf; memset(&readbuf,0,sizeof(readbuf)); int ret=readn(sock,&readbuf.msgLen,4); if(ret == -1) err_exit("readn"); else if(ret == 0) { printf("peer1 close\n"); break; } int dataBytes = ntohl(readbuf.msgLen); int readBytes= readn(sock,readbuf.data,dataBytes); if(readBytes<0) err_exit("read"); fputs(readbuf.data,stdout); } } close(sock); return 0; }

伺服器:

/*
單程序伺服器
實現簡單點到點的聊天功能
*/
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"common.h"

int main()
{
    int listenfd;
    listenfd = socket(PF_INET , SOCK_STREAM, 0);
    if(listenfd <0)
    {
        err_exit("socket");
    }

    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(5888);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //設定地址可重複利用
    int on = 1;
    setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));   

    int ret = bind(listenfd , (const struct sockaddr *)&addr,sizeof(addr));
    if(ret == -1)
    {
        err_exit("bind");
    }

    ret = listen(listenfd , SOMAXCONN);
    if(ret < 0)
    {
        err_exit("listen");
    }

    struct sockaddr_in peerAddr ;
    socklen_t peerlen = sizeof(peerAddr);    //peerlen一定要有初始值

    int conn;
    conn = accept (listenfd ,(struct sockaddr *)&peerAddr,&peerlen);
    if(conn <0)
    {
        err_exit("accept");
    }

    printf("客戶端的IP地址是:%s,埠號是:%d\n",
        inet_ntoa(peerAddr.sin_addr),ntohs(peerAddr.sin_port));

    fd_set fds;
    FD_ZERO(&fds);    //清空集合
    int maxfds;
    if(STDIN_FILENO > conn)
        maxfds=STDIN_FILENO;
    else
        maxfds=conn;

    while(1)
    {
        FD_SET(STDIN_FILENO,&fds);
        FD_SET(conn,&fds);
        int nready=select(maxfds+1,&fds,NULL,NULL,NULL);
        if(nready==-1)                   //出錯
            err_exit("select");
        else if(nready == 0)             //無資料
            continue;

        //鍵盤有輸入
        if(FD_ISSET(STDIN_FILENO,&fds))
        {
            struct packet writebuf;
            memset(&writebuf,0,sizeof(writebuf));
            //獲取鍵盤輸入
            fgets(writebuf.data,sizeof(writebuf.data),stdin);    
            int n=strlen(writebuf.data);
            writebuf.msgLen=htonl(n);
            writen(conn,&writebuf,4+n);   //傳送
            memset(&writebuf,0,sizeof(writebuf));
        }
        if(FD_ISSET(conn,&fds))
        {
            struct packet readbuf;
            memset(&readbuf,0,sizeof(readbuf));
            int ret=readn(conn,&readbuf.msgLen,4);
            if(ret == -1)
                err_exit("readn");
            else if(ret == 0)
            {
                printf("peer1 close\n");
                break;
            }

            int dataBytes = ntohl(readbuf.msgLen);
            int readBytes= readn(conn,readbuf.data,dataBytes);
            if(readBytes<0)
                err_exit("read");
            fputs(readbuf.data,stdout);
        }
    }   
    close(listenfd);
    close(conn);
    return 0;
}

common.h

#ifndef __COMMON__
#define __COMMON__
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>

# define err_exit(m)\
    do\
    {\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)
struct packet
{
    unsigned int msgLen ;
    char data [1024];
};
void handler(int sig)
{
    printf("recv a sig = %d \n",sig);
    exit(EXIT_SUCCESS);
}
/*
readn 函式
讀取count位元組資料
*/
ssize_t readn(int fd,void *buf, size_t count)
{
    int left = count ; //剩下的位元組
    char * ptr = (char*)buf ;
    while(left>0)
    {
        int readBytes = read(fd,ptr,left);
        if(readBytes< 0)//read函式小於0有兩種情況:1中斷 2出錯
        {
            if(errno == EINTR)//讀被中斷
            {
                continue;
            }   
            return -1;
        }
        if(readBytes == 0)//讀到了EOF
        {
            return count - left;
        }   
        left -= readBytes;
        ptr += readBytes ;
    }
    return count ;
}

/*
writen 函式
寫入count位元組的資料
*/
ssize_t writen(int fd, void *buf, size_t count)
{
    int left = count ;
    char * ptr = (char *)buf;           
    while(left >0)
    {
        int writeBytes = write(fd,ptr,left);
        if(writeBytes<0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(writeBytes == 0)
            continue;
        left -= writeBytes;
        ptr += writeBytes; 
    }
    return count;
}

/*
從緩衝區中讀取指定長度的資料,但不清楚緩衝區內容
*/
ssize_t read_peek(int sockfd, void *buf, size_t len )
{
    while(1)
    {
        int ret = recv (sockfd , buf ,len ,MSG_PEEK);
        if(ret  == -1)
        {
            if(errno ==EINTR)  //出現中斷
                continue ;
        }
        return ret ;
    }       
}

/*
按行讀取資料
引數說明:
    sockfd:套接字
    buf :應用層緩衝區,儲存讀取到的資料
    maxline:所規定的一行的長度
返回值說明:  
*/
ssize_t readLine (int sockfd , void *buf ,size_t maxline)
{
    int ret;
    int nRead = 0;
    int left = maxline ; //剩下的位元組數
    char * pbuf = (char *) buf ; 
    int count = 0;
    while(1)
    {
        ret = read_peek ( sockfd, pbuf, left);   //讀取長度為left的socket緩衝區內容
        if(ret <0)
        {
            return ret;
        }
        nRead = ret;
        int i = 0;
        for(;i<nRead;++i)//看看讀取出來的資料中是否有換行符\n
        {
            if(pbuf[i]=='\n')//如果有換行符
            {
                ret = readn(sockfd , pbuf , i+1);//讀取一行
                if(ret != i+1)  //一定會讀到i+1個字元,否則是讀取出錯
                {
                    exit(EXIT_FAILURE); 
                }
                return ret + count ;
            }
        }
        //窺探的資料中並沒有換行符
        //把這段沒有\n的讀出來
        ret = readn(sockfd , pbuf,nRead); 
        if(ret != nRead )
        {
            exit(EXIT_FAILURE); 
        }
        pbuf += nRead;
        left -= nRead;
        count += nRead; 
    }
    return -1;

}
#endif //__COMMON__