Linux基础13(I/O复用)

第十三章、I/O复用

在这里插入图片描述总而言之:需要处理多个文件描述符时,就用io复用

1.select方法

在这里插入图片描述在这里插入图片描述

1.select方法的应用-使用select方法实现服务器并发处理

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<sys/select.h>
#include<sys/time.h>

#define MAXFD 100

void fds_init(int fds[])
{
        int i=0;
        for(;i<MAXFD;++i)
        {
                fds[i] = -1;
        }
}
void fds_add(int fds[],int fd)
{
        int i=0;
        for(;i<MAXFD;++i)
        {
                if(fds[i] == -1)
                {
                        fds[i]=fd;
                        break;
                }
        }
}
void fds_del(int fds[],int fd)
{
        int i=0;
        for(;i<MAXFD;++i)
        {
                if(fds[i]==fd)
                {
                        fds[i] = -1;
                        break;
                }
        }
}

int create_socket();
int main()
{
        int sockfd = create_socket();
        assert(sockfd != -1);

        int fds[MAXFD];
        fds_init(fds);

        fds_add(fds,sockfd);

        fd_set fdset;//把所有文件描述符放进集合中

        while(1)
        {
                FD_ZERO(&fdset);
                int maxfd=-1;

                int i=0;
                for(;i<MAXFD;++i)
                {
                        if(fds[i]!=-1)
                        {
                                FD_SET(fds[i],&fdset);
                                if(maxfd<fds[i])
                                {
                                        maxfd =fds[i];//找到数组中描述符最大的那个
                                }
                        }
                }
                struct timeval tv ={5,0};

                int n=select(maxfd+1,&fdset,NULL,NULL,&tv);
                if(n<0)
                {
                        printf("select error\n");
                        continue;
                }
                else if(n==0)
                {
                        printf("time out\n");
                        continue;
                }
                else
                {
                        int i=0;
                        for(;i<MAXFD;++i)
                        {

                                if(fds[i] == -1)
                                {continue;}

                                if(FD_ISSET(fds[i],&fdset))//检测当前描述符上有没有数据就>绪
                                {
                                //处理分两类,对于socket套接字(监听套接字)采用accept()方法
                                //对于连接套接字,采用recv()方法
                                        if(fds[i]==sockfd)
                                        {
                                                struct sockaddr_in caddr;
                                                int len= sizeof(caddr);
                                                int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
                                                if (c==-1)
                                                {
                                                continue;
                                                }
                                                printf("accept c=%d\n",c);
                                                fds_add(fds,c);
                                        }
                                        else
                                        {
                                                char buff[128]={0};
                                                int res =recv(fds[i],buff,127,0);
                                                if(res <= 0)//对方出错/关闭
                                                {
                                                        close(fds[i]);
                                                        fds_del(fds,fds[i]);
                                                        printf("one client over\n");
                                                }
                                                else
                                                {
                                                printf("buff(c=%d)=%s\n",fds[i],buff);
                                                send(fds[i],"ok",2,0);
                                                }
                                        }
                                }
                        }

                }

        }
}

int create_socket()
{
        int sockfd =socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }

        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port=htons(6000);
        saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

        int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        res = listen(sockfd,5);
        if(res == -1)
        {
                return -1;
        }
        return sockfd;
}

2.select方法的应用-使用select方法实现客户端并发处理

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

int main()
{
        int sockfd =socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }

        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port=htons(6000);
        saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

        int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        while(1)
        {
                printf("input:\n");
                char buff[128]={0};
                fgets(buff,127,stdin);//从键盘获取数据

                if(strncmp(buff,"end",3) == 0)
                {
                        break;
                }

                send(sockfd,buff,strlen(buff)-1,0);//把键盘上接收到的数据发送给服务器

                memset(buff,0,128);
                recv(sockfd,buff,127,0);//将服务器返回的打印出来
                printf("buff=%s\n",buff);

        }
        close(sockfd);
}

2.poll方法

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
//poll系统调用成功后返回就绪文件描述符的总数,超时返回0,失败返回-1

服务器端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<poll.h>

#define MAXFD 10

void fds_init(struct pollfd fds[])
{
        int i=0;
        for(;i<MAXFD;++i)
        {
                fds[i].fd = -1;
                fds[i].events = 0;//注册的事件
                fds[i].revents = 0;//内核反馈给我们的事件
        }
}
void fds_add(struct pollfd fds[],int fd)
{
//在此代码中,我们只关注读事件因此将其写死
        int i=0;
        for(;i<MAXFD;++i)
        {
                if(fds[i].fd == -1)
                {
                        fds[i].fd = fd;
                        fds[i].events = POLLIN; //读事件r           
                        fds[i].revents = 0;
                        break;
                }
        }
}
void fds_del(struct pollfd fds[],int fd)
{
        int i=0;
        for(;i<MAXFD;++i)
        {
                if(fds[i].fd == fd)
                {
                        fds[i].fd = -1;
                        fds[i].events = 0;
                        fds[i].revents = 0;
                }
        }
}


