miwaiwai

导航

企业财富库read_timeout,write_timeout,accept_timeout,connection_timeout企业财富库:select实现[超时]检测:read_timeout;write_timeout;connect_timeout;accept_timeout

https://blog.csdn.net/weixin_36750623/article/details/83307973

1.实现read超时检测:read_timeout

/**
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的过程中被别的信号中断(可中断睡眠原理)
        //2-1 若返回-1,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;
        }
        else if (ret == 1)
            ret = 0;
    }

    return ret;
}

 2.实现write超时检测:write_timeout

/**
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 ret;
}

 

3.实现connect超时检测:connect_timeout

1.在建立套接字(fd)以后默认是阻塞的(如果客户端连接服务器发生异常,则默认阻塞的情况下,返回时间是1.5RTT,大约100秒!–软件质量低下)
2.先把套接字通过fcntl变为非阻塞模型,再调用connect函数
[1]如果网络顺利,直接建立链接
[2]如果网络不好,则根据返回值判断:如果connect的返回值-1&&errno==EINPROGRESS,则表示客户端和服务器正在建立连接,需要等待一段时间才能建立链接(可以利用select监控该套接字是否可写来设定等待时间),进一步对select返回的结果判断是否可写。
[3]尽管select返回了套接字的可写状态,但不一定表示就是正确建立链接,(前面已经知道),导致select监控的套接字可读可写有两种情况
case1:真正的可读可写,即表示建立了连接

/**
 * activate_noblock - 设置I/O为非阻塞模式
 * @fd: 文件描符符
 */
int activate_nonblock(int fd)
{
  int ret = 0;

  int flags = fcntl(fd,F_GETFL);
  if(-1 == flags)
  {
    ret =flags;
    perror("fcntl");
    return ret;
  }

  flags |= O_NONBLOCK;

  ret = fcntl(fd,F_SETFL,flags);
  if(ret == -1)
  {
    perror("fcntl(fd,F_SETFL,flags)");
    return ret;
  }

  return ret;
}

/**
 * deactivate_nonblock - 设置I/O为阻塞模式
 * @fd: 文件描符符
 */
int deactivate_nonblock(int fd)
{
  int ret = 0;

  int flags = fcntl(fd,F_GETFL);
  if(-1 == flags)
  {
    ret =flags;
    perror("fcntl");
    return ret;
  }

  flags &= ~O_NONBLOCK;

  ret = fcntl(fd,F_SETFL,flags);
  if(ret == -1)
  {
    perror("fcntl(fd,F_SETFL,flags)");
    return ret;
  }

  return ret;
}

/**
connect_timeout
@fd:套接字
@addr:要连接的对方地址
@wait_seconds:等待超时秒数,如果为0表示正常模式
    成功(未超时):返回0
    失败:返回-1
    超时:返回-1并且errno=ETIMEOUT
 */
static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
  int ret = 0;
  socklen_t addrlen = sizeof(struct sockaddr_in);
  fd_set connect_fdset;
  struct timeval timeout;

  int err;
    socklen_t socklen = sizeof(err);
    int sockoptret; 

  if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的
  {
    activate_nonblock(fd);
  }


  /*非阻塞
  --成功:立马建立连接
  --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接
  */
  ret = connect(fd,(struct sockaddr*)addr,addrlen);
  if(ret < 0 && errno == EINPROGRESS)
  {
    FD_ZERO(&connect_fdset);
    FD_SET(fd,&connect_fdset);

    timeout.tv_sec = wait_seconds;
    timeout.tv_usec = 0;

    do
    {
      // 一但连接建立,则套接字就可写  所以connect_fdset放在了写集合中
      ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接
    }while(ret < 0 && errno == EINTR);

    if(ret == 0)//超时
    {
      ret = -1;
      errno = ETIMEDOUT;
    }
    else if (ret < 0)//select出错
    {
      return -1;
    }
    else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态
    {
      /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
            /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
      sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态
      if(sockoptret == -1)//getsockopt调用失败
      {
        return -1;
      }
      if(err == 0)//若无错误发生,getsockopt()返回0,表示真正可写入/准备好
        ret = 0;
      else{//表示套接字产生错误
        errno = err; 
        ret = -1;
      }
    }
  }

  if (wait_seconds > 0)
  {
      deactivate_nonblock(fd);
  }

  return ret;
}

 

4.实现accept超时检测:accept_timeout

/**
 * accept_timeout - 带超时的accept
 * @fd: 套接字
 * @addr: 输出参数,返回对方地址
 * @wait_seconds: 等待超时秒数,如果为0表示正常模式
 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
 */
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){
      perror("accept");
    }
    
    return ret;
}

 

综合代码:

sockAPI.h

#ifndef _SOCKAPI_H_
#define _SOCKAPI_H_ 

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>


ssize_t readn(int fd,void *buf,size_t cnt);
ssize_t writen(int fd,const void *buf,size_t cnt);


int deactivate_nonblock(int fd);
int activate_nonblock(int fd);

int read_timeout(int fd, unsigned int wait_seconds);
int write_timeout(int fd, unsigned int wait_seconds);
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);

#endif 

sockAPI.c

#include "sockAPI.h"

