【网络】(七)五种I/O模型--I/O复用 Select(一)

1、五种I/O模型

五种I/O模型分别是:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O复用(select/poll)
  • 信号驱动I/O
  • 异步I/O

0.1 阻塞I/O

这里写图片描述

连接完成,我们通常使用recv函数读取数据,如果此时没有数据,那么该函数阻塞,直到对方发送的数据到达套接口缓冲区,此时该函数将数据复制到用户缓冲区中,并返回!

0.2 非阻塞I/O

这里写图片描述

连接完成,调用recv函数,并将套接口设置为非阻塞模式(fcntl(sk_fd,F_SETFL,flag|O_NONBLOCK));此时,即使没有数据到来,该函数也不会阻塞,它会返回一个错误-1,errno为EWOULDBLOCK,此时需要再次提交请求,一直这样循环,直到有数据到来,才复制数据到用户缓冲区!很少使用这种方式!

0.3 I/O 复用

这里写图片描述

连接完成后,使用select来管理所有的I/O,一旦其中的一个IO或这多个IO发生了我们感兴趣的事件,select函数就返回,返回值为检测到的个数,并且会告诉我们哪些IO发生了事件,事件发生后我们再调用recv去复制数据到用户缓冲区,就不会发生阻塞的情况!这种方法经常使用!

0.4 信号驱动I/O

这里写图片描述

在程序中建立一个处理SIGIO的处理程序,捕获该信号;程序可继续处理其他事情,一旦有数据到来,会以信号的方式通知程序,然后应用程序就跳转到信号处理程序中,通过recv函数复制数据到用户缓冲区!这种方式也较少使用!

0.5 异步I/O

这里写图片描述

这种方式效率是比较高的,它通过aio_read函数来实现,调用该函数,它会递交一个用户缓冲区给操作系统,就算没有数据,也立即返回,一旦有数据到来,操作系统内核会自动将数据拷贝到用户缓冲区中,然后通过一个信号通知应用程序。这种方式相当于内核在自动“推送”数据!

2、使用I/O 复用–select

select函数使用示例

客户端程序
client.c

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



#define handle_error(msg)   \
                    do{perror(msg);exit(EXIT_FAILURE);}while(0)

ssize_t readn(int fd, void *buf, size_t count)
{
  if((fd < 0) || (buf == NULL) || (count < 0))
        return -1;
  size_t nleft = count;   //剩余字节数
  ssize_t nread = 0;      //已读字节数
  char *pbuf = (char*)buf;
  while(nleft > 0)
  {
    if((nread = read(fd, pbuf, nleft)) < 0)
    {
        if(errno == EINTR)
            continue;
        return -1;
    }
    else if (nread == 0)
        return count - nleft;
    pbuf += nread;
    nleft -= nread;
  }
  return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    if((fd < 0) || (buf == NULL) || (count < 0))
          return -1;
    size_t nleft = count;   //剩余字节数
    ssize_t nwritten = 0;      //已发送字节数
    char *pbuf = (char*)buf;
    while (nleft > 0)
    {
        if((nwritten = write(fd, pbuf, nleft)) < 0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nwritten == 0)
            continue;
        pbuf += nwritten;
        nleft -= nwritten;
    }
    return count;
}

//使用recv函数从套接字接收缓冲区中接收数据,但并不从缓冲去中清除数据
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while(true)
    {
        int iret = recv(sockfd, buf, len, MSG_PEEK);
        if(iret == -1 && errno == EINTR)    //如果失败是因为信号中端,那么就重新再试
            continue;
        return iret;
    }
}



ssize_t recvline(int sockfd, void *buf, size_t maxlen)
{
    int iret = 0;
    int nread = 0;  //已读数据
    char *pbuf = (char*)buf;
    int nleft = maxlen; //剩余字符
    while(true)
    {
        iret = recv_peek(sockfd, pbuf, nleft);
        if(iret < 0)
            return iret;    //读取失败
        else if(iret == 0)
            return iret;    //对方关闭套接口

        nread = iret;
        if(nread > nleft)       //已经读取到的数据只可能小于或者等于剩余的数据
            exit(EXIT_FAILURE);

        for(int i = 0; i < nread; i++)
        {
            if(pbuf[i] == '\n')
            {
                iret = readn(sockfd, pbuf, i+1);    //从缓冲区中读走包括\n在内的数据
                if(iret != i+1)
                    exit(EXIT_FAILURE);     //没有读取都i+1个数据,说明失败
                return iret;                //读取都\n返回
            }
        }

        //在当前读到的数据中没有发现\n,那么先将这部分数据从缓冲区中读走,然后接着偷窥后面的数据
        nleft -= nread;
        iret = readn(sockfd, pbuf, nread);
        if(iret != nread)
            exit(EXIT_FAILURE);
        pbuf += nread;
    }
    return -1;
}