int create_socket();
int main()
{
        int sockfd = create_socket();
        assert(sockfd != -1);
        //由于是对应多个客户端,因此我们要把文件描述符收集起来
        //之后再来检测有没有我们关心的事件产生

        struct  pollfd fds[MAXFD]; //将文件描述符存入数组中
        fds_init(fds);

        fds_add(fds,sockfd);

        while(1)
        {
                int n=poll(fds,MAXFD,5000);//时间单位是ms,会阻塞住,直到超时或者有事件返>回
                //也可以将其写为-1,就是永久阻塞住,直到有时间返回
                if(n== -1)
                {
                        continue;
                }
                else if(n==0)
                {
                        printf("time out\n");
                        continue;
                }
                else
                {
                        //检测哪个描述符上有数据
                        int i = 0;
                        for(;i<MAXFD;++i)
                        {
                                if(fds[i].fd == -1)
                                {
                                        continue;
                                }
                                if(fds[i].revents&POLLIN)//判断返回的事件中有没有pollin读>事件
                                {
                                        if(fds[i].fd == sockfd)
                                        {
                                                //accept
                                                struct sockaddr_in caddr;
                                                int len = sizeof(caddr);
                                                int c=accept(sockfd,(struct sockaddr*)&caddr,&len);

                                                if(c<0)
                                                {
                                                        continue;
                                                }
                                              printf("accept c=%d\n",c);
                                                fds_add(fds,c);
                                        }
                                        else
                                        {
                                                //recv
                                                char buff[128]={0};
                                                int num =recv(fds[i].fd,buff,127,0);
                                                if(num<=0)
                                                {
                                                        close(fds[i].fd);
                                                        fds_del(fds,fds[i].fd);
                                                        printf("client close\n");
                                                        continue;
                                                }
                                                printf("buff(%d)=%s\n",fds[i].fd,buff);
                                                send(fds[i].fd,"ok",2,0);

                                        }
                                }
                                //if(fds[i].revents&POLLOUT)//判断是否有写事件
                        }
                }

        }
}
int create_socket()
{
        int sockfd =socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }

        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port=htons(6000);
        saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

        int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        res = listen(sockfd,5);
        if(res == -1)
        {
                return -1;
        }
        return sockfd;
}

客户端代码与之前的一模一样,略

3.epoll

在这里插入图片描述在这里插入图片描述

1.epoll各接口的使用

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

2.LT与ET模式

2.1.LT模式下的服务器代码(默认情况下是LT模式)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/epoll.h>

#define MAXFD 10
//此时只关注读事件,因此不传入事件
void epoll_add(int epfd ,int fd)
{
        struct epoll_event ev;
        ev.events = EPOLLIN;//read
        ev.data.fd=fd;

        if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
        {
                perror("epoll ctl add err");
        }
}
void epoll_del(int epfd,int fd)
{
        if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1)
        {
                perror("epoll ctl del err");
        }
}


int create_socket();
int main()
{
        int sockfd = create_socket();
        assert(sockfd != -1);
        //由于是对应多个客户端,因此我们要把文件描述符收集起来
        //之后再来检测有没有我们关心的事件产生

        int epfd = epoll_create(MAXFD);
        assert(epfd!=-1);

        epoll_add(epfd,sockfd);
        //往内核实现表中添加元素,检测是否有数据
        //获取就绪描述符

        struct epoll_event evs[MAXFD];

        while(1)
        {
                int n=epoll_wait(epfd,evs,MAXFD,5000);//5000ms=5s
                if(n==-1)
                {
                        perror("epoll wait err\n");
                }
                else if(n==0)
                {
                        printf("time out\n");
                }
                else
                {
                        int i=0;
                        for(;i<n;++i)
                        {
                                int fd=evs[i].data.fd;

                                if(evs[i].events&EPOLLIN)
                                {
                                        //描述符不同处理方法不同
                                        if(fd==sockfd)
                                        {
                                                //accept
                                                struct sockaddr_in caddr;
                                                int len = sizeof(caddr);
                                                int c=accept(sockfd,(struct sockaddr*)&caddr,&len);

                                                if(c<0)
                                                {
                                                        continue;
                                                }
                                                printf("accept c=%d\n",c);
                                                epoll_add(epfd,c);
                                        }
                                        else
                                        {
                                                //recv
                                                char buff[128]={0};
                                                int num =recv(fd,buff,127,0);
                                                if(num<=0)
                                                {
                                                        epoll_del(epfd,fd);
                                                        close(fd);
                                                        printf("client close\n");
                                                        continue;
                                                }
                                                printf("buff(%d)=%s\n",fd,buff);
                                                send(fd,"ok",2,0);
                                        }
                                }
                        }
                }
        }

}
int create_socket()
{
        int sockfd =socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }

        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port=htons(6000);
        saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

        int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        res = listen(sockfd,5);
        if(res == -1)
        {
                return -1;
        }
        return sockfd;
}
2.2.ET模式