//@ssize_t:返回读的长度 若ssize_t<count 读失败
//@buf:接受数据内存首地址
//@count:接受数据长度
ssize_t readn(int fd,void *buf,size_t cnt)
{
  size_t nleft = cnt;//定义剩余没有读取的个数
  ssize_t nread = 0;//读取的个数
  char * bufp = (char *)buf;//将参数接过来

  while(nleft > 0)//当剩余需要读取的个数>0
  {
    if((nread  = read(fd,bufp,nleft)) < 0)//成功读取的个数小于0,则判断出错的原因
    {
      //如果errno被设置为EINTR为被信号中断,如果是被信号中断继续,
            //不是信号中断则退出。
      if(errno == EINTR)
      {
        continue;
      }  
      perror("write");
      exit(-1);
    }
    else if(nread == 0)//若对方已关闭
    {
      return cnt - nleft;
    }

    bufp += nread;//将 字符串指针向后移动已经成功读取个数的大小。
    nleft -= nread;//需要读取的个数=需要读取的个数-以及成功读取的个数
  }

  return cnt;
}


//@ssize_t:返回写的长度 -1失败
//@buf:待写数据首地址
//@count:待写长度
ssize_t writen(int fd,const void *buf,size_t cnt)
{
  size_t nleft = cnt;//需要写入的个数
  ssize_t nwritten = 0;//已经成功写入的个数
  char * bufp = (char *)buf;//接参数

  while(nleft > 0)//如果需要写入的个数>0
  {
    //如果写入成功的个数<0 判断是否是被信号打断
    if((nwritten  = write(fd,bufp,nleft)) < 0)
    {
      if(errno == EINTR)//信号打断,则继续
      {
        continue;
      }  
      perror("write");
      exit(-1);
    }
    //需要写入的数据个数>0
        //如果成功写入的个数为0 则继续
    else if(nwritten == 0)
    {
      continue;
    }

    bufp += nwritten;//将bufp指针向后移动已经
    nleft -= nwritten;//剩余个数
  }

  return cnt;
}

/**
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的过程中被别的信号中断(可中断睡眠原理)
        //2-1 若返回-1,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;
        }
        else if (ret == 1)
            ret = 0;
    }

    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 ret;
}

/**
 * activate_noblock - 设置I/O为非阻塞模式
 * @fd: 文件描符符
 */
int activate_nonblock(int fd)
{
  int ret = 0;

  int flags = fcntl(fd,F_GETFL);
  if(-1 == flags)
  {
    ret =flags;
    perror("fcntl");
    return ret;
  }

  flags |= O_NONBLOCK;

  ret = fcntl(fd,F_SETFL,flags);
  if(ret == -1)
  {
    perror("fcntl(fd,F_SETFL,flags)");
    return ret;
  }

  return ret;
}

/**
 * deactivate_nonblock - 设置I/O为阻塞模式
 * @fd: 文件描符符
 */
int deactivate_nonblock(int fd)
{
  int ret = 0;

  int flags = fcntl(fd,F_GETFL);
  if(-1 == flags)
  {
    ret =flags;
    perror("fcntl");
    return ret;
  }

  flags &= ~O_NONBLOCK;

  ret = fcntl(fd,F_SETFL,flags);
  if(ret == -1)
  {
    perror("fcntl(fd,F_SETFL,flags)");
    return ret;
  }

  return ret;
}

/**
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 = 0;
  socklen_t addrlen = sizeof(struct sockaddr_in);
  fd_set connect_fdset;
  struct timeval timeout;

  int err;
    socklen_t socklen = sizeof(err);
    int sockoptret; 

  if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的
  {
    activate_nonblock(fd);
  }


  /*非阻塞
  --成功:立马建立连接
  --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接
  */
  ret = connect(fd,(struct sockaddr*)addr,addrlen);
  if(ret < 0 && errno == EINPROGRESS)
  {
    FD_ZERO(&connect_fdset);
    FD_SET(fd,&connect_fdset);

    timeout.tv_sec = wait_seconds;
    timeout.tv_usec = 0;

    do
    {
      // 一但连接建立,则套接字就可写  所以connect_fdset放在了写集合中
      ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接
    }while(ret < 0 && errno == EINTR);

    if(ret == 0)//超时
    {
      ret = -1;
      errno = ETIMEDOUT;
    }
    else if (ret < 0)//select出错
    {
      return -1;
    }
    else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态
    {
      /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
            /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
      sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态
      if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败
      {
        return -1;
      }
      if(err == 0)//真正可写入/准备好
        ret = 0;
      else{//-1表示套接字产生错误
        errno = err;
        ret = -1;
      }
    }
  }

  if (wait_seconds > 0)
  {
      deactivate_nonblock(fd);
  }

  return ret;
}

/**
 * accept_timeout - 带超时的accept
 * @fd: 套接字
 * @addr: 输出参数,返回对方地址
 * @wait_seconds: 等待超时秒数,如果为0表示正常模式
 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
 */
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){
      perror("accept");
    }
    
    return ret;
}

 主函数:

 

 

posted on 2023-03-10 15:20  米歪歪  阅读(31)  评论(0编辑  收藏  举报