当前位置:   article > 正文

12.I/O超时设置方法_如何设置读取文件描述符的超时时间

如何设置读取文件描述符的超时时间

一. 使用alarm函数设置超时

       #include <unistd.h>

       unsigned int alarm(unsigned int seconds);


                        alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。

                     要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。需要注意的是,经过指定的秒数后,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一些时间。

  1. void handle(int sig)
  2. {
  3. }
  4. signal(SIGALRM,handler);
  5. alarm(5);
  6. int ret = read(fd,buf,sizeof(buf));
  7. if(ret == -1 && errno == EINTR)
  8. errno=ETIMEOUT;
  9. else if(ret >=0)
  10. alarm(0); http://write.blog.csdn.net/postedit/40829321

    像上面那样,如果在5s内被SIGALRM信号中断,表示超时,否则,已经读取到数据.但是这么方法在网络编程中不太常用, 因为有时可能在其他地方使用了alarm会造成混乱。

二. 使用套接字选项SO_SNDTIMEO,SO_RECVTIMEO实现超时

  

  1. struct timeval timeout={3,0};
  2. setsockopt(sock,SOL_SOCKET,SO_RECVTIME0,(char*)&timeout,sizeof(struct timeval));
  3. int ret=read(sock,buf,sizeof(buf));
  4. if(ret == -1 && errno==EWOULDBLOCK)
  5. ERRNO=ETIMEOUT;

   即使用setsockopt 函数进行设置,但这种方法可移植性比较差,不是每种系统实现都有这些选项。

三. 使用select实现超时

(1) read_timeout


  1. /*
  2. read_time-------读超时检测函数,不含读操作
  3. fd--------------文件描述符
  4. wait_seconds----等待的秒数,
  5. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  6. 如果读擦做超时(失败),不进行
  7. 如果成功,进行读操作
  8. */
  9. int read_timeout(int fd,unsigned int wait_seconds)
  10. {
  11. int ret=0;
  12. if(wait_seconds > 0)
  13. {
  14. fd_set read_fdset;// 读的文件描述符集合
  15. struct timeval timeout;// 超时时间结构提体
  16. FD_ZERO(&read_fdset);//初始话集合
  17. FD_SET(fd,&read_fdset);
  18. timeout.tv_sec = wait_seconds;
  19. timeout.tv_usec = 0;
  20. do
  21. {
  22. ret = select(fd+1,&read_fdset,NULL,NULL,&timeout);//select会阻塞直到检测到事件或者超时
  23. // 如果selcel检测到可读事件,,则此时调用read不会阻塞
  24. }while(ret < 0 && errno == EINTR);
  25. if(ret == 0)
  26. {
  27. ret = -1;
  28. errno = ETIMEDOUT;
  29. }
  30. else if(ret == 1)// 检测到一个事件
  31. return 0;
  32. }
  33. return ret;
  34. }

  简析: read_time,只是超时检测函数,没有读操作,如果从此函数成功返回,则此时调用read将不再阻塞.

如果 read_timeout(fd, 0); 则表示不检测超时,函数直接返回为0,此时再调用read 将会阻塞。

当wait_seconds 参数大于0,则进入if 括号执行,将超时时间设置为select函数的超时时间结构体,select会阻塞直到检测到事件发生或者超时。如果select返回-1且errno 为EINTR,说明是被信号中断,需要重启select;如果select返回0表示超时;如果select返回1表示检测到可读事件;否则select返回-1 表示出错。


应用模式:

  1. int ret;
  2. ret = read_timeout(fd,5);
  3. if(ret == 0)//未超时或不检测超时
  4. {
  5. read(fd,....);
  6. }
  7. else if(ret ==-1 && errno == ETIMEDOUT)// 超时
  8. {
  9. printf("timeout...\n");
  10. }
  11. els
  12. {
  13. ERR_EXIT("READ_EXIT");
  14. }


(2)write_timeout

  1. /*
  2. write_timeout-------写超时检测函数,不含读操作
  3. fd--------------文件描述符
  4. wait_seconds----等待的秒数,
  5. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  6. */
  7. int write_timeout(int fd,unsigned int wait_seconds)
  8. {
  9. int ret=0;
  10. if(wait_seconds > 0)
  11. {
  12. fd_set write_fdset;
  13. struct timeval timeout;
  14. FD_ZERO(&write_fdset);
  15. FD_SET(fd,&write_fdset);
  16. timeout.tv_sec = wait_seconds;
  17. timeout.tv_usec = 0;
  18. do
  19. {
  20. ret = select(fd+1,NULL,&write_fdset,NULL,&timeout);
  21. }while(ret<0 && errno == EINTR);
  22. if(ret == 0)
  23. {
  24. ret = -1;
  25. errno = ETIMEDOUT;
  26. }
  27. else if(ret == 1)
  28. return 0;
  29. }
  30. return ret;
  31. }



