(linux)i/o复用

目录

1、i/o复用概述:

2、select机制:

(1)、select过程建立如下:

 (2)、select的相关函数:

(3)、select的相关代码演示:

3、epoll机制:

1、epoll实现过程:

2、epoll_event 结构:

3、相关函数:

4、相关命令:


1、i/o复用概述:

       通过一种机制,让单个进程可以监视多个文件描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作

解释:

        i/o复用与非阻塞机制类似,但比非阻塞较好。i/o复用通过循环的请求,但循环请求的对象不单一,一旦满足就进行相应的操作。比如:一个服务端建立,多个对象可以连接服务端,进行相应的操作,也可以采取退出,与服务端断开连接操作。

i/o复用不需要创建进程与线程,也不需要对进程进行维护。其优势就是在于系统开销小

i/o复用有三种:select、epoll、poll(介绍前2种,select是普通的i/o复用,epoll是更高效的i/o复用,poll是select与epoll之间的过度)

2、select机制:

(1)、select过程建立如下:

        1、将需要进行io操作的文件描述符fd添加到select中

        2、阻塞等待select系统调用返回

        3、数据到达后,select函数返回

        4、用户线程正式发起read请求,读取数据并继续执行

select最大优势在于,用户可以在一个线程里可以同时处理多个i/o复用的请求

 (2)、select的相关函数:

        select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型。     

int select(int maxfd, //最大套接字的值+1
           fd_set *rdset,
           fd_set *wrset,
           fd_set *exset,
           struct timeval *timeout
);

参数解释:

fd_set代表文字描述符的集合,2,3,4分别表示读数据、写数据、执行数据集合。内核当中,3个数据会集合成一个,传入时候通常只需要2给参数,其余的给空就行

struct temval *timeout表示时间设置具体结构如下:

struct timeval
{
__time_t tv_sec;        /* Seconds. */
__suseconds_t tv_usec;  /* Microseconds. */
};

返回值如果为-1表示出错,返回值为0表示超时,返回值为其他值的话则表示客户端与服务端成功去的联系。

对于fd_set之类的设置给出了以下3种设置方式

1、将指定的文件描述符集清空     

FD_ZERO(fd_set *fdset);

2、用于在文件描述符集合中增加一个新的文件描述符     

FD_SET(fd_set *fdset);

3、用于测试指定的文件描述符是否在该集合中     

FD_ISSET(int fd,fd_set *fdset);

(3)、select的相关代码演示:

1、服务端代码:

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

#define MAXBUF 1024
int main(int argc,char* argv[]){
    int sockfd;//用来表示socket句柄
    int new_fd;//用来表示客户端连接进来后的句柄
    struct sockaddr_in my_addr,client_addr;//分别为服务端和客户端的操作
    socklen_t len = sizeof(struct sockaddr_in); //存储(struct sockaddr_in )的内存大小
    unsigned int myport,lisnum;//设置端口号和监听的个数
    char buf[MAXBUF + 1];
    int retval,maxfd;

    //select 机制
    fd_set rfds; //fd_set本质为一个 long 类型的数组 8 * 128 = 1024
    /*目的:将每个位置表示为0,将对应操作位置改成1*/
    struct timeval tv;//超时时间

    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket error\n");
        return 1;
    }
    // argv[1]给ip地址,argv[2]给端口号
    if(argv[2])
    {
        myport = atoi(argv[2]);
    }else{
        myport = 7838;
    }

    if(argv[3])
    {
         lisnum = atoi(argv[3]);
    }else{
        lisnum = 2;
    }

    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(myport);
    if(argv[1])
        my_addr.sin_addr.s_addr = inet_addr(argv[1]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;

    if((bind(sockfd,(struct sockaddr*)&my_addr,len)) == -1)
    {
        perror("bind error!\n");
        return 1;
    }

    if(listen(sockfd,lisnum) == -1)
    {
        perror("listen error!\n");
        return 1;
    }

    /*以上前期服务端的准备工作完成,等待客户链接*/
    while (1)
    {
        printf("wait for connect\n");

        if((new_fd = accept(sockfd,(struct sockaddr*)&client_addr,&len)) == -1)
        {
            perror("accept error!\n");
            return 1;
        }else{
            printf("client ip : %s,port : %d, sock : %d\n",
                    inet_ntoa(client_addr.sin_addr),
                    ntohs(client_addr.sin_port),
                    new_fd);
        }
        //以上为阻塞,等待客户端发信息给服务端
        while (1)
        {
            //每一次循环将rfds数组整体清0;
            FD_ZERO(&rfds);
            FD_SET(0,&rfds);
            FD_SET(new_fd,&rfds);
            maxfd = new_fd;
            tv.tv_sec = 100;
            tv.tv_usec = 0;

            retval = select(maxfd + 1,&rfds,NULL,NULL,&tv);
            
            if(retval == -1)
            {
                perror("select error!\n");
                return 1;
            }else if(retval == 0) continue;
            else {
                if(FD_ISSET(0,&rfds))//如果是标准输入返回
                {
                    memset(buf, 0 ,MAXBUF + 1);
                    fgets(buf,MAXBUF,stdin);
                    if(!strncasecmp(buf,"quit",4))//判断输入是否为quit(不分大小写)
                    {
                        printf("will quit\n") ;
                        break;
                        //该break仅退出select机制内循环,链接并未结束
                    }
                    len = send(new_fd,buf,strlen(buf) - 1,0);//将输入的发给用户端
                    if(len < 0)
                    {
                        printf("send error\n");
                        break;
                    }
                }
                if(FD_ISSET(new_fd,&rfds))//判断客户端是否发送消息来
                {
                    memset(buf, 0 ,MAXBUF + 1);
                    len = recv(new_fd,buf,MAXBUF,0);
                    if(len < 0)
                    {
                        printf("recv error\n");
                        break;
                    }else{
                        printf("receive from client message is :%s,byte:%d",buf,len);
                    }
                }
            }
        }
        close(new_fd);
        printf("need other connect (no->quit)");
        fflush(stdout);
        memset(buf,0,MAXBUF + 1);
        fgets(buf,MAXBUF,stdin);
        if(!strncasecmp(buf,"no",2))
        {
            printf("quit\n");
            break;
        }
    }
    
    close(sockfd);
    return 0;
}

