IO多路复用 select与poll

1.    阻塞与非阻塞

阻塞方式block,进程执行到这一函数时必须等待事件发生,如果没发生,就一直阻塞函数不能返回

非阻塞 non-block

进程或线程执行不必等待事件发生一旦执行肯定返回以不停返回返回值来反应函数执行情况

 Select就是这样监视描述符的变化

2.select模型

两结构体:

Structfd_set存放描述符,文件句柄的聚合

建立文件联系四个宏

FD_ZERO(fd_set*fd_set);//清空fdset与所有文件句柄的联系

FD_SET(intfd,fd_set *fdset);//建立文件句柄与fdset的联系

FD_CLR(intfd,fd_set *fdset); 清除文件句柄fd与fdset的联系

FD_ISSET(intfd,fd_set*fdset);//检查fdset联系的文件句柄fd是否读写,>0表示可读写

Structtimeval 两个成员 tv_sec表示秒数,tv_usec表示毫秒数

Select函数原型

   Int select(intnfds,fd_set *rdfds,fd_set *wtfds,fd_set *exfds,struct timeval *timeout);

A.其中rdfds(wtfds)读集,度过有一个文件可读,select返回一个大于0的值表示文件可读超出timeout时间select返回0,发生错误返回负值,

可传入NULL表示不关心文件读写变化

Exfds异常文件集有特殊情况发生select返回

B.timeval*out 若果设置为NULL变为阻塞方式

设置为0完全非阻塞

 C.返回值大于0文件可读或可写

      返回值为0,out of time

返回值小于0,select错误


network.h

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

#define ERR_EXIT(m) \
        do { \
                perror(m);\
                exit(EXIT_FAILURE);\
            }while(0)


ssize_t readn(int fd, void *usrbuf, size_t n);
ssize_t writen(int fd, void *usrbuf, size_t n);
ssize_t recv_peek(int sockfd, void *usrbuf, size_t n);
ssize_t readline(int sockfd, void *usrbuf, size_t maxline);

network.c

#include "network.h"


ssize_t readn(int fd, void *usrbuf, size_t n)
{
    size_t nleft = n; //表示还需要读取的字节数
    ssize_t nread;
    char *bufp = usrbuf; //控制read函数存放的位置

    while(nleft > 0)
    {
        if((nread = read(fd, bufp, nleft)) == -1)
        {
            if(errno == EINTR)  //interupt
                nread = 0;  //continue;  中断需要再次读取
            else
                return -1;  // ERROR
        }else if(nread == 0)  // EOF
            break;
        
        nleft -= nread;
        bufp += nread;
    }
    return (n - nleft);
}

ssize_t writen(int fd, void *usrbuf, size_t n)
{
    size_t nleft = n;
    ssize_t nwrite;

    char *bufp = usrbuf;

    while(nleft > 0)
    {
        //nwrite == 0也属于错误
        if((nwrite = write(fd, bufp, nleft)) <= 0)
        {
            if(errno == EINTR)
                nwrite = 0;
            else
                return -1; // -1 和 0
        }

        nleft -= nwrite;
        bufp += nwrite;
    }
    return n;  //这里不是 n- nleft 必须是n
}

//recv_peek选项完成一次正确的读取过程。
ssize_t recv_peek(int sockfd, void *buf, size_t len) {
    int nread;
    while (1) {
        //这个过程只成功调用一次
        nread = recv(sockfd, buf, len, MSG_PEEK);
        if (nread < 0 && errno == EINTR) {  //被中断则继续读取
            continue;
        }
        if (nread < 0) {
            return -1;
        }
        break;
    }
    return nread;
}


