IO 多路转接 select

痴心易碎 提交于 2020-02-19 00:52:40

TCP状态时序图:

    1.主动发起连接请求端:  CLOSE --发送SYN--SEND_SYN --接受 ACK、SYN --SEND_SYN--发送 ACK --ESTABLISHED(数据通信状态)

    2.主动关闭连接请求端;  ESTABLISHED(数据通信状态) --发送FIN --FIN_WAIT_1 --接受 ACK --FIN_WAIT_2(半关闭)

                --接受对端发送 FIN --FIN_WAIT_2(半关闭) --回发 ACK --TIME_WAIT(只有主动关闭连接方会经历该状态)


                 -- 等 2MLS时长(大约40s) --CLOSE

    3.被动接受连接请求端:    CLOSE -- LISTEN --接收 SYN -- LISTEN --发送 ACK SYN --SYN_RCVD --接收ACK --ESTABLISHED

    4.被动关闭连接请求端:    ESTABLISHED -- 接收 FIN --ESTABLISHED --发送ACK

                --CLOSE_WAIT (说明对端【主动关闭连接请求端】处于半关闭状态)

                --发送 FIN--LASK_ACK --接收ACK--CLOSE

    重点记忆: ESTABLISHED 、FIN_WAIT_2 <->CLOSE_WAIT 、TIME_WAIT(2MLS)
2MLS时长:

    一定出现在 主动关闭连接请求端  ---TIME_WAIT
    
    保证,最后一个 ACK 能成功被对端收到。(等待期间,对端没收到我发的 ACK ,对端会再次发送 FIN 请求)

端口复用:

    int opt =1 ; //设置端口复用。

    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt))

半关闭:    


    通信双方中,只有一端关闭通信   --FIN_WAIT_2

    close(lfd);

    shutdown (int fd,int how)


        how:  SHUT_RD  关读端
        
            SHUT_WR  关写端

            SHUT_RDWR  关读写

    shutdown 在关闭多个文件描述符对应的文件时(采用dup2后),采用全关闭方法。close 值关闭一个

select多路IO转接:

    原理:  借助内核,select来监听,客户端连接、数据通信事件

    void FD_CLR(int fd, fd_set *set);   将一个文件描述符从监听集合中 移除
    
        FD_CLR(4,&rset)

        
       int  FD_ISSET(int fd, fd_set *set);  判断一个文件描述符是否在监听集合中

        FD_ISSET(4,&rset)

       void FD_SET(int fd, fd_set *set);    将待监听的文件描述符添加到 监听集合中

        FD_SET(4,&rset)

       void FD_ZERO(fd_set *set);           清空一个文件描述符集合


        FD_ZERO(4,&rset)


     int select(int nfds, fd_set *readfds, fd_set *writefds,

                  fd_set *exceptfds, struct timeval *timeout);
    


        nfds: 监听的所有文件描述符中,最大的文件描述符 +1

        readfds: 读 文件描述符监听集合     传入传出 参数

        writefds: 写 文件描述符监听集合      传入传出 参数


        exceptfds: 异常 文件描述符监听集合     传入传出 参数


        timeout:  》0 : 设置监听超时时长

              NULL  阻塞监听

              0    非阻塞 轮训监听


        返回值:    

            》0 所有监听集合(3个)中, 满足对应事件的总数

            0   没有满足条件 的文件描述符

            -1 : errno

思路分析:

    lfd = socket();         创建套接字

    bind();            绑定地址结构

    listen()            设置监听上限

    fd_set rset,allset;          创建监听集合

    FD_ZERO(&allset)        将监听集合清空

    FD_SET(lfd,&allset)        将lfd添加到读集合中

    while(1){

        rset = allset
    
        ret = select(lfd+1,&rset,NULL,NULL,NULL)  监听文件描述符集合对应事件

        if(ret>0){

        
            if(FD_ISSET(lfd,&rset)){

                cfd =accept()

                FD_SET(cfd,&allset)

            }

            for(i = lfd+1;i<=最大文件描述符;i++){


                FD_ISSET(I,&rset)

                read()

                小 -- 大

                write();

    
            }

        }        


    }


select优缺点:

    缺点: 监听上限受文件描述符限制 最大 1024

        检测满足条件的 fd, 自己添加业务逻辑  提高可编码难度

        返回满足监听条件的文件描述符个数 并没有直接告诉哪些文件描述符就绪了 需要从头遍历传出的事件集合


    优点:跨平台。 

    

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>


#define SERV_PORT  6666