(3)accept_timeout

  1. /*
  2. accept_time-------带超时的accept
  3. fd--------------文件描述符
  4. wait_seconds----等待的秒数,
  5. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  6. */
  7. int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
  8. {
  9. int ret;
  10. socklen_t addrlen = sizeof(struct sockaddr_in);
  11. if(wait_seconds > 0)
  12. {
  13. fd_set accept_fdset; //创建accept文件描述集合
  14. struct timeval timeout;
  15. FD_ZERO(&accept_fdset);
  16. FD_SET(fd,&accept_fdset);
  17. timeout.tv_sec = wait_seconds;
  18. timeout.tv_usec= 0;
  19. do
  20. {
  21. ret = select(fd+1,&accept_fdset,NULL,NULL,&timeout);
  22. }while(ret <0 && errno==EINTR);//EINTR表示信号中断
  23. if(ret == -1)
  24. return -1;
  25. else if(ret == 0)
  26. {
  27. errno = ETIMEDOUT;// 连接时间超出
  28. return -1;
  29. }
  30. }
  31. //执行到这里,已经检测到事件
  32. if(addr != NULL)
  33. ret = accept(fd,(struct sockaddr*)addr,&addrlen);
  34. else
  35. ret = accept(fd,NULL,NULL);
  36. if(ret== -1)
  37. ERR_EXIT("accept err");
  38. return ret;
  39. }

解析: accept_timeout :此函数是带超时的accept 函数,如果能从if (wait_seconds > 0) 括号执行后向下执行,说明select 返回为1,检测到已连接队列不为空,此时再调用accept 不再阻塞,当然如果wait_seconds == 0 则像正常模式一样,accept 阻塞等待,注意,accept 返回的是已连接套接字。


(4)connect_timeout

 
        非阻塞I/O使我们的操作要么成功,要么立即返回错误,不被阻塞。
对于一个给定的描述符两种方法对其指定非阻塞I/O:
(1)调用open获得描述符,并指定O_NONBLOCK标志
(2)对已经打开的文件描述符,调用fcntl,打开O_NONBLOCK文件状态标志。


flags = fcntl( s, F_GETFL, 0 ) )
fcntl( s, F_SETFL, flags | O_NONBLOCK )

