Linux网络编程学习笔记(7)---5种I/O模型及select轮询

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38215395/article/details/80325452

本文主要介绍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__

猜你喜欢

转载自blog.csdn.net/weixin_38215395/article/details/80325452