//select可看做一个管理者,可用它来管理多个IO,
//一旦其中的一个IO或这多个IO发生了我们感兴趣的事件,select函数就返回,
//返回值为检测到的个数,并且会告诉我们哪些IO发生了事件

//参数:
    //1、读写异常集合中的文件描述父的最大值+1
    //读集合,输入输出参数
    //写集合,输入输出参数
    //异常集合,输入输出参数
    //超时结构体,设置此结构体后,可设定让select函数返回的超时时间
void client_handler(int sk_fd)
{
    fd_set readset;
    FD_ZERO(&readset);
    int nready = 0;
    int fd_stdin = fileno(stdin);   //获得标准输入的描述符,防止输入重定向
    int fd_max = fd_stdin > sk_fd ? fd_stdin : sk_fd;
    while(true)
    {
        FD_SET(fd_stdin, &readset);
        FD_SET(sk_fd, &readset);        //readset集合可能会改变,所以每次都要添加
        nready = select(fd_max + 1, &readset, NULL, NULL, NULL);
        if(nready == -1)
            handle_error("select");
        if(nready == 0)
            continue;
        if(FD_ISSET(sk_fd, &readset))   //套接口产生了读事件
        {
            char recvbuf[1024] = {0};
            int iret = recvline(sk_fd, recvbuf, sizeof(recvbuf)); //接收包数据长度
            if(iret == -1)
                handle_error("read");
            else if(iret == 0)
            {
                printf("Server was closed!\n");
                break;
            }
            fputs(recvbuf, stdout);
            memset(&recvbuf, 0, sizeof(recvbuf));
        }
        if(FD_ISSET(fd_stdin, &readset))    //标准输入产生读事件
        {
            char sendbuf[1024] = {0};
            if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
                break;
            writen(sk_fd, sendbuf, strlen(sendbuf));   //发送数据
            memset(&sendbuf, 0, sizeof(sendbuf));
        }
    }
}



int main(void)
{
    int sk_fd;
    sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
    if(sk_fd < 0)
        handle_error("socket");
    struct sockaddr_in sr_addr;
    memset(&sr_addr,0,sizeof(sr_addr));
    sr_addr.sin_family = AF_INET;
    sr_addr.sin_port = htons(5188);
    //sr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1",&sr_addr.sin_addr);

    if(connect(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0)
    {
        close(sk_fd);
        handle_error("connect");
    }

    client_handler(sk_fd);
    close(sk_fd);
    return 0;
}

服务端程序
server.c

编译命令:gcc -Wall -g -std=gnu99 server.c -o server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>


#define handle_error(msg)   \
                    do{perror(msg);exit(EXIT_FAILURE);}while(0)

//忽略子进程的SIG_CHLD信号
void handle_SIGIGN(void)
{
  struct sigaction act_chld;
  act_chld.sa_handler = SIG_IGN;
  act_chld.sa_flags = 0;
  sigemptyset(&act_chld.sa_mask);

  if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
      handle_error("sigaction SIGCHLD");
}

void wait_sigchld(int sig)
{
    wait(NULL);
}
//调用wait函数处理该信号
void handle_wait(void)
{
  struct sigaction act_chld;
  act_chld.sa_handler = wait_sigchld;
  act_chld.sa_flags = 0;
  sigemptyset(&act_chld.sa_mask);

  if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
      handle_error("sigaction SIGCHLD");
}

void waitpid_sigchld(int sig)
{
    while( waitpid(-1,NULL,WNOHANG) >= 0); //不将执行挂起
}
//调用wait函数处理该信号
void handle_waitpid(void)
{
  struct sigaction act_chld;
  act_chld.sa_handler = waitpid_sigchld;
  act_chld.sa_flags = 0;
  sigemptyset(&act_chld.sa_mask);

  if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号
      handle_error("sigaction SIGCHLD");
}

ssize_t readn(int fd, void *buf, size_t count)
{
  if((fd < 0) || (buf == NULL) || (count < 0))
        return -1;
  size_t nleft = count;   //剩余字节数
  ssize_t nread = 0;      //已读字节数
  char *pbuf = (char*)buf;
  while(nleft > 0)
  {
    if((nread = read(fd, pbuf, nleft)) < 0)
    {
        if(errno == EINTR)
            continue;
        return -1;
    }
    else if (nread == 0)
        return count - nleft;
    pbuf += nread;
    nleft -= nread;
  }
  return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    if((fd < 0) || (buf == NULL) || (count < 0))
          return -1;
    size_t nleft = count;   //剩余字节数
    ssize_t nwritten = 0;      //已发送字节数
    char *pbuf = (char*)buf;
    while (nleft > 0)
    {
        if((nwritten = write(fd, pbuf, nleft)) < 0)
        {
            if(errno == EINTR)
                continue;
            return -1;
        }
        else if(nwritten == 0)
            continue;
        pbuf += nwritten;
        nleft -= nwritten;
    }
    return count;
}

//使用recv函数从套接字接收缓冲区中接收数据,但并不从缓冲去中清除数据
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while(true)
    {
        int iret = recv(sockfd, buf, len, MSG_PEEK);
        if(iret == -1 && errno == EINTR)    //如果失败是因为信号中端,那么就重新再试
            continue;
        return iret;
    }
}