Q:如何开启ET模式
A:在添加事件时或一个EPOLLET即:

void epoll_add(int epfd ,int fd)
{
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLET;///在这里!!!!
        ev.data.fd=fd;

        if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
        {
                perror("epoll ctl add err");
        }
}
        
2.2.1.ET和LT的区别

在这里插入图片描述

2.2.2.ET模式下的服务器代码(只提醒一次,因此要在收到一次提醒后,把所有数据读完)

accept方法也需要循环读取,此代码并未修改这个部分
ET模式下会产生矛盾:
1.收到提示后,如果只读一次,如果没读完,不会再提醒。
2.如果第一次读完后再去读第二次,如果没数据了,recv会阻塞。

因此将思路设置为:
1.将描述符设置为非阻塞模式。
2.循环读取数据,直到数据读取完成

步骤:
1.在添加事件类型时,添加上EPOLLET;(开启ET模式)
2.描述符需要设置为非阻塞模式(fcntl方法)
3.在io函数返回后,提醒我们描述符上有读事件产生。采用循环处理,直到数据被读完

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<errno.h>


#define MAXFD 10
/!!!!!!!!!!!!!///
//1.将描述符设为非阻塞//
void setnonblock(int fd)
{
        int oldfl = fcntl(fd,F_GETFL);
        int newfl = oldfl | O_NONBLOCK;

        if(fcntl(fd,F_SETFL,newfl) == -1)
        {
                perror("fcntl error\n");
        }
}
//!!!!!!!!//

//此时只关注读事件,因此不传入事件
void epoll_add(int epfd ,int fd)
{
        struct epoll_event ev;
        ev.events = EPOLLIN;//read
        ev.data.fd=fd;
        //每添加一个描述符,都将其设为非阻塞模式!!!!        
        setnonblock(fd);
        /!!!!!!!!!!!!

        if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
        {
                perror("epoll ctl add err");
        }
}
void epoll_del(int epfd,int fd)
{
        if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1)
        {
                perror("epoll ctl del err");
        }
}


int create_socket();
int main()
{
        int sockfd = create_socket();
        assert(sockfd != -1);
        //由于是对应多个客户端,因此我们要把文件描述符收集起来
        //之后再来检测有没有我们关心的事件产生

        int epfd = epoll_create(MAXFD);
        assert(epfd!=-1);

        epoll_add(epfd,sockfd);
        //往内核实现表中添加元素,检测是否有数据
        //获取就绪描述符

        struct epoll_event evs[MAXFD];

        while(1)
        {
                int n=epoll_wait(epfd,evs,MAXFD,5000);//5000ms=5s
                if(n==-1)
                {
                        perror("epoll wait err\n");
                }
                else if(n==0)
                {
                        printf("time out\n");
                }
                else
                {
                        int i=0;
                        for(;i<n;++i)
                        {
                                int fd=evs[i].data.fd;

                                if(evs[i].events&EPOLLIN)
                                {
                                        //描述符不同处理方法不同
                                        if(fd==sockfd)
                                        {
                                                //accept
                                                struct sockaddr_in caddr;
                                                int len = sizeof(caddr);
                                                int c=accept(sockfd,(struct sockaddr*)&caddr,&len);

                                                if(c<0)
                                                {
                                                        continue;
                                                }
                                                printf("accept c=%d\n",c);
                                                epoll_add(epfd,c);
                                        }
                                        else
                                        {
                                                //recv
                                                //修改对于连接套接字的处理方式(循环读取)
                                               while(1)
                                                {
                                                        char buff[128]={0};
                                                        int num =recv(fd,buff,1,0);
                                                        if(num == -1)
                                                        {
                                                                if(errno != EAGAIN && errno != EWOULDBLOCK)
                                                                {
                                                                        perror("recv err");
                                                                }
                                                                else //这种情况只是因为数据读完了,因此回复ok(非阻塞情况下,缓冲区中没有数据)
                                                                {
                                                                        send(fd,"ok",2,0);
                                                                }
                                                                break;
                                                        }
                                                        else if(num==0)
                                                        {
                                                                epoll_del(epfd,fd);
                                                                close(fd);
                                                                printf("client close\n");
                                                                break;
                                                        }
                                                        else
                                                        {
                                                                printf("rec:%s\n",buff);
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }

}
int create_socket()
{
        int sockfd =socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }

        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family=AF_INET;
        saddr.sin_port=htons(6000);
        saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

        int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        res = listen(sockfd,5);
        if(res == -1)
        {
                return -1;
        }
        return sockfd;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值