• Linux基础(06)IO复用


    在Windows文件指的就是普通的肉眼可见的文件 , 而Linux一切皆文件  https://blog.csdn.net/nan_nan_nan_nan_nan/article/details/81233599

    一定要注意生成文件的警告和报错,不能忽略了!!!!!!!

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
        FILE* fp;
        char buf[]="hello world";
        char buff2[128];
        if((fp = fopen("1.txt","w+")) == NULL)
        {
            perror("file open failure");
            exit(1);
        }
        fwrite(buf , sizeof(buf), 1 , fp);
        memset(buff2,0,sizeof(buff2));
        fseek(fp,0,SEEK_SET);
        fread(buff2 ,sizeof(buff2) ,1, fp);
        printf(">>%s
    ",buff2);
        getchar();
        fclose(fp);
        return 0;
    }

    1.标准流和流功能  write和read 可以对任何文件读写

      stdin  0  标准输入

      stdout  1  标准输出

      stderr  2  标准错误(报错)

      可以使用write代替printf , printf是实现比较复杂局限性也小

      write(目的/0, buff , length)  目的是写入的目标文件或使用上面的std进行打印和输出到控制台 , buff是要写入的数据 , length是写入的大小

      FILE* 是指向一个内存  open返回的是一个句柄


    2.缓冲区

      1.缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

      2.缓冲区有分为下列几种

      _IOFBF 全缓冲  | 全缓冲区 默认大小BUFSIZE 4096  fflush() https://baike.baidu.com/item/fflush/10942194?fr=aladdin  默认开启全缓冲区

      _ IOLBF 行缓冲 | 行缓冲区 遇到换行符才进行刷新 参考Linux终端和scanf

      _IONBF 无缓冲 | 无缓冲区 stdio库 参考read, write ,stderr 都是不带缓冲区的

      指定缓冲区 setbuf(FILE* stream , char* buf);  buf的长度必须是指向长度为BUFSIZE的缓冲区

            setbuffer(FILE* stream , char* buf , size_t size);

            setlinebuf(FILE* stream);

      3.设置缓冲区的函数

        void setbuf(FILE *stream, char *buf);
        void setbuffer(FILE *stream, char *buf, size_t size);
        void setlinebuf(FILE *stream);
        int setvbuf(FILE *stream, char *buf, int mode , size_t size);

      4.为什么使用setvbuf函数  

      如果你的内存足够大,可以把文件IO的BUF设置大一些,这样每次你用fopen/fread/fwrite/fscanf/fprintf语句的时候,都会在内存里操作,减少内存到磁盘IO读写的操作次数,提高系统效率。如果你的程序的功能涉及到类似数据库、视频、音频、图像处理等大量需要爆发式磁盘到内存的IO情况下,可以考虑用setvbuf进行优化内存IO,其他情况下可以不考虑,LINUX/WINDOWS会自动处理这些问题。


    3.fopen的权限  https://blog.csdn.net/gettogetto/article/details/72867757

      函数原型:FILE * fopen(const char * path,const char * mode);

      mode:

      “r” 以只读方式打开文件,该文件必须存在。

      “r+” 以可读写方式打开文件,该文件必须存在。
      ”rb+“ 读写打开一个二进制文件,允许读写数据(可以任意修改),文件必须存在。
      “w” 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
      “w+” 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
      “a” 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
      ”a+“ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
      “wb” 只写打开或新建一个二进制文件;只允许写数据(若文件存在则文件长度清为零,即该文件内容会消失)。
      “wb+” 读写打开或建立一个二进制文件,允许读和写(若文件存在则文件长度清为零,即该文件内容会消失)
      “ab” 追加打开一个二进制文件,并在文件末尾写数据
      “ab+” 读写打开一个二进制文件,允许读,或在文件末追加数据


    4.文件读写函数

      write read (fd , buff , size(buff));  任何文件都可读写 如:txt ,套接字等等...  

      fwrite fread (buff , sizeof(buff), 1 , fp); 只能读写标准文件

      fgetc和getc他们的区别并不是在他们的使用上,而是在他们的实现上!具体来说,就是带f的(fgetc、fputc)实现的时候是通过函数来实现的,而不带f(putc、getc)的实现的时候是通过宏定义来实现的!

      char *gets(char *str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

      char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。

      int getc(FILE *stream)   int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

      int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符。

      int puts(const char *str) 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。

      int fputc(int char, FILE *stream)  int putc(int char, FILE *stream)  把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。


     5.文件流定位

      iint fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

        whence :  (SEEK_SET  文件开始的位置)(SEEK_CUR 文件的当前位置)(SEEK_END 文件结束的的位置)

      long int ftell(FILE *stream) 返回文件读写指针当前的位置距离开头的字节数

      void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。

      需求:  获得当前文件的真实大小 实现:fseek到文件end 再 ftell 获得


    6.标准文件和socket文件的区别和联系  以上所述都是标准的IO , 以下的是socketIO

      区别:

        文件的量 不同  (网络文件的量更加庞大)

        处理的实时性要求不同  (文件不能提前获取(如电影缓冲好的和未缓冲好的区别) , 标准文件是以阻塞的方式进行的在网络文件行不通)

        

      联系: 都是文件可以使用相同的函数


    7.socket的IO模型  IO模型 https://www.cnblogs.com/LittleHann/p/3897910.html

              阻塞非阻塞区别 https://baijiahao.baidu.com/s?id=1623908582750588151&wfr=spider&for=pc

                      以下对照IO模型

    阻塞IO处理   如: read() 如果没有收到对端数据 , 内核会处于挂起状态直到有数据才会往下执行

    非阻塞IO处理 如: 把read()设置成非阻塞 无论是否收到数据都会立刻返回,再不断的访问socket(也是一种阻塞) ,直到有数据才往下执行

    IO复用式    一个IO复用到多个IO , 并一起等待 , 一个文件夹里有多个文件,一起阻塞到内核里

    信号驱动式IO  不等待socket是否有数据, 当socket有数据时会发起信号并优先处理数据

    信号驱动式IO:  当在执行程序时, 绑定的信号IO发起信号时, 挂起当前执行的程序去处理发起信号的IO,处理完信号后再继续之前的操作

    异步IO:  前面所述都会占用IO,一直等待内核拷贝完数据返回后才执行下个IO 无法同时并发处理多个IO

         同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读

         写操作。异步文件IO也就是重叠IO允许一个或多个线程同时发出IO请求。

         异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了


    8.IO复用的使用与流程

     IO复用:  IO复用是把多个IO的fd加入到fd_set这个文件集里,多个IO共同使用一个select的fd (一起阻塞), select()轮询fd_set , 但凡有数据的IO则立刻返回

          select poll epoll 

        selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就立刻返回往下执行

        select()的详解 https://blog.csdn.net/jiaqi_327/article/details/25657601

           分配 struct fd_set rfds;  创建IO复用的文件集

           设置  初始化FD_ZERO(&rfds);  加入句柄FD_SET(fd , &rfds);把fd加入到rfds  移除句柄FD_CLR(fd, &rfds));把fd移出rfds

           使用  int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);           

                  int nfds 文件集合中文件描述符最大值(fd是int类型,每个进程默认有3个标准fd,从3开始+1)+1

        比如stdin(0) , stdout(1) , 因为select的起始位置不同,fd是从0开始的 ,select是从1开始所以要+1(0+1才能指向stdin代表第一个fd)

                  freadfds, writefds, exceptfds 是可读文件节点集, 可写文件节点集, 检查节点错误集,(给freadfds参数就行了其他NULL)
                  struct timeval *timeout  设置select的间隔时间,超过时间立刻返回
                  struct timeval {  创建对象设置好后传入
                          long tv_sec; /* seconds */  秒
                          long tv_usec; /* microseconds */  微秒};

         int FD_ISSET(int fd, fd_set *set)是一个宏,不是函数,作用就是检察一下现在是否有数据可读。  通过select返回后(证明有数据)不再进行阻塞

                经过第一次select后 , 每次重新select之前都要重新加入FD_SET ,因为select会改变rfds里的值

       inet_aton(const char* cp,struct in_addr* inp) 将cp所指的网络地址字符串转换成网络使用的二进制的数,然后存于inp所指的in_addr结构中

       inet_ntoa相反

       continue回到while(1)

       select的第二个参数在select之前是作为参数传入 , select之后 改变了rfds的值后作为返回数据的指针返回出来

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/time.h>
    #include <sys/types.h>
    
    #define MAXBUF 1024
    
    int main(int argc, char **argv)
    {
            int sockfd, new_fd;
            socklen_t len;
            struct sockaddr_in my_addr, their_addr;
            unsigned int myport, lisnum;
            char buf[MAXBUF + 1];
            fd_set rfds;        //创建IO复用的容器
            struct timeval tv;    //select的超时时间
            int retval, maxfd = -1;
    
            if (argv[2])
                    myport = atoi(argv[2]);        //输入端口
            else
                    myport = 7838;
            if (argv[3])
                    lisnum = atoi(argv[3]);        //输入最大连接数,也可以NULL
            else
                    lisnum = 2;
            if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {        //创建socket对象
                    perror("socket");
                    exit(EXIT_FAILURE);
            }
    
            bzero(&my_addr, sizeof(my_addr));    //置0
            my_addr.sin_family = PF_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, sizeof(struct sockaddr)) == -1) {    //绑定
               perror("bind");
               exit(EXIT_FAILURE);
            }
             if (listen(sockfd, lisnum) == -1) {        //监听
                perror("listen");
                exit(EXIT_FAILURE);
             }
    
             while (1) 
             {
                   printf ("
    ----wait for new connect
    ");
                len = sizeof(struct sockaddr);
                if ((new_fd =accept(sockfd, (struct sockaddr *) &their_addr,&len)) == -1) {        //接受连接并保存对端信息
                    perror("accept");
                     exit(errno);
                } else
                       printf("server: got connection from %s, port %d, socket %d
    ", 
                            inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port), new_fd);
                while (1) 
                {
                      FD_ZERO(&rfds);        //把IO复用的容器(rfds)置0
                       FD_SET(0, &rfds);    //把stdin加入到 rfds 
                    FD_SET(new_fd, &rfds);    //把new_fd加入到 rfds 
                    maxfd = new_fd;
                    tv.tv_sec = 1;        //超时1秒
                    tv.tv_usec = 0;
                    retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);        //阻塞 , 轮询加入到rfds里的fd ,超时后跳过阻塞往下执行
                    if (retval == -1) 
                    {
                        perror("select");
                        exit(EXIT_FAILURE);
                    } else if (retval == 0) {    //如果select返回0证明没有数据更新
                             continue;    //跳过当前循环,强制开始下一次循环
                    } 
                    else
                    {
                        if (FD_ISSET(0, &rfds))        //FD_ISSET 判断stdin是否有可读数据
                        {
                                 bzero(buf, MAXBUF + 1);
                                fgets(buf, MAXBUF, stdin);    //获取stdin里的可读数据存入buf
                                  if (!strncasecmp(buf, "quit", 4)) {
                                    printf("i will quit!
    ");
                                     break;
                                  }
                                 len = send(new_fd, buf, strlen(buf) - 1, 0);    //send  buf
                                if (len > 0)
                                       printf ("send successful,%d byte send!
    ",len);
                                  else {
                                      printf("send failure!");
                                    break;
                                }
                        }
                        if (FD_ISSET(new_fd, &rfds)) //FD_ISSET 判断new_fd是否有可读数据
                        { 
                                   bzero(buf, MAXBUF + 1);
                                  len = recv(new_fd, buf, MAXBUF, 0);    //读取socket(new_fd)的可读数据
                                   if (len > 0)
                                       printf ("recv success :'%s',%dbyte recv
    ", buf, len);
                                   else
                                {
                                     if (len < 0)
                                        printf("recv failure
    ");
                                       else
                                    {
                                          printf("the ohter one end ,quit
    ");
                                         break;
                                    }
                                   }
                           }
                     }
                }
                close(new_fd);
                printf("need othe connecdt (no->quit)");
                fflush(stdout);    //清空stdout的缓冲区
                bzero(buf, MAXBUF + 1);
                fgets(buf, MAXBUF, stdin);
                if (!strncasecmp(buf, "no", 2)) 
                {
                    printf("quit!
    ");
                    break;
                }
            }
            close(sockfd);
            return 0;
    }
    select_sever
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <resolv.h>
    #include <stdlib.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sys/types.h>
    
    #define MAXBUF 1024
    int main(int argc, char **argv)
    {
        int sockfd, len;
        struct sockaddr_in dest;
        char buffer[MAXBUF + 1];
        fd_set rfds;
        struct timeval tv;
        int retval, maxfd = -1;
    
        if (argc != 3) 
        {
            printf("argv format errno,pls:
    		%s IP port
    ",argv[0], argv[0]);
            exit(EXIT_FAILURE);
        }
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        {
            perror("Socket");
            exit(EXIT_FAILURE);
        }
    
        bzero(&dest, sizeof(dest));
        dest.sin_family = AF_INET;
        dest.sin_port = htons(atoi(argv[2]));
        if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) 
        {
            perror(argv[1]);
            exit(EXIT_FAILURE);
        }
    
        if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) 
        {
            perror("Connect ");
            exit(EXIT_FAILURE);
        }
    
        printf("
    get ready pls chat
    ");
        while (1) 
        {
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            FD_SET(sockfd, &rfds);
            maxfd = sockfd;
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
            if (retval == -1) 
            {
                printf("select %s", strerror(errno));
                break;
            } 
            else if (retval == 0)
                continue;
            else
            {
                if (FD_ISSET(sockfd, &rfds)) 
                {
                    bzero(buffer, MAXBUF + 1);
                    len = recv(sockfd, buffer, MAXBUF, 0);
                    if (len > 0)
                        printf ("recv message:'%s',%d byte recv
    ",buffer, len);
                    else 
                    {
                        if (len < 0)
                            printf ("message recv failure
    ");
                        else
                        {
                            printf("the othe quit ,quit
    ");
                            break;
                        }
                    }
                }
                if (FD_ISSET(0, &rfds)) 
                {
                    bzero(buffer, MAXBUF + 1);
                    fgets(buffer, MAXBUF, stdin);
                    if (!strncasecmp(buffer, "quit", 4)) {
                        printf("i will quit
    ");
                        break;
                    }
                    len = send(sockfd, buffer, strlen(buffer) - 1, 0);
                    if (len < 0) {
                        printf ("message send failure");
                        break;
                    } else
                        printf
                            ("send success,%d byte send
    ",len);
                }
            }
        }
        close(sockfd);
        return 0;
    } 
    select_client

     利用数组存放不确定数量的IO句柄fd判断其状态(数组全是-1,如果有句柄fd存放将改变状态(-1变成select的返回值),无数据时fd返回0,有数据时fd返回数据大小,而并非-1(error除外))

      利用循环把数组里(要select的)的句柄加入到fd_set, 轮询过后再利用循环更新rfds

    #include<stdio.h>
    #include<sys/types.h>
    #include<stdlib.h>
    #include<sys/socket.h>
    #include<sys/select.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<unistd.h>
    #include<string.h>
    
    #define _BACKLOG_ 5 //监听队列里允许等待的最大值
    
    //当不确定有多少需要重载fd_set的fd时,把fd存入一个数组,方便循环重载
    int fds[20];//用来存放需要处理的IO事件
    
    int creat_sock(char *ip,char *port)
    {
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock < 0){
            perror("creat_sock error");
            exit(1);
        }
    
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(atoi(port));
        local.sin_addr.s_addr = inet_addr(ip);
        
        if( bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
            perror("bind");
            exit(2);
         }
    
        if(listen(sock,_BACKLOG_) < 0 ){
            perror("listen");
            exit(4);
         }
    
        return sock;
    }
    
    int main(int argc,char* argv[])
    {
        if(argc != 3){
            printf("Please use : %s [ip] [port]
    ",argv[0]);
            exit(3);
        }
        int listen_sock = creat_sock(argv[1],argv[2]);    //创建socket对象
    
        size_t fds_num = sizeof(fds)/sizeof(fds[0]);    //获得数组长度
        size_t i = 0;
        for(;i < fds_num;++i)    //在socket里0也是句柄, 所以全部-1 , 以确定数组状态
        {
            fds[i] = -1;
        }
        
        int max_fd = listen_sock;    //确定最大fd    select时+1
        
        fd_set rset;    //创建rfds
        while(1)
        {        
            FD_ZERO(&rset);        //置0
            FD_SET(listen_sock,&rset);    //把本地fd (加入重载)rfds
            max_fd = listen_sock;
            //struct timeval timeout = {20 , 0};
            fds[0]=listen_sock;        //确定第一个fd是本地直接加入
            size_t i = 0;
            for(i=1;i < fds_num;++i)    //从第二个开始加入fd
            {
                if(fds[i] > 0 ){        //大于0说明有fd
                    FD_SET(fds[i] ,&rset);    //把fd (加入重载)rfds
                    if(max_fd < fds[i])    //冒泡算法,找出最大的fd
                    {
                        max_fd = fds[i];
                    }
                }
            }
    
            switch(select(max_fd+1,&rset,NULL,NULL,NULL))    //select最大fd+1
            {
                case -1:
                    perror("select");
                    break;
                case 0:
                    printf("time out..
    ");
                    break;
                default:
                {
                    size_t i = 0;
                    for(;i < fds_num;++i)
                    {
                     //当为listen_socket事件就绪的时候,就表明有新的连接请求
                        if(FD_ISSET(fds[i],&rset) && fds[i] == listen_sock)        //判断数组里的第一个fd(本地fd)是否有数据
                        {
                            struct sockaddr_in client;
                            int accept_sock = accept(listen_sock,(struct sockaddr*)&client,sizeof(client));    //接受连接并保存对端信息
                            if(accept_sock < 0){
                                perror("accept");
                                exit(5);
                            }
                            //char * paddr=NULL;
                            //char * saddr=NULL;
                            //paddr=inet_ntoa(client.sin_addr);
                            //saddr=inet_ntoa(client.sin_addr);
                            printf("connect by a client, ip:%s port:%d
    ",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
    
                            size_t i = 0;
                            for(;i < fds_num;++i)//将新接受的描述符存入集合中
                            {
                                if(fds[i] == -1){
                                    fds[i] = accept_sock;
                                    break;
                                }
                            }
                            if(i == fds_num)
                            {
                                close(accept_sock);
                            }
                        }
                        //普通请求
                        else if(FD_ISSET(fds[i],&rset) && (fds[i] > 0))
                        {
                            char buf[1024];
                            memset(buf,'',sizeof(buf));
                            ssize_t size = read(fds[i],buf,sizeof(buf)-1);
                            if(size < 0){
                                perror("read");
                                exit(6);
                            }else if(size == 0){
                                printf("client close..
    ");
                                close(fds[i]);
                                fds[i] = -1;
                            }else{
                                printf("client say: %s
    ",buf);
                            }
                            
                        }
                        else{}
                    }
                    
                }
                break;
            }
        }
        return 0;
    }
    更优化的select使用

    9.IO复用的内核实现      Linux应用层的阻塞都可能会被信号所中断(应用层的中断叫可中断睡眠状态) 

      9.1 内核的实现其实是一个面向对象 步骤:

        

      面向对象三步骤: (回顾04所描述的 , Linux一切皆文件(内核设计的核心之一,链表: 利用结构体里的函数指针针对不同的需求,进行不同的分配(设置函数指针指向驱动(实现需求的)函数) , 再把对象注册(加入)到管理链表的函数里使用)完成面向对象设计的思路)

        注册对象: 内核里注册一个file_operations对象

        分配对象: 把结构体里的函数指针分别指向对应的函数驱动(等同于赋值吧)

        使用对象: 使用file提供的register(加入链表)函数, 把file_operations提交到file里

      应用层要使用select,通过一系列的查找,找到file---->file_operations------->select(select其实是调用了poll的驱动函数)逐层查找

      9.2: poll的实现和使用差不多(一般不会单独使用poll) 使用: https://blog.csdn.net/zhuxiaoping54532/article/details/51701549

                   select 和 poll 和epoll的   区别: https://www.cnblogs.com/aspirant/p/9166944.html

      9.3 epoll的实现流程和使用 (IO复用大多数情况都是使用epoll了)

       select和poll受限于fd(数量有1000就影响效率了,并不能满足高并发的服务器), epoll将事件抽象成event(不受限于fd)

      epoll_even的结构:
    struct epoll_event
    {
        uint32_t events;   /* 事件的类型 */
        epoll_data_t data;    /* 事件的信息 */
    } __attribute__ ((__packed__));
    
    typedef union epoll_data
    {
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;

      一样是面向对象三步骤:

        创建epollfd对象

          int epoll_create(int size) //size = 监听事件的数量

          返回值 epoll_fd  , epoll_ event

        设置epollfd对象

          创建epoll_event对象

            struct epoll_event ep_ev  

          设置epoll_event对象

            ep_ev.events=(EPOLLIN 输入事件)(EPOLLOUT 输出事件)...  监听的事件类型,读取或者写入

            ep_ev.events.fd=listen_sock  监听句柄是否有事件产生

          使用:epoll_event对象 https://www.cnblogs.com/Dream-Chaser/p/7401184.html

            int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, epoll_event, ep_ev)
        使用epollfd对象
          int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout); events用来做返回的

    http://blog.sina.com.cn/s/blog_488531130100i706.html
    大白话的讲就是, 创建epoll_fd容器,里面放的是一个个的epoll_event事件, 事件对listen_sock监听,如果listen_sock有对客户端传入
    的数据进行读取(进而产生事件)则进行一系列的返回, 而epoll_wait进行轮询(就是监听)也有超时(和select作用一样), epoll_ctl的作用是把事件
    加入到epoll_fd里
    epoll使用的精髓  https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html
    #include<stdio.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<sys/epoll.h>
    #include<fcntl.h>
    #include<stdlib.h>
    #include<string.h>
    
    int creat_socket(char *ip,char* port)
    {
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock < 0){
            perror("socket");
            exit(2);
        }
    
        //调用setsockopt使当server先断开时避免进入TIME_WAIT状态,
         将其属性设定为SO_REUSEADDR,使其地址信息可被重用
        int opt = 1;
        if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) < 0){
            perror("setsockopt");
            exit(3);
        }
    
        struct sockaddr_in local;
    
        local.sin_family = AF_INET;
        local.sin_port = htons(atoi(port));
        local.sin_addr.s_addr = inet_addr(ip);
    
        if( bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0 ){
            perror("bind");
            exit(4);
        }
    
        if(listen(sock,5) < 0){
            perror("listen");
            exit(5);
        }
        
        printf("listen and bind succeed
    ");
    
        return sock;
    }
    
    int set_noblock(int sock)
    {
        int fl = fcntl(sock,F_GETFL);
        return fcntl(sock,F_SETFL,fl|O_NONBLOCK);
    }
    
    int main(int argc,char *argv[])
    {
        if(argc != 3){
            printf("please use:%s [ip] [port]",argv[0]);
            exit(1);
        }
        int listen_sock = creat_socket(argv[1],argv[2]);    //创建socket
    
        int epoll_fd = epoll_create(256);                    //创建epoll_fd容器
        if(epoll_fd < 0){
            perror("epoll creat");
            exit(6);
        }
    
        struct epoll_event ep_ev;            //创建事件
        ep_ev.events = EPOLLIN;                //事件的类型 IN读取
        ep_ev.data.fd = listen_sock;        //产生事件的对象 listen_sock
    
        //添加关心的事件
        //把事件和对象追加到epoll_fd
        if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev) < 0){
            perror("epoll_ctl");
            exit(7);
        }
    
        struct epoll_event ready_ev[128];//申请空间来放就绪的事件。
        int maxnum = 128;
        int timeout = 1000;//设置超时时间,若为-1,则永久阻塞等待。
        int ret = 0;
        
        int done = 0;
        //把产生的事件放入ready_ev里,再循环处理每一个事件, 先判断事件是否为socket且是PEOLLIN, 如果是则把socket里的事件
        追加到epoll_fd中,退出当次循环,进行下一次循环. 其实就是把所以事件都当成普通IO进行处理,因为epoll_event里有fd所以可以使用
        recv read等读写函数
        while(!done){
                    //阻塞: 轮询epoll_fd里的所有事件直到(fd对象)产生事件并返回事件数量,或者超时后返回0往下执行
                            产生的事件会存放到ready_ev里
            switch(ret = epoll_wait(epoll_fd,ready_ev,maxnum,timeout)){
                case -1:
                    perror("epoll_wait");
                    break;
                case 0:
                    printf("time out...
    ");
                    break;
                default://至少有一个事件就绪
                {
                    int i = 0;
                    for(;i < ret;++i){    //循环处理epoll_fd里的事件
                        //判断是否为监听套接字,是的话accept
                        int fd = ready_ev[i].data.fd; 
                        if((fd == listen_sock) && (ready_ev[i].events & EPOLLIN)){
                            struct sockaddr_in remote;
                            socklen_t len = sizeof(remote);
    
                            int accept_sock = accept(listen_sock,(struct sockaddr*)&remote,&len);
                            if(accept_sock < 0){
                                perror("accept");
                                continue;
                            }
                            printf("accept a client..[ip]: %s,[port]: %d
    ",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));
                            //将新的事件添加到epoll集合中
                            ep_ev.events = EPOLLIN | EPOLLET;
                            ep_ev.data.fd = accept_sock;
    
                            set_noblock(accept_sock);    //设置成非阻塞
    
                            if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,accept_sock,&ep_ev) < 0){
                                perror("epoll_ctl");
                                close(accept_sock);
                            }
                        }
                        else{//普通IO
                             if(ready_ev[i].events & EPOLLIN){
                                 //申请空间同时存文件描述符和缓冲区地址
    
                                 char buf[102400];
                                 memset(buf,'',sizeof(buf));
    
                                 ssize_t _s = recv(fd,buf,sizeof(buf)-1,0);
                                 if(_s < 0){
                                     perror("recv");
                                     continue;
                                 }else if(_s == 0){
                                     printf("remote close..
    ");
                                    //远端关闭了,进行善后
                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
                                     close(fd);
                                 }else{
                                     //读取成功,输出数据
                                     printf("client# %s",buf);
                                     fflush(stdout);
    
                                     //将事件改写为关心事件,进行回写
                                     ep_ev.data.fd = fd;
                                     ep_ev.events = EPOLLOUT | EPOLLET;
    
                                     //在epoll实例中更改同一个事件
                                     epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ep_ev);
                                 }
                             }else if(ready_ev[i].events & EPOLLOUT){
                                     const char*msg = "HTTP/1.1 200 OK 
    
    <h1> hi girl </h1>
    ";
                                     send(fd,msg,strlen(msg),0);
                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
                                     close(fd);
                                }
                            }
                        }
                    }
                    break;
                
            }
        }
        close(listen_sock);
        return 0;
    }
    tcp_epoll

    总结: IO复用是一种机制,一个进程可以监听多个描述符,一旦某个描述符就绪(读就绪和写就绪),能够对程序进行相应的读写操作

      目前支持I/O复用的系统调用有select,poll,pselect,epoll,本质上这些I/O复用技术是同步I/O技术。一般都是使用epoll的

      与多进程和多线程相比,I/O复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

      select(): 创建fd_set结构体对象rfds(这是一个保存文件描述符的文件数组) , FD_ZERO(&rfds)置空rfds , FD_SET(fd, &rfds)文件描述符加入到rfds, 

          select(nfds+1, &rfds, NULL, NULL, &timeout);轮询监听 rfds 里的文件状态是否改变, 返回状态改变的fd的数量, 无则返回0 超时, error返回-1

          FD_ISSET(fd, &rfds) 判断状态改变的fd是否是当前fd ,是则返回1 ,否则返回0 ,技巧:可以把fd放入一个数组通过下标 ,加入和判断

      epoll():  把监听fd状态的改变 ,转变成监听事件的发生  详细 https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html

    epoll_fd = epoll_create(size)    //创建事件容器和其大小
    struct epoll_event ep_ev;           //创建事件
    ep_ev.events = EPOLLIN;             //事件的类型   如果发生IN读取,则统一做出对应处理
    ep_ev.data.fd = fd;              //产生事件的对象 ,如果产生事件的是一个socket_fd, 接受连接accept()后把其转化成事件,并加入rfds里
    struct epoll_event ready_ev[128];        //申请空间来放就绪的事件。最大数不能超过创建epoll的大小
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,fd,&ep_ev);  //把fd和其事件类型追加到epoll_fd里
    epoll_wait(epoll_fd,ready_ev,maxnum,timeout);  //等待事件的产生,类似于select()调用 ,把产生的事件依次存入ready_ev里 ,用循环[i]依次处理就绪事件
    关于ET、LT两种工作模式: LT:只要内核缓冲区有数据就一直通知(一直触发),直到读完缓冲区, 这种模式可靠,但低效率
                 ET:只有状态发生变化才通知 ,只触发一次(文件描述符状态变化时),可能会导致数据读不完 ,这种模式不可靠 ,但高效率,
                                                所以要自己实现一个能读取完整缓冲区的recv函数

         

  • 相关阅读:
    设置Jmeter默认为中文, 就是这么简单!
    Jmeter启动报错
    绑定bindchange事件的微信小程序swiper闪烁,抖动问题解决,(将微信小程序切换到后台一段时间,再打开微信小程序,会出现疯狂循环轮播,造成抖动现象)
    vue2.0 事件处理常用修饰符-----------------记录,加强记忆。
    记录一下,PC端vue开发常用框架,已经用过elementUI和iview 接下来尝试另一个Muse-UI 喜欢它的点击效果
    随机获取min和max之间的一个整数
    js在数组arr中随机获取count数量的元素
    滑动时候报错:Unable to preventDefault inside passive event listener, 移动端滑动性能优化
    js小方法,获取知道公历生日 (‘1992-01-19’),获取阴历生日日期,属相,非简单根据年份判断-----------声明:整理自网络!!
    vue组件,通过props父组件给子组件传值,WTF, 子组件报错undefined???
  • 原文地址:https://www.cnblogs.com/yxnrh/p/11579010.html
Copyright © 2020-2023  润新知