//如果读到的数据包含\n,则返回,表示一条完整的消息读取完毕
ssize_t recvline(int sockfd, void *buf, size_t maxlen)
{
    int iret = 0;
    int nread = 0;  //已读数据
    char *pbuf = (char*)buf;
    int nleft = maxlen; //剩余字符
    while(true)
    {
        iret = recv_peek(sockfd, pbuf, nleft);
        if(iret < 0)
            return iret;    //读取失败
        else if(iret == 0)
            return iret;    //对方关闭套接口

        nread = iret;
        if(nread > nleft)       //已经读取到的数据只可能小于或者等于剩余的数据
            exit(EXIT_FAILURE);

        for(int i = 0; i < nread; i++)
        {
            if(pbuf[i] == '\n')
            {
                iret = readn(sockfd, pbuf, i+1);    //从缓冲区中读走包括\n在内的数据
                if(iret != i+1)
                    exit(EXIT_FAILURE);     //没有读取都i+1个数据,说明失败
                return iret;                //读取都\n返回
            }
        }

        //在当前读到的数据中没有发现\n,那么先将这部分数据从缓冲区中读走,然后接着偷窥后面的数据
        nleft -= nread;
        iret = readn(sockfd, pbuf, nread);
        if(iret != nread)
            exit(EXIT_FAILURE);
        pbuf += nread;
    }
    return -1;
}


void do_work(int sock);


int main(void)
{
    //三种僵尸进程处理方式
    //handle_SIGIGN();
    //handle_wait();
    handle_waitpid();


    int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
    if(sk_fd < 0)
        handle_error("socket");


    //使用REUSEADDR,不必等待TIME_WAIT 状态消失,就可以重新使用端口
    int on = 1;
    if(setsockopt(sk_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
        close(sk_fd);
        handle_error("setsockopt");
    }


    struct sockaddr_in sr_addr;
    memset(&sr_addr,0,sizeof(sr_addr));
    sr_addr.sin_family = AF_INET;
    sr_addr.sin_port = htons(5188);
    sr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1",&sr_addr.sin_addr);

    if(bind(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0)
    {
        close(sk_fd);
        handle_error("bind");
    }

    //被动套接字
    if(listen(sk_fd, SOMAXCONN) < 0)     //内核为此套接字排队的最大连接数由SOMAXCONN宏指定
    {
        close(sk_fd);
        handle_error("listen");
    }


    struct sockaddr_in cl_addr;
    socklen_t cl_length = sizeof(cl_addr);
    pid_t pid;
    while (true)
    {
        memset(&cl_addr,0,sizeof(cl_addr));
        int ac_sk = accept(sk_fd, (struct sockaddr *)&cl_addr, &cl_length);
        if(ac_sk < 0)
        {
            if(errno == EINTR)
                continue;
            close(sk_fd);
            handle_error("accept");
        }
        printf("Connect ip = %s\tport = %d\n",inet_ntoa(cl_addr.sin_addr),ntohs(cl_addr.sin_port));

        //每个客户端对应一个子进程
        pid = fork();
        if(pid == -1)
            handle_error("fork");
        if(pid == 0)
        {
            close(sk_fd);
            do_work(ac_sk);
            close(ac_sk);
            exit(EXIT_SUCCESS);
        }
        else
            close(ac_sk);
    }
    close(sk_fd);
    return 0;
}


void do_work(int sock)
{
    char recvbuf[1024];
    while(true)
    {
        memset(recvbuf,0,sizeof(recvbuf));
        int iret = recvline(sock,recvbuf,sizeof(recvbuf));    //获取包数据长度
        if(iret == -1)
            handle_error("read");
        else if(iret == 0)
        {
            printf("Client was closed!\n");
            break;
        }
        fputs(recvbuf,stdout);
        writen(sock, recvbuf, strlen(recvbuf));    //回传数据
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值