int main(){


  int i,j,n,nready;
  int maxfd =0 ;

  int listenfd,connfd;
  char buf[BUFSIZ];

  struct sockaddr_in clie_addr,serv_addr;
  socklen_t clie_addr_len;

  listenfd = socket(AF_INET,SOCK_STREAM,0);
  int opt =1;
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  bzero(&serv_addr,sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(SERV_PORT);
  serv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
  bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
  listen(listenfd,128);
  fd_set rset,allset;             //定义读集合,备份集合allset
  maxfd = listenfd;               //最大文件描述符
  FD_ZERO(&allset);               //清空监听集合
  FD_SET(listenfd,&allset);       //将fd 添加到集合
  while(1){
    rset = allset;                
    nready = select (maxfd+1,&rset,NULL,NULL,NULL);   //使用select监听
    if(nready<0){
      perror("select error");
      exit(1);
    }
    if(FD_ISSET(listenfd,&rset)){         //listenfd 满足监听的 读事件
      clie_addr_len = sizeof(clie_addr);
      connfd = accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);//建立连接 不阻塞

      FD_SET(connfd,&allset);       //将产生的fd 添加到监听集合中,监听数据读事件
      if(maxfd<connfd)          //修改 maxfd
        maxfd= connfd;
      if(nready == 1)         //说明select只 返回一个 ,并且是listenfd 后续执行无需执行
        continue;

    }
    for(i=listenfd+1;i<=maxfd;++i){   //处理满足读事件的 fd
      if(FD_ISSET(i,&rset)){          //找到满足读事件的那个fd
        if((n=read(i,buf,sizeof(buf)))==0){ //检测到客户端已经关闭连接
          close(i);                 
          FD_CLR(i,&allset);        //将关闭的fd 移除监听集合
        }else if(n>0){
          for(j=0;j<n;j++)
            buf[j]=toupper(buf[j]);
          write(i,buf,n);
          write(STDOUT_FILENO,buf,n);

        }
      }
    }

  }
  close(listenfd);
  return 0;
}
// 优化后 select

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>


#define SERV_PORT  6666

int main(){


  int i,j,n,nready,maxi;
  
  int client[FD_SETSIZE];  //自定义数组client 防止遍历 1024个文件描述符 FD_SETSIZE 为1024
  int maxfd =0 ;

  int listenfd,connfd;
  char buf[BUFSIZ];

  struct sockaddr_in clie_addr,serv_addr;
  socklen_t clie_addr_len;

  listenfd = socket(AF_INET,SOCK_STREAM,0);
  int opt =1;
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  bzero(&serv_addr,sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(SERV_PORT);
  serv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
  bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
  listen(listenfd,128);

  fd_set rset,allset;             //定 读 集和,备份集和allset
  maxfd = listenfd;               //最大文件描述符
  

  maxi = -1;
  for(i=0;i<FD_SETSIZE;++i){      //用-1 初始化 client【】
    client[i]=-1;
  }


  FD_ZERO(&allset);               //清空监听集合
  FD_SET(listenfd,&allset);       //将fd 添加到集合
  while(1){
    rset = allset;                
    nready = select (maxfd+1,&rset,NULL,NULL,NULL);   //使用select监听
    if(nready<0){
      perror("select error");
      exit(1);
    }
    if(FD_ISSET(listenfd,&rset)){         //listenfd 满足监听的 读事件
      clie_addr_len = sizeof(clie_addr);
      connfd = accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);//建立连接 不阻塞


      for(i=0;i<FD_SETSIZE;i++){
        if(client[i]<0){
          client[i]=connfd;
          break;
        }
      }
      if(i==FD_SETSIZE){
        fputs("too many clients\n",stderr);//达到select能监控的 上限 1024
        exit(1);
      }




      FD_SET(connfd,&allset);       //将产生的fd 添加到监听集合中,监听数据读事件
      if(maxfd<connfd)          //修改 maxfd
        maxfd= connfd;

      if(i>maxi)
        maxi =i;     //保证maxi总是存的 client【】最后一个元素下表



      if(nready == 1)         //说明select只 返回一个 ,并且是listenfd 后续执行无需执行
        continue;

    }
    for(i=0;i<=maxi;++i){   //处理满足读事件的 fd
      if((listenfd=client[i])<0)
        continue;

      if(FD_ISSET(client[i],&rset)){          //找到满足读事件的那个fd
        if((n=read(client[i],buf,sizeof(buf)))==0){ //检测到客户端已经关闭连接服务器也关闭
          close(client[i]);                 
          FD_CLR(client[i],&allset);        //将关闭的fd 移除监听集合
          client[i]=-1;
        }else if(n>0){
          for(j=0;j<n;j++)
            buf[j]=toupper(buf[j]);
          write(client[i],buf,n);
          write(STDOUT_FILENO,buf,n);

        }
        
      }
    }

  }
  close(listenfd);
  return 0;
}


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!