ssize_t readline(int sockfd, void *buf, size_t maxline) {
    int nread;  //一次IO读取的数量
    int nleft;  //还剩余的字节数
    char *ptr;  //存放数据的指针的位置
    int ret;    //readn的返回值
    int total = 0;  //目前总共读取的字节数

    nleft = maxline-1;
    ptr = buf;

    while (nleft > 0) {
        //这一次调用仅仅是预览数据
        //并没有真的把数据从缓冲区中取走
        ret = recv_peek(sockfd, ptr, nleft);
        //注意这里读取的字节不够,绝对不是错误!!!
        if (ret <= 0) {
            return ret;
        }

        nread = ret;
        int i;
        for (i = 0; i < nread; ++i) {
            if (ptr[i] == '\n') {
                //这里才是真正的读取过程
                ret = readn(sockfd, ptr, i + 1);
                if (ret != i + 1) {
                    return -1;
                }
                total += ret;
                ptr += ret;
                *ptr = 0;
                return total;   //返回此行的长度 '\n'包含在其中
            }
        }
        //如果没有发现\n,这些数据应全部接收
        ret = readn(sockfd, ptr, nread);
        if (ret != nread) {
            return -1;
        }
        nleft -= nread;
        total += nread;
        ptr += nread;
    }
    *ptr = 0;
    return maxline-1;
}
client.c
#include "network.h"
#include <sys/select.h>
#include <sys/epoll.h>
void do_service(int peerfd)
{
    char recvbuf[1024]={0};
    char sendbuf[1024]={0};
   //init
    fd_set readset;
    FD_ZERO(&readset);
    //防止重定向不用STDIN_FILENO
    int stdin_fd=fileno(stdin);

    int maxfd=(stdin_fd>peerfd)?stdin_fd:peerfd;
    int nready;
    while(1)
    {
        FD_ZERO(&readset);
        FD_SET(stdin_fd,&readset);
        FD_SET(peerfd,&readset);
        nready=select(maxfd+1,&readset,NULL,NULL,NULL);
        if(nready==-1)
        {
            if(errno==EINTR)
                continue;
            ERR_EXIT("select");
        }
        if(FD_ISSET(stdin_fd,&readset))
        {
            fgets(sendbuf,1024,stdin);
            writen(peerfd,sendbuf,strlen(sendbuf));
        }
        if(FD_ISSET(peerfd,&readset))
        {
           int ret= readline(peerfd,recvbuf,1024);
           if(ret==-1)
           {
               ERR_EXIT("readline");
           }
           else
               if(ret==0)
               {
                   shutdown(peerfd,SHUT_WR);
                   return;
               }
            printf("recv data : %s",recvbuf);
        }
    }
    


}

int main(int argc, const char *argv[])
{
    int peerfd=socket(AF_INET,SOCK_STREAM,0);
    if(peerfd==-1)
        ERR_EXIT("socket");
    struct sockaddr_in peeraddr;
    peeraddr.sin_family=AF_INET;
    peeraddr.sin_port=htons(8989);
    peeraddr.sin_addr.s_addr=inet_addr("127.0.0.1");

    if(connect(peerfd,(struct sockaddr *)&peeraddr,sizeof(peeraddr))==-1)
    ERR_EXIT("CONNECT");

    do_service(peerfd);
    close(peerfd);
    return 0;
}

1.使用select编写客户端程序的一般步骤:

初始化参数,包括readset、maxfd、nready

while(1)

{

1.先重新设置readset

2.执行select调用,包括检查返回值

3.依次检查stdin和peerfd,如果是前者就从键盘读取数据,如果是后者,就使用readline接收网络数据。

}


3.poll模型

client.c

#include "network.h"
#include <poll.h>

void do_service(int peerfd)
{
    char recvbuf[1024]={0};
    char sendbuf[1024]={0};
   //init

    struct pollfd client[2];//listen 2 fd
    client[0].fd=fileno(stdin);
    client[0].events =POLLIN; //listen read events

    client[1].fd=peerfd;
    client[1].events=POLLIN;

    int maxi =1; //max subscript of array client[1]
    int nready;// join poll echo value

    while(1)
    {
    // int poll(struct pollfd *fds,nfds_t nfds,int timeout);
     nready=poll(client,maxi+1,-1);  // -1 said permanent waiting
    if(nready==-1)
    {
        if(errno==EINTR)
            continue;
        ERR_EXIT("poll");
    }
    else if(nready ==0) 
    {
        continue;
    }
    //check stdin and peerfd according to priority
    if(client[0].revents & POLLIN)
    {
        if(fgets(sendbuf,1024,stdin)==NULL)
        {
            shutdown(peerfd,SHUT_WR);
            client[0].fd=-1; //do not listen stdin;
        }
        else
        {
            writen(peerfd,sendbuf,strlen(sendbuf));
        }
    }

    if(client[1].revents&POLLIN)
    {
        int ret=readline(peerfd,recvbuf,1024);
        if(ret==-1)
            ERR_EXIT("readline");
        else if(ret==0)
        {
            printf("server close\n");
            close(peerfd);
            break;
        }
        printf("recv data: %s",recvbuf);

    }
    }

}

int main(int argc, const char *argv[])
{
    int peerfd=socket(AF_INET,SOCK_STREAM,0);
    if(peerfd==-1)
        ERR_EXIT("socket");
    struct sockaddr_in peeraddr;
    peeraddr.sin_family=AF_INET;
    peeraddr.sin_port=htons(8989);
    peeraddr.sin_addr.s_addr=inet_addr("127.0.0.1");

    if(connect(peerfd,(struct sockaddr *)&peeraddr,sizeof(peeraddr))==-1)
    ERR_EXIT("CONNECT");

    do_service(peerfd);
    close(peerfd);
    return 0;
}


使用poll编写客户端的一般步骤:

准备数组(这里为2),填入相应的fd以及events。还有maxi、nready

while(1)

{

1.执行poll,以及检查返回值

2.检查两个fd,通过revents字段

}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值