代码流程图

 2、客户端发送接受代码:

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

#define MAXBUF 1024

int main(int agrc,char *argv[])
{
    int sockfd;
    struct sockaddr_in My_clientaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    char buf[MAXBUF + 1];
    int retval,maxfd;

    fd_set rfds;
    struct timeval tv;

    if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket error!\n");
        return 1;
    }

    My_clientaddr.sin_family = AF_INET;
    My_clientaddr.sin_port = htons(atoi(argv[2]));
    if(argv[1])
        My_clientaddr.sin_addr.s_addr = inet_addr(argv[1]);
    else
        My_clientaddr.sin_addr.s_addr = INADDR_ANY;

    if(connect(sockfd,(struct sockaddr *)&My_clientaddr,sizeof(My_clientaddr)) != 0)
    {
        perror("connect error\n");
        return 1;
    }

    printf("prepare for receiving.......\n");
    while (1)
        {
            //每一次循环将rfds数组整体清0;
            FD_ZERO(&rfds);
            FD_SET(0,&rfds);
            FD_SET(sockfd,&rfds);
            maxfd = sockfd;
            tv.tv_sec = 100;
            tv.tv_usec = 0;

            retval = select(maxfd + 1,&rfds,NULL,NULL,&tv);
            
            if(retval == -1)
            {
                perror("select error!\n");
                return 1;
            }else if(retval == 0) continue;
            else {
                if(FD_ISSET(0,&rfds))//如果是标准输入返回
                {
                    memset(buf, 0 ,MAXBUF + 1);
                    fgets(buf,MAXBUF,stdin);
                    if(!strncasecmp(buf,"quit",4))//判断输入是否为quit(不分大小写)
                    {
                        printf("will quit\n") ;
                        break;
                        //该break仅退出select机制内循环,链接并未结束
                    }
                    len = send(sockfd,buf,strlen(buf) - 1,0);//将输入的发给用户端
                    if(len < 0)
                    {
                        printf("send error\n");
                        break;
                    }
                }
                if(FD_ISSET(sockfd,&rfds))//判断客户端是否发送消息来
                {
                    memset(buf, 0 ,MAXBUF + 1);
                    len = recv(sockfd,buf,MAXBUF,0);
                    if(len < 0)
                    {
                        printf("recv error\n");
                        break;
                    }else{
                        printf("receive from client message is :%s,byte:%d",buf,len);
                    }
                }
            }
        }
    close(sockfd);
    return 0;
}

效果展示:

 

3、epoll机制:

        一个文件是需要消耗资源的,所以对于文件的监听来说是有限度的。select里面如果监听1024个左右的文件句柄的时候可能会出一些问题,如果超过1024出现的问题更大。每个进程对于file的监控是有限的。所以如果能够脱离文件就会更合适一些。

        为解决上面的问题,实现大规模并发服务器,采取了下方的epoll机制

        epoll 是通过 epoll_fd 对象来实现。

        epoll_fd 简单理解为一个单个的文件,文件中存在多个 epoll_event 对象,每个 epoll_event 都表示一个事件,这些事件都可以添加到epoll_fd对象中。linux 对于事件的处理很方便,所以 epoll 可以实现一个高并发的监听。

1、epoll实现过程:

        1.1、创建 epoll_fd 对象

        1.2、设置 epoll_event 对象     

        1.2.1、创建 epoll_event 对象     

        1.2.2、设置 epoll_event 对象             

                对象.events = EPOLLIN;//数据的读取             

                对象.data.fd = listen_socket;     

         1.2.3、使用epoll_event对象             

int epoll_ctl(int epfd,
              int op,
              int fd,
              struct epoll_event *event);

        1.3、使用 epoll_fd 对象     

int epoll_wait(int epfd,
               struct epoll_event *events,
                int maxevents,
                int timeout);

2、epoll_event 结构:

struct epoll_event {
    __uint32_t events; //事件类型
    epoll_data_t data; //数据
};

EPOLLIN: 表示对应的文件描述符可以读   EPOLLOUT
EPOLLET:表示被 epoll 设为边缘触发

3、相关函数:

3.1、进行各种控制操作以改变已打开文件的的各种属性     

int fcntl(int fd, 
          int cmd, 
          long arg);     
O_NONBLOCK:表示非阻塞i/o

3.2、获取或者设置与某个套接字关联的选项     

int setsockopt(int sock, 
               int level, 
               int optname, 
               const void *optval, 
               socklen_t optlen);    
level:SOL_SOCKET     
optname:SO_REUSEADDR

4、相关命令:

可以作为 client 发起 tcp 或 udp 连接     

nc ip地址 端口号 就可以实现

因此可以不需要写用户端代码

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

small建攻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值