zoukankan      html  css  js  c++  java
  • 第十章 linux I/O模型 (文件 socket i/o)

     (1) 阻塞模型

      没有数据到达时候程序会一直阻塞直到有数据到达  如tcp 的recv 函数,当然也可以设置超时。

    (2)非阻塞I/O模型

      通过fcntl函数设置socket 非阻塞模型  fcntl(sockfd, F_SETFL, O_NONBLOCK);如果没有数据到达会返回一个错误码。一般对于一般都对非阻塞I/O模型进行轮询,就是一直在检查这个状态,查看有无数据到达。

    (3)3. I/O复用模型  select模型 (重点)

      利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。

      select参数和返回值意义如下:

      int select (

             IN int nfds,                           //0,无意义

             IN OUT fd_set* readfds,      //检查可读性

             IN OUT fd_set* writefds,     //检查可写性

             IN OUT fd_set* exceptfds,  //例外数据

             IN const struct timeval* timeout);    //函数的返回时间

      返回值:错误:-1,超时:0, 执行成功则返回文件描述词状态已改变的个数。

      参数nfds代表最大的文件描述词加1,   

      参数 timeout为结构timeval,用来设置select()的等待时间,有三种情况,1永远等待,超时参数设置为NULL,2等待一定时间,时间自己设定。3不等待,超时设置为0。

       参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。

      fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:

      FD_CLR( s, *set) 从队列set删除句柄s;

      FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;

      FD_SET( s, *set )把句柄s添加到队列set中;

      FD_ZERO( *set ) 把set队列初始化成空队列.

      Select工作流程:

        1> 用FD_ZERO宏来初始化我们感兴趣的fd_set。

        2> 用FD_SET宏来将套接字句柄分配给相应的fd_set。

        3> 调用select函数。

        4:用FD_ISSET对套接字句柄进行检查。 

    #include <stdio.h> 
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <netinet/in.h> 
    #include <string.h> 
    #include <unistd.h> 
      
    int main(int argc, char *argv[]) 
    { 
        //这个服务器同时监听7777和7778两个端口 
          
        //绑定监听7779端口的fd 
        int listenfd1; 
        struct sockaddr_in serv_addr1; 
        listenfd1 = socket(AF_INET, SOCK_STREAM, 0); 
          
        bzero((char *) &serv_addr1, sizeof(serv_addr1)); 
        serv_addr1.sin_family = AF_INET; 
        serv_addr1.sin_port = htons(7777); 
        serv_addr1.sin_addr.s_addr = INADDR_ANY; 
          
        bind(listenfd1, (struct sockaddr *) &serv_addr1, sizeof(serv_addr1)); 
        listen(listenfd1, 5); 
          
        //绑定监听7778端口的fd 
        int listenfd2; 
        struct sockaddr_in serv_addr2; 
        listenfd2 = socket(AF_INET, SOCK_STREAM, 0); 
          
        bzero((char *) &serv_addr2, sizeof(serv_addr2)); 
        serv_addr2.sin_family = AF_INET; 
        serv_addr2.sin_port = htons(7778); 
        serv_addr2.sin_addr.s_addr = INADDR_ANY; 
          
        bind(listenfd2, (struct sockaddr *) &serv_addr2, sizeof(serv_addr2)); 
        listen(listenfd2, 5); 
          
          
        int maxfd; 
        //为什么这里设置两个fd_set?每次select的时候函数会把没有事件发生的描述字清零,所以需要两个集合 
        fd_set allset, rset; 
        maxfd = listenfd1; 
        if(listenfd2 > maxfd) { 
            maxfd = listenfd2; 
        } 
          
        FD_ZERO(&allset); 
        FD_SET(listenfd1, &allset); 
        FD_SET(listenfd2, &allset); 
          
        int clifd, clilen; 
        struct sockaddr_in cli_addr; 
        char buffer[256]; 
        for(;;) { 
            rset = allset; 
            select(maxfd + 1, &rset, NULL, NULL, NULL); 
              
            //如果是listenfd1 获取消息 
            if(FD_ISSET(listenfd1, &rset)) { 
                clilen = sizeof(cli_addr); 
                clifd = accept(listenfd1, (struct sockaddr *) &cli_addr, &clilen); 
                  
                bzero(buffer, 256); 
                read(clifd, buffer, 255); 
                printf("Listenfd1 Message is:%s
    ", buffer); 
            } 
              
            //如果是listenfd1 获取消息 
            if(FD_ISSET(listenfd2, &rset)) { 
                clilen = sizeof(cli_addr); 
                clifd = accept(listenfd2, (struct sockaddr *) &cli_addr, &clilen); 
                  
                bzero(buffer, 256); 
                read(clifd, buffer, 255); 
                printf("Listenfd2 Message is:%s
    ", buffer); 
            } 
            close(clifd); 
        } 
          
        close(listenfd1); 
        close(listenfd2); 
      
        return 0; 
    }

     

    (4)信号驱动I/O模型

      当socket 有数据到达,内核就会发送 SIGIO 信号,可以调用 sigaction 安装 SIGIO 信号的处理函数,这个时候就可以在 SIGIO 信号处理函数中进行 recv函数来接受数据。

    (5)异步I/O模型  (poll模型 和 epoll模型) (重点)

      注:  这里只讲epoll    poll模型 查阅函数int poll(struct pollfd *ufds, unsigned int nfds, int time­out);

      Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 。

      epoll 模型优点:

        Epoll 没有最大并发连接的限制 

        效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关.

        内存拷贝, Epoll 在这点上使用了“共享内存"

      使用epoll:

        typedef union epoll_data{void *ptr;int fd; /*Socket*/  __uint32_t u32;  __uint64_t u64; } epoll_data_t; 

        struct epoll_event {__uint32_t events;  /*程序关注的事件 */   epoll_data_t data;  };

        其中events可以用以下几个宏的集合:

        EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)  

        EPOLLOUT:表示对应的文件描述符可以写

        EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

        EPOLLERR:表示对应的文件描述符发生错误

        EPOLLHUP:表示对应的文件描述符被挂断;

        EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

        

        1> 生成一个 Epoll 专用的文件描述符  int epoll_create(int size);   size 就是你在这个 Epoll fd 上能关注的最大 socket数.

        2> 控制某个 Epoll 文件描述符上的事件:注册、修改、删除。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event );

           参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。        

           参数op操作类型,有如下取值: EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除

           参数fd   socket

           参数event 指向epoll_event的指针

     

         3> 等待 I/O 事件的发生 int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);  

           参数epfd:由epoll_create 生成的epoll专用的文件描述符;

           参数epoll_event:用于回传等待处理的事件数组; 

           参数maxevents:每次能处理的事件数; 

           参数timeout:等待I/O事件发生的超时值(ms);-1永不超时,直到有事件产生才触发,0立即返回

      

      工作方式:

        1> LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。

        2>ET(edge-triggered):边沿触发,高速工作方式, 只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

      区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。 

    #include stdio.h>
    #include string.h>
    #include unistd.h>
    #include fcntl.h>
    #include errno.h>
    #include sys/types.h>
    #include sys/socket.h>
    #include sys/epoll.h>
    #include netinet/in.h>
    #include pthread.h>
    #include stdlib.h>
    #define SERV_PORT 5358
    #define MAX_CONN 1024
    #define EVENT_NUM 1024
    #define EPOLL_SIZE 1024
    #define BUF_LEN 1024
    
    int setnonblocking(int fd)
    {
        int opts;
        if((opts = fcntl(fd, F_GETFL))  0)
        {
            return -1;
        }
        opts |=O_NONBLOCK;
        if(fcntl(fd, F_SETFL, opts) 0)
        {
            return -1;
        }
        return 0;
    }
    
    
    void *str_echo(void *arg)
    {
        int sockfd;
        ssize_t nread;
        char buf[BUF_LEN] = {0};
        pthread_detach(pthread_self());
        sockfd = *(int *)arg;
        while(1) 
        {
            bzero(buf, BUF_LEN);
            if((nread = read(sockfd, buf, BUF_LEN)) == -1)
            {
                if(errno == EINTR) 
                {
                    continue;
                }
                else 
                {
                    printf("read error: %s
    ", strerror(errno));
                    continue;
                }
            }
            else if (nread == 0) 
            {
                break;
            }
            else 
            {
                //fputs(buf, stdout);
                write(sockfd, buf, nread);
            }
        }
        return NULL;    
    }
    int main(int argc, char **argv)
    {
        int listenfd, connfd, epfd, nfds;
        socklen_t addrlen;
        struct sockaddr_in cliaddr, servaddr;
        struct epoll_event ev, events[EVENT_NUM];
        pthread_t tid;
        //create socket fd
        if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
            printf("Create socket error!
    ");
            return 0;
        }
        if (setnonblocking(listenfd) == -1)
        {
            printf("setnonblicking error!
    ");
            close(listenfd);
            return 0;
        }
        //bind
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
        if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
        {
            printf("Socket bind error!
    ");
            close(listenfd);
            return 0;
        }
        //listen
        if(listen(listenfd, MAX_CONN) == -1)
        {
            printf("listen error
    ");
            close(listenfd);
            return 0;
        }
        //create epoll
        if((epfd = epoll_create(EPOLL_SIZE)) == -1)
        {
            printf("Create epoll error!
    ");
            close(listenfd);
            return 0;
        }
        //register epoll event
        ev.data.fd = listenfd;
        ev.events = EPOLLIN | EPOLLET;
        if ((epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev)) == -1)
        {
            printf("epoll_ctl error!
    ");
            close(listenfd);
            return 0;
        }
        while(1)
        {
            if((nfds = epoll_wait(epfd, events, EVENT_NUM, -1)) ==-1)
            {
                if(errno == EINTR){
                    printf("%s
    ", strerror(errno));
                    continue;
                }
                else{
                    printf("epoll_wait error!
    ");
                    continue;
                }
            }
            int i;
            for(i=0;infds;i++)
            {
                if(events.data.fd == listenfd)
                {
                    addrlen = sizeof(cliaddr);
                    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &addrlen);
                    if(connfd == -1)
                    {
                        printf("%s
    ", strerror(errno));
                        continue;
                    }
                    
                    printf("New Connection %d
    ", connfd);
                    ev.data.fd = connfd;
                    ev.events = EPOLLIN | EPOLLET;
                    if((epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev)) == -1)
                    {
                        printf("connect failed!
    ");
                    }
                }
                else
                {
                    if((pthread_create(&tid, NULL, str_echo, &events.data.fd)) == -1) 
                    {
                        exit(0);
                    }
                }
            }
        }
        return 0;
    }

     

      附加: epoll_data.ptr 可以设置成socket对应的回掉函数,当事件发生时候可以调用该函数处理。 也可设置成其它参数。

     

       

     

  • 相关阅读:
    如何成为一名数据科学家
    暑假反思
    暑假计划(7月23日-8月21日)
    ACM数论模板
    Nelder–Mead method
    Introduction to Data Mining
    51_1037最长循环节 (miller rabin算法 pollard rho算法 原根)
    乘法逆元(转)
    51_1228 序列求和(伯努利数)(转)
    清除input中内容的简单方法
  • 原文地址:https://www.cnblogs.com/wolfrickwang/p/3193419.html
Copyright © 2011-2022 走看看