--------------------------------------------------------------------------------------
            fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列四个命令有特定返回值:F_DUPFD、F_GETFD、F_GETFL、F_GETOWN.第一个返回新的文件描述符,接下来的两个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。

  1. //activate_nonblock --- 设置IO为非阻塞模式
  2. void activate_nonblock(int fd)
  3. {
  4. int ret;
  5. int flags = fcntl(fd,F_GETFL,NULL);
  6. if( flags == -1)
  7. ERR_EXIT("fcntl error");
  8. flags |= O_NONBLOCK;
  9. ret = fcntl(fd,F_SETFL,flags);
  10. if(ret == -1)
  11. ERR_EXIT("fcntl error");
  12. }
  13. /*
  14. deactivate_nonblock ----设置IO为阻塞模式
  15. */
  16. void deactivate_nonblock(int fd)
  17. {
  18. int ret;
  19. int flags = fcntl(fd,F_GETFL,NULL);
  20. if(flags == -1)
  21. ERR_EXIT("fcntl error");
  22. flags &= ~O_NONBLOCK;
  23. ret = fcntl(fd,F_SETFL,flags);
  24. if( ret == -1)
  25. ERR_EXIT("fcntl error");
  26. }
  27. /*
  28. connect_timeout ----带超市的connect
  29. addr: 输出参数,返回对方地址
  30. wait_seconds: 等待超时秒数,如果为0表示正常模式
  31. 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
  32. */
  33. int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
  34. {
  35. int ret;
  36. socklen_t addrlen = sizeof(struct sockaddr_in);
  37. if(wait_seconds > 0)
  38. activate_nonblock(fd);//设置IO为非阻塞模式
  39. ret = connect(fd,(struct sockaddr*)addr,addrlen);
  40. if(ret < 0 && errno == EINPROGRESS)//Linux 非阻塞connect,错误码:EINPROGRESS,连接正在处理当中
  41. {
  42. printf("aaaaaaaa\n");
  43. fd_set connect_fdset;
  44. struct timeval timeout;
  45. FD_ZERO(&connect_fdset);
  46. FD_SET(fd,&connect_fdset);
  47. timeout.tv_sec = wait_seconds;
  48. timeout.tv_usec = 0;
  49. do
  50. {
  51. // 这里select使用写集合,因为一旦建立连接,套接字就写
  52. ret = select(fd+1,NULL,&connect_fdset,NULL,&timeout);
  53. }while(ret<0 && errno==EINTR);
  54. if(ret == 0)
  55. {
  56. errno = ETIMEDOUT;// 连接超时
  57. return -1;
  58. }
  59. else if(ret <0)
  60. return -1;
  61. else if(ret == 1)
  62. {
  63. //ret返回为1,可能有两种情况,
  64. //一种是连接建立成功,
  65. //一种是套接字产生错误
  66. // 此时错误信息不会保存至errno变量中(select没出错),因此,
  67. // 需要调用getsockopt来获取
  68. // 获取套接字的错误
  69. printf("bbbbbbbbbbb\n");
  70. int err;
  71. socklen_t socklen = sizeof(err);
  72. int sockoptret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);
  73. if( sockoptret == -1)
  74. return -1;
  75. if( err == 0)//没有错误
  76. {
  77. printf("dddddddddd\n");
  78. ret = 0;
  79. }
  80. else// 产生错误
  81. {
  82. printf("cccccccccc\n");
  83. errno = err;
  84. ret = -1;
  85. }
  86. }
  87. }
  88. if(wait_seconds > 0)/// 重新置为阻塞模式
  89. deactivate_nonblock(fd);
  90. return ret;
  91. }

解析: 在调用connect前需要使用fcntl 函数将套接字标志设置为非阻塞,如果网络环境很好,则connect立即返回0,不进入if 大括号执行;如果网络环境拥塞,则connect返回-1且errno == EINPROGRESS,表示正在处理。此后调用select与前面3个函数类似,但这里关注的是可写事件,因为一旦连接建立,套接字就可写。还需要注意的是当select 返回1,可能有两种情况,一种是连接成功,一种是套接字产生错误,这两种情况都会产生可写事件,所以需要使用getsockopt来获取一下。退出之前还需重新将套接字设置为阻塞。


完整的C/S测试程序如下:

服务器程序:

  1. /// srv.c
  2. #include <unistd.h>
  3. #include <sys/types.h>
  4. #include <sys/socket.h>
  5. #include <netinet/in.h>
  6. #include <arpa/inet.h>
  7. #include <signal.h>
  8. #include <sys/wait.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <errno.h>
  12. #include <stdio.h>
  13. #define ERR_EXIT(m) \
  14. do { \
  15. perror(m); \
  16. exit(EXIT_FAILURE); \
  17. } while (0)
  18. int main()
  19. {
  20. int listenfd;
  21. printf("%d",INADDR_ANY);
  22. if((listenfd = socket(PF_INET,SOCK_STREAM,0)) < 0)
  23. ERR_EXIT("socket err");
  24. struct sockaddr_in servaddr;
  25. memset(&servaddr,0,sizeof(servaddr));
  26. servaddr.sin_family = AF_INET;
  27. servaddr.sin_port = htons(5188);
  28. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  29. // 设定地址重复利用
  30. int on=1;
  31. if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
  32. ERR_EXIT("setsockopt err");
  33. if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
  34. ERR_EXIT("bind err");
  35. if(listen(listenfd,SOMAXCONN) < 0)
  36. ERR_EXIT("listen err");
  37. struct sockaddr_in peeraddr;
  38. socklen_t peerlen=sizeof(peeraddr);
  39. int conn;
  40. if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)
  41. ERR_EXIT("accept err");
  42. //连接成功,打印对方IP地址
  43. printf("ip=%s ,port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
  44. return 0;
  45. }

