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函数

     

原文地址:https://www.cnblogs.com/yxnrh/p/11579010.html