12.IO多路复用:select—套接字IO超时处理**(企业财富库):sckutil.c sckutil.h

sckutil.h头文件

#ifndef _SCKUTIL_H_                                                                                                              
#define _SCKUTIL_H_                                                                                                              
                                                                                                                                 
#include <stdio.h>                                                                                                               
#include <stdlib.h>                                                                                                              
#include <string.h>                                                                                                              
                                                                                                                                 
#include <unistd.h>                                                                                                              
#include <sys/types.h>                                                                                                           
#include <fcntl.h>                                                                                                               
#include <sys/time.h>                                                                                                            
#include <sys/socket.h>                                                                                                          
#include <netinet/in.h>                                                                                                          
#include <arpa/inet.h>                                                                                                           
                                                                                                                                 
#include <netdb.h>                                                                                                               
                                                                                                                                 
#include <errno.h>                                                                                                               
                                                                                                                                 
#define ERR_EXIT(m) \                                                                                                            
  do{\                                                                                                                           
    perror(m); \                                                                                                                 
    exit(EXIT_FAILURE);  \                                                                                                       
  }while(0);                                                                                                                     
                                                                                                                                 
void activate_nonblock(int fd);                                                                                                  
void deactibe_nonblock(int fd);                                                                                                  
                                                                                                                                 
int read_timeout(int fd,unsigned int wait_seconds);                                                                              
int write_timeout(int fd,unsigned int wait_seconds);                                                                             
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds);                                            
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds);

ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,const void* buf,size_t count);                                                                             
ssize_t recv_peek(int fd,void* buf,size_t len);
ssize_t readline(int fd,void* buf,size_t maxline);

#endif

sckutil.c

//设置I/O为非阻塞模式
void activate_nonblock(int fd){                                                                                                  
  int ret;                                                                                                                       
  int flags=fcntl(fd,F_GETFL);                                                                                                   
  if(flags==-1)                                                                                                                  
    ERR_EXIT("fcntl");                                                                                                           
                                                                                                                                 
  flags|=O_NONBLOCK;                                                                                                             
  ret=fcntl(fd,F_SETFL,flags);                                                                                                   
  if(ret==-1)                                                                                                                    
    ERR_EXIT("fcntl");                                                                                                           
}
//设置I/O为阻塞模式                                                                                                                                
void deactibe_nonblock(int fd){                                                                                                  
  int ret;                                                                                                                       
  int flags=fcntl(fd,F_GETFL);                                                                                                   
  if(flags==-1)                                                                                                                  
    ERR_EXIT("fcntl");                                                                                                           
                                                                                                                                 
  flags&=~O_NONBLOCK;                                                                                                            
  ret=fcntl(fd,F_SETFL,flags);                                                                                                   
  if(ret==-1)                                                                                                                    
    ERR_EXIT("fcntl");                                                                                                           
} 
//-------------------------------
/*  read_timeout的使用方法:                                                                                                                             
int ret;                                                                                                                         
ret=read_timeout(fd,5);                                                                                                             
if(ret==0){ //成功(未超时):真正的调用[read/readn/readline]去读取数据
  read(fd,recvbuf,sizeof(recvbuf));                                                                
  ... ...  
}                                                
else if(ret==-1&&errno==ETIMEOUT){ //超时
  printf("读超时...\n");                           
  continue;
}                                                                                            
else //异常                                                              
  ERR_EXIT("read_line");                                                                                                         
*/  
//---------------------------

/*
read_timeout-读超时检测函数,不含读操作
	(即:判断[从fd套接字]中读数据,是否超时,不真正的读走数据)
@fd:文件描述符
@wait_seconds:等待超时秒数,如果为0表示不检测超时
	成功(未超时):返回0
	失败:返回-1
	超时:返回-1并且errno=ETIME_OUT
*/
int read_timeout(int fd,unsigned int wait_seconds){                                                                              
  int ret=0;                                                                                                                     
  if(wait_seconds>0){                                                                                                            
    fd_set read_fdset;                                                                                                           
    struct timeval timeout;                                                                                                      
                                                                                                                                 
    FD_ZERO(&read_fdset);                                                                                                        
    FD_SET(fd,&read_fdset);                                                                                                      
                                                                                                                                 
    timeout.tv_sec=wait_seconds;                                                                                                 
    timeout.tv_usec=0;                                                                                                           
    
    //select返回值三态
    //1.若timeout时间到(超时),没检测到读事件,返回值ret=0
    //2.若返回值ret<0&&errno==EINTR,说明select的过程被别的信号中断(可中断睡眠)
    //3.若返回值ret>0,表示有read事件发生,返回事件发生的个数                                                                   
    do{                                                                                                                          
      ret=select(fd+1,&read_fdset,NULL,NULL,&timeout);                                                                           
    }while(ret<0&&errno==EINTR);                                                                                                 
                                                                                                                                 
    if(ret==0){                                                                                                                  
      ret=-1;                                                                                                                    
      errno=ETIMEDOUT;                                                                                                           
    }
  }
  return ret;
}
//--------------------------------
/*
write_timeout-写超时检测函数,不含写操作
	(即:判断[向fd套接字]中写数据,是否超时,不真正的写入数据)
@fd:文件描述符
@wait_seconds:等待超时秒数,如果为0表示不检测超时
	成功(未超时):返回0
	失败:返回-1
	超时:返回-1并且errno=ETIME_OUT
*/
int write_timeout(int fd,unsigned int wait_seconds){                                                                             
  int ret=0;                                                                                                                     
  if(wait_seconds>0){                                                                                                            
    fd_set write_fdset;                                                                                                          
    struct timeval timeout;                                                                                                      
                                                                                                                                 
    FD_ZERO(&write_fdset);                                                                                                       
    FD_SET(fd,&write_fdset);                                                                                                     
                                                                                                                                 
    timeout.tv_sec=wait_seconds;                                                                                                 
    timeout.tv_usec=0;                                                                                                           
                                                                                                                                 
    do{                                                                                                                          
      ret=select(fd+1,NULL,&write_fdset,NULL,&timeout);                                                                          
    }while(ret<0&&errno==EINTR);                                                                                                 
    if(ret==0){                                                                                                                  
      ret=-1;                                                                                                                    
      errno=ETIMEDOUT;                                                                                                           
    }                                                                                                                            
    else if(ret==-1)                                                                                                             
      ret=0;                                                                                                                     
  }                                                                                                                              
  return 0;                                                                                                                      
}
//-------------------------
/*
accept_timeout-带超时的accept
@fd:套接字
@addr:输出参数,返回对方地址
@wait_seconds:等待超时秒数,如果为0表示正常模式
	成功(未超时):返回已连接的套接字
	失败:返回-1
	超时:返回-1并且errno=ETIME_OUT
*/
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds){                                                   
  int ret;                                                                                                                       
  if(wait_seconds>0){                                                                                                            
    fd_set accept_fdset;                                                                                                         
    struct timeval timeout;                                                                                                      
                                                                                                                                 
    FD_ZERO(&accept_fdset);                                                                                                      
    FD_SET(fd,&accept_fdset);                                                                                                    
                                                                                                                                 
    timeout.tv_sec=wait_seconds;                                                                                                 
    timeout.tv_usec=0;                                                                                                           
                                                                                                                                 
    do{                                                                                                                          
      ret=select(fd+1,&accept_fdset,NULL,NULL,&timeout);                                                                         
    }while(ret<0&&errno==EINTR);                                                                                                 
    if(ret==-1)                                                                                                                  
      return -1;                                                                                                                 
    else if(ret==0){                                                                                                             
      errno=ETIMEDOUT;                                                                                                           
      return -1;                                                                                                                 
    }                                                                                                                            
  }                                                                                                                              
  socklen_t addrlen=sizeof(struct sockaddr_in);                                                                                  
  if(addr!=NULL)                                                                                                                 
    ret=accept(fd,(struct sockaddr*)addr,&addrlen);                                                                              
  else                                                                                                                           
    ret=accept(fd,NULL,NULL);
  if(ret==-1)
    ERR_EXIT("accept");

  return ret;
}
//-------------------------
/*connect_timeout为什么要这么做呢?设计思路!
1.TCPIP在客户端连接服务器的时候,如果异常,connect在fd是阻塞的情况下,返回时
间是1.5RTT(大约是75s以上),势必造成软件质量的下降
2.首先将fd变成非阻塞,之后试着先调用connect连接服务器
[1]如果网络好,回合服务器马上建立连接
[2]如果网络不好,则根据返回值进行下面操作:
	if(ret<0&&errno==EINPROGRESS) 表示客户端和服务器[正在建立连接]
[2]我们此时需要等一等,等待的时间我们可以自己控制(即:[使用select管理
   中心去监控fd],这样会大大的提高我们的产品质量)
其中:需要注意一点—>
	select监控到fd[可读],并不能代表连接是可用的,还需要做进一步的判断—>
	造成可读有2种情况:
	  case1:真正的连接建立起来了;
	  case2:建立连接的时候,失败了(失败会回写失败信息,造成fd也可读)
	  	可以通过int sockoptret=getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);做一个容错即可*/
/*connect_timeout
@fd:套接字
@addr:要连接的对方地址
@wait_seconds:等待超时秒数,如果为0表示正常模式
	成功(未超时):返回0
	失败:返回-1
	超时:返回-1并且errno=ETIMEOUT
*/
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds){                                                
  int ret;
  if(wait_seconds>0)
    activate_nonblock(fd);
                                                                                                                               
  socklen_t addrlen=sizeof(struct sockaddr_in);
  ret=connect(fd,(struct sockaddr*)addr,addrlen);                                                                              
                                                                                                                               
  if(ret<0&&errno==EINPROGRESS){ //客户端和服务器正在建立连接                                                                                               
    fd_set connect_fdset;                                                                                                      
    struct timeval timeout;                                                                                                    
                                                                                                                               
    FD_ZERO(&connect_fdset);                                                                                                   
    FD_SET(fd,&connect_fdset);                                                                                                 
                                                                                                                               
    timeout.tv_sec=wait_seconds;                                                                                               
    timeout.tv_usec=0;                                                                                                         
                                                                                                                               
    do{  
      //一旦连接建立,则fd就可写,所以connect_fdset放在select的写集合中                                                                                                                      
      ret=select(fd+1,NULL,&connect_fdset,NULL,&timeout);                                                                      
    }while(ret<0&&errno==EINTR);                                                                                                                                                                                          
    if(ret==0){ //select返回0,表示select调用超时                                                                                                                
      ret==-1;                                                                                                                 
      errno=ETIMEDOUT;                                                                                                         
    }                                                                                                                          
    else if(ret<0) //select调用失败                                                                                                             
      return -1;                                                                                                               
    else if(ret==1){ /*表示套接字可写(但是有两种情况:一种是建立连接成功,一种是套
    		接字产生错误[此时错误信息不会保存在errno变量中,需要使用getsockopt去获取]*/
      int err;                                                                                                                 
      socklen_t socklen=sizeof(err);
      int sockoptret=getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);
      if(sockoptret==-1){
        return -1;                                                                                                             
      }
      if(err==0){
        ret=0;
      }                                                                                                                        
    }
  }
  if(wait_seconds>0){
    deactibe_nonblock(fd);                                                                                                     
  }
  return ret;
}
//----------------------------
ssize_t readn(int fd,void* buf,size_t count){
  ssize_t nread; //每次循环read函数的返回值
  char* bufp=(char*)buf; //作为辅助指针变量:每次读到nread个数据后,指针都要后移nread位置
  size_t nleft=count;  //nleft:剩余nleft个数据[未读]                                                                          
  while(nleft>0) //当有数据可读时,进入循环                                                                                    
  {                                                                                                                            
    if((nread=read(fd,bufp,nleft))<0) //read是可中断睡眠                                                                       
    {                                                                                                                          
      if(errno==EINTR) //在读的过程中,被其他信号中断                                                                          
        continue; //执行continue,返回去继续读                                                                                 
      return -1;                                                                                                               
    }                                                                                                                          
    else if(nread==0) //本次read返回值nread=0,说明对方socket已经关闭                                                          
      return count-nleft; //返回readn读取到的值                                                                                
                                                                                                                               
    //读取到nread个数据,更新bufp和nleft                                                                                       
    bufp+=nread;                                                                                                               
    nleft-=nread;                                                                                                              
  }                                                                                                                            
  return count;                                                                                                                
}     

ssize_t writen(int fd,const void* buf,size_t count){                                                                           
  ssize_t nwrite;                                                                                                              
  char* bufp=(char*)buf;                                                                                                       
  size_t nleft=count;                                                                                                          
                                                                                                                               
  while(nleft>0)                                                                                                               
  {                                                                                                                            
    if((nwrite=write(fd,bufp,nleft))<0)                                                                                        
    {                                                                                                                          
      if(errno==EINTR)                                                                                                         
        continue;                                                                                                              
      return -1;                                                                                                               
    }                                                                                                                          
    else if(nwrite==0) //当且仅当nleft==0时,nwrite才等于0--->执行continue,此时while(nleft>0)不成立,退出while循环            
      continue;                                                                                                                
    bufp+=nwrite;                                                                                                              
    nleft-=nwrite;                                                                                                             
  }                                                                                                                            
  return count;                                                                                                                
}  

ssize_t recv_peek(int fd,void* buf,size_t len){                                                                                
  while(1){                                                                                                                    
    int ret=recv(fd,buf,len,MSG_PEEK);                                                                                         
    if(ret==-1 && errno==EINTR)                                                                                                
      continue;                                                                                                                
    return ret;                                                                                                                
  }                                                                                                                            
}                                                                                                                              
                                                                                                                               
ssize_t readline(int fd,void* buf,size_t maxline)                                                                              
{                                                                                                                              
  int ret;                                                                                                                     
  int nread;                                                                                                                   
  char* bufp=(char*)buf;                                                                                                       
  size_t nleft=maxline;                                                                                                        
  while(1){                                                                                                                    
    ret=recv_peek(fd,bufp,nleft);                                                                                              
    if(ret<0) //失败                                                                                                           
      return ret;                                                                                                              
                                                                                                                               
    else if(ret==0) //对方已关闭                                                                                               
      return ret;                                                                                                              
    //else if(ret>0) //recv_peekt偷窥到了ret个字节的数据                                                                       
    nread=ret;                                                                                                                 
    int i;                                                                                                                     
    for(i=0;i<nread;i++){ //逐个判断读到的bufp中是否有\n                                                                       
      if(bufp[i]=='\n'){ //如果缓冲区中有\n                                                                                    
        ret=readn(fd,bufp,i+1); //读走数据                                                                                     
        if(ret!=i+1)                                                                                                           
          exit(EXIT_FAILURE);                                                                                                  
        return ret; //有\n就返回,并返回读走的字节数                                                                           
      }                                                                                                                        
    }                                                                                                                          
                                                                                                                               
    if(nread>nleft) //if 读到的数 > 一行最大数  —> 异常处理                                                                   
      exit(EXIT_FAILURE);                                                                                                      
    nleft-=nread; //若缓冲区没有\n,把剩余的数据读走                                                                           
    ret=readn(fd,bufp,nread);                                                                                                  
    if(ret!=nread)                                                                                                             
      exit(EXIT_FAILURE);                                                                                                      
    bufp+=nread; //bufp指针后移后,再接着偷看缓冲区数据recv_peek,直到遇到\n                                                   
  }                                                                                                                            
}  

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/83242757
今日推荐