客户端程序:
  1. // sys.h
  2. #include<stdio.h>
  3. #include<sys/types.h>
  4. #include<sys/socket.h>
  5. #include<unistd.h>
  6. #include<stdlib.h>
  7. #include<errno.h>
  8. #include<arpa/inet.h>
  9. #include<netinet/in.h>
  10. #include<string.h>
  11. #include<signal.h>
  12. #include<sys/wait.h>
  13. #include<fcntl.h>
  14. #define ERR_EXIT(m) \
  15. do { \
  16. perror(m); \
  17. exit(EXIT_FAILURE); \
  18. } while (0)
  19. /*
  20. read_time-------读超时检测函数,不含读操作
  21. fd--------------文件描述符
  22. wait_seconds----等待的秒数,
  23. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  24. 如果读擦做超时(失败),不进行
  25. 如果成功,进行读操作
  26. */
  27. int read_timeout(int fd,unsigned int wait_seconds)
  28. {
  29. int ret=0;
  30. if(wait_seconds > 0)
  31. {
  32. fd_set read_fdset;// 读的文件描述符集合
  33. struct timeval timeout;// 超时时间结构提体
  34. FD_ZERO(&read_fdset);//初始话集合
  35. FD_SET(fd,&read_fdset);
  36. timeout.tv_sec = wait_seconds;
  37. timeout.tv_usec = 0;
  38. do
  39. {
  40. ret = select(fd+1,&read_fdset,NULL,NULL,&timeout);//select会阻塞直到检测到事件或者超时
  41. // 如果selcel检测到可读事件,,则此时调用read不会阻塞
  42. }while(ret < 0 && errno == EINTR);
  43. if(ret == 0)
  44. {
  45. ret = -1;
  46. errno = ETIMEDOUT;
  47. }
  48. else if(ret == 1)// 检测到一个事件
  49. return 0;
  50. }
  51. return ret;
  52. }
  53. /*
  54. write_timeout-------写超时检测函数,不含读操作
  55. fd--------------文件描述符
  56. wait_seconds----等待的秒数,
  57. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  58. */
  59. int write_timeout(int fd,unsigned int wait_seconds)
  60. {
  61. int ret=0;
  62. if(wait_seconds > 0)
  63. {
  64. fd_set write_fdset;
  65. struct timeval timeout;
  66. FD_ZERO(&write_fdset);
  67. FD_SET(fd,&write_fdset);
  68. timeout.tv_sec = wait_seconds;
  69. timeout.tv_usec = 0;
  70. do
  71. {
  72. ret = select(fd+1,NULL,&write_fdset,NULL,&timeout);
  73. }while(ret<0 && errno == EINTR);
  74. if(ret == 0)
  75. {
  76. ret = -1;
  77. errno = ETIMEDOUT;
  78. }
  79. else if(ret == 1)
  80. return 0;
  81. }
  82. return ret;
  83. }
  84. /*
  85. accept_time-------带超时的accept
  86. fd--------------文件描述符
  87. wait_seconds----等待的秒数,
  88. 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
  89. */
  90. int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
  91. {
  92. int ret;
  93. socklen_t addrlen = sizeof(struct sockaddr_in);
  94. if(wait_seconds > 0)
  95. {
  96. fd_set accept_fdset; //创建accept文件描述集合
  97. struct timeval timeout;
  98. FD_ZERO(&accept_fdset);
  99. FD_SET(fd,&accept_fdset);
  100. timeout.tv_sec = wait_seconds;
  101. timeout.tv_usec= 0;
  102. do
  103. {
  104. ret = select(fd+1,&accept_fdset,NULL,NULL,&timeout);
  105. }while(ret <0 && errno==EINTR);//EINTR表示信号中断
  106. if(ret == -1)
  107. return -1;
  108. else if(ret == 0)
  109. {
  110. errno = ETIMEDOUT;// 连接时间超出
  111. return -1;
  112. }
  113. }
  114. //执行到这里,已经检测到事件
  115. if(addr != NULL)
  116. ret = accept(fd,(struct sockaddr*)addr,&addrlen);
  117. else
  118. ret = accept(fd,NULL,NULL);
  119. if(ret== -1)
  120. ERR_EXIT("accept err");
  121. return ret;
  122. }
  123. //activate_nonblock --- 设置IO为非阻塞模式
  124. void activate_nonblock(int fd)
  125. {
  126. int ret;
  127. int flags = fcntl(fd,F_GETFL,NULL);
  128. if( flags == -1)
  129. ERR_EXIT("fcntl error");
  130. flags |= O_NONBLOCK;
  131. ret = fcntl(fd,F_SETFL,flags);
  132. if(ret == -1)
  133. ERR_EXIT("fcntl error");
  134. }
  135. /*
  136. deactivate_nonblock ----设置IO为阻塞模式
  137. */
  138. void deactivate_nonblock(int fd)
  139. {
  140. int ret;
  141. int flags = fcntl(fd,F_GETFL,NULL);
  142. if(flags == -1)
  143. ERR_EXIT("fcntl error");
  144. flags &= ~O_NONBLOCK;
  145. ret = fcntl(fd,F_SETFL,flags);
  146. if( ret == -1)
  147. ERR_EXIT("fcntl error");
  148. }
  149. /*
  150. connect_timeout ----带超市的connect
  151. addr: 输出参数,返回对方地址
  152. wait_seconds: 等待超时秒数,如果为0表示正常模式
  153. 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
  154. */
  155. int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
  156. {
  157. int ret;
  158. socklen_t addrlen = sizeof(struct sockaddr_in);
  159. if(wait_seconds > 0)
  160. activate_nonblock(fd);//设置IO为非阻塞模式
  161. ret = connect(fd,(struct sockaddr*)addr,addrlen);
  162. if(ret < 0 && errno == EINPROGRESS)//Linux 非阻塞connect,错误码:EINPROGRESS,连接正在处理当中
  163. {
  164. printf("aaaaaaaa\n");
  165. fd_set connect_fdset;
  166. struct timeval timeout;
  167. FD_ZERO(&connect_fdset);
  168. FD_SET(fd,&connect_fdset);
  169. timeout.tv_sec = wait_seconds;
  170. timeout.tv_usec = 0;
  171. do
  172. {
  173. // 这里select使用写集合,因为一旦建立连接,套接字就写
  174. ret = select(fd+1,NULL,&connect_fdset,NULL,&timeout);
  175. }while(ret<0 && errno==EINTR);
  176. if(ret == 0)
  177. {
  178. errno = ETIMEDOUT;// 连接超时
  179. return -1;
  180. }
  181. else if(ret <0)
  182. return -1;
  183. else if(ret == 1)
  184. {
  185. //ret返回为1,可能有两种情况,
  186. //一种是连接建立成功,
  187. //一种是套接字产生错误
  188. // 此时错误信息不会保存至errno变量中(select没出错),因此,
  189. // 需要调用getsockopt来获取
  190. // 获取套接字的错误
  191. printf("bbbbbbbbbbb\n");
  192. int err;
  193. socklen_t socklen = sizeof(err);
  194. int sockoptret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);
  195. if( sockoptret == -1)
  196. return -1;
  197. if( err == 0)//没有错误
  198. {
  199. //printf("dddddddddd\n");
  200. ret = 0;
  201. }
  202. else// 产生错误
  203. {
  204. //printf("cccccccccc\n");
  205. errno = err;
  206. ret = -1;
  207. }
  208. }
  209. }
  210. if(wait_seconds > 0)/// 重新置为阻塞模式
  211. deactivate_nonblock(fd);
  212. return ret;
  213. }

  1. /cli.c
  2. #include "sys.h"
  3. int main()
  4. {
  5. int sock;
  6. if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
  7. ERR_EXIT("socket err");
  8. struct sockaddr_in servaddr;
  9. memset(&servaddr,0,sizeof(servaddr));
  10. servaddr.sin_family = AF_INET;
  11. servaddr.sin_port = htons(5188);
  12. servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  13. int ret = connect_timeout(sock,&servaddr,5);
  14. if(ret == -1 && errno == ETIMEDOUT)
  15. {
  16. printf("timeout ... \n");
  17. return 1;
  18. }
  19. else if(ret == -1)
  20. ERR_EXIT("connect_timeout err");
  21. struct sockaddr_in localaddr;
  22. socklen_t addrlen = sizeof(localaddr);
  23. if( getsockname(sock,(struct sockaddr *)&localaddr,&addrlen) <0)
  24. ERR_EXIT("getsockname err");
  25. printf("ip=%s,port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
  26. return 0;
  27. }

Makefile:

  1. .PHONY:clean all
  2. CC=gcc
  3. CFLAGS=-Wall -g
  4. BIN=srv cli 1 #echoser echocli echocli2 echoser2
  5. all:$(BIN)
  6. %.o:%.c
  7. $(cc) $(CFLAGS) -c $< -o $@ #$< 当前依赖的文件名,$@当前目标的文件名
  8. clean:
  9. rm -f *.o $(BIN)







声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号