赞
踩
一. 使用alarm函数设置超时
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。
要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。需要注意的是,经过指定的秒数后,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一些时间。
- void handle(int sig)
- {
- }
-
- signal(SIGALRM,handler);
- alarm(5);
- int ret = read(fd,buf,sizeof(buf));
-
- if(ret == -1 && errno == EINTR)
- errno=ETIMEOUT;
- else if(ret >=0)
- alarm(0); http://write.blog.csdn.net/postedit/40829321
二. 使用套接字选项SO_SNDTIMEO,SO_RECVTIMEO实现超时
- struct timeval timeout={3,0};
- setsockopt(sock,SOL_SOCKET,SO_RECVTIME0,(char*)&timeout,sizeof(struct timeval));
- int ret=read(sock,buf,sizeof(buf));
- if(ret == -1 && errno==EWOULDBLOCK)
- ERRNO=ETIMEOUT;
三. 使用select实现超时
(1) read_timeout
- /*
- read_time-------读超时检测函数,不含读操作
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- 如果读擦做超时(失败),不进行
- 如果成功,进行读操作
- */
-
- 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;
-
- do
- {
- ret = select(fd+1,&read_fdset,NULL,NULL,&timeout);//select会阻塞直到检测到事件或者超时
- // 如果selcel检测到可读事件,,则此时调用read不会阻塞
- }while(ret < 0 && errno == EINTR);
-
- if(ret == 0)
- {
- ret = -1;
- errno = ETIMEDOUT;
- }
- else if(ret == 1)// 检测到一个事件
- return 0;
- }
- return ret;
- }

如果 read_timeout(fd, 0); 则表示不检测超时,函数直接返回为0,此时再调用read 将会阻塞。
当wait_seconds 参数大于0,则进入if 括号执行,将超时时间设置为select函数的超时时间结构体,select会阻塞直到检测到事件发生或者超时。如果select返回-1且errno 为EINTR,说明是被信号中断,需要重启select;如果select返回0表示超时;如果select返回1表示检测到可读事件;否则select返回-1 表示出错。
应用模式:
- int ret;
- ret = read_timeout(fd,5);
- if(ret == 0)//未超时或不检测超时
- {
- read(fd,....);
- }
- else if(ret ==-1 && errno == ETIMEDOUT)// 超时
- {
- printf("timeout...\n");
- }
- els
- {
- ERR_EXIT("READ_EXIT");
- }
(2)write_timeout
- /*
- write_timeout-------写超时检测函数,不含读操作
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- */
-
- 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)
- return 0;
- }
- return ret;
- }

(3)accept_timeout
- /*
- accept_time-------带超时的accept
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- */
-
- int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
- {
- int ret;
- socklen_t addrlen = sizeof(struct sockaddr_in);
-
- if(wait_seconds > 0)
- {
- fd_set accept_fdset; //创建accept文件描述集合
- 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);//EINTR表示信号中断
-
- if(ret == -1)
- return -1;
- else if(ret == 0)
- {
- errno = ETIMEDOUT;// 连接时间超出
- return -1;
- }
- }
- //执行到这里,已经检测到事件
- if(addr != NULL)
- ret = accept(fd,(struct sockaddr*)addr,&addrlen);
- else
- ret = accept(fd,NULL,NULL);
- if(ret== -1)
- ERR_EXIT("accept err");
- return ret;
- }

(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。
- //activate_nonblock --- 设置IO为非阻塞模式
- void activate_nonblock(int fd)
- {
- int ret;
- int flags = fcntl(fd,F_GETFL,NULL);
- if( flags == -1)
- ERR_EXIT("fcntl error");
- flags |= O_NONBLOCK;
- ret = fcntl(fd,F_SETFL,flags);
- if(ret == -1)
- ERR_EXIT("fcntl error");
- }
-
- /*
- deactivate_nonblock ----设置IO为阻塞模式
- */
-
- void deactivate_nonblock(int fd)
- {
- int ret;
- int flags = fcntl(fd,F_GETFL,NULL);
- if(flags == -1)
- ERR_EXIT("fcntl error");
-
- flags &= ~O_NONBLOCK;
- ret = fcntl(fd,F_SETFL,flags);
- if( ret == -1)
- ERR_EXIT("fcntl error");
- }
-
- /*
- connect_timeout ----带超市的connect
- addr: 输出参数,返回对方地址
- wait_seconds: 等待超时秒数,如果为0表示正常模式
- 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
- */
-
- int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
- {
- int ret;
- socklen_t addrlen = sizeof(struct sockaddr_in);
-
- if(wait_seconds > 0)
- activate_nonblock(fd);//设置IO为非阻塞模式
-
- ret = connect(fd,(struct sockaddr*)addr,addrlen);
- if(ret < 0 && errno == EINPROGRESS)//Linux 非阻塞connect,错误码:EINPROGRESS,连接正在处理当中
- {
- printf("aaaaaaaa\n");
- 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
- {
- // 这里select使用写集合,因为一旦建立连接,套接字就写
- ret = select(fd+1,NULL,&connect_fdset,NULL,&timeout);
- }while(ret<0 && errno==EINTR);
- if(ret == 0)
- {
- errno = ETIMEDOUT;// 连接超时
- return -1;
- }
- else if(ret <0)
- return -1;
- else if(ret == 1)
- {
- //ret返回为1,可能有两种情况,
- //一种是连接建立成功,
- //一种是套接字产生错误
- // 此时错误信息不会保存至errno变量中(select没出错),因此,
- // 需要调用getsockopt来获取
- // 获取套接字的错误
- printf("bbbbbbbbbbb\n");
- 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)//没有错误
- {
- printf("dddddddddd\n");
- ret = 0;
- }
- else// 产生错误
- {
- printf("cccccccccc\n");
- errno = err;
- ret = -1;
- }
- }
- }
- if(wait_seconds > 0)/// 重新置为阻塞模式
- deactivate_nonblock(fd);
-
- return ret;
- }
-

解析: 在调用connect前需要使用fcntl 函数将套接字标志设置为非阻塞,如果网络环境很好,则connect立即返回0,不进入if 大括号执行;如果网络环境拥塞,则connect返回-1且errno == EINPROGRESS,表示正在处理。此后调用select与前面3个函数类似,但这里关注的是可写事件,因为一旦连接建立,套接字就可写。还需要注意的是当select 返回1,可能有两种情况,一种是连接成功,一种是套接字产生错误,这两种情况都会产生可写事件,所以需要使用getsockopt来获取一下。退出之前还需重新将套接字设置为阻塞。
完整的C/S测试程序如下:
服务器程序:
- /// srv.c
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <signal.h>
- #include <sys/wait.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <stdio.h>
-
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while (0)
-
-
- int main()
- {
- int listenfd;
- printf("%d",INADDR_ANY);
- if((listenfd = socket(PF_INET,SOCK_STREAM,0)) < 0)
- ERR_EXIT("socket err");
-
- struct sockaddr_in servaddr;
- memset(&servaddr,0,sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(5188);
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- // 设定地址重复利用
- int on=1;
- if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
- ERR_EXIT("setsockopt err");
-
- if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
- ERR_EXIT("bind err");
-
- if(listen(listenfd,SOMAXCONN) < 0)
- ERR_EXIT("listen err");
-
- struct sockaddr_in peeraddr;
- socklen_t peerlen=sizeof(peeraddr);
- int conn;
-
- if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)
- ERR_EXIT("accept err");
-
- //连接成功,打印对方IP地址
- printf("ip=%s ,port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
- return 0;
- }
-
-
-

- // sys.h
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<unistd.h>
- #include<stdlib.h>
- #include<errno.h>
- #include<arpa/inet.h>
- #include<netinet/in.h>
- #include<string.h>
- #include<signal.h>
- #include<sys/wait.h>
- #include<fcntl.h>
-
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while (0)
-
-
-
- /*
- read_time-------读超时检测函数,不含读操作
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- 如果读擦做超时(失败),不进行
- 如果成功,进行读操作
- */
-
- 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;
-
- do
- {
- ret = select(fd+1,&read_fdset,NULL,NULL,&timeout);//select会阻塞直到检测到事件或者超时
- // 如果selcel检测到可读事件,,则此时调用read不会阻塞
- }while(ret < 0 && errno == EINTR);
-
- if(ret == 0)
- {
- ret = -1;
- errno = ETIMEDOUT;
- }
- else if(ret == 1)// 检测到一个事件
- return 0;
- }
- return ret;
- }
-
- /*
- write_timeout-------写超时检测函数,不含读操作
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- */
-
- 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)
- return 0;
- }
- return ret;
- }
-
- /*
- accept_time-------带超时的accept
- fd--------------文件描述符
- wait_seconds----等待的秒数,
- 成功(未超时)返回0,失败返回-1, 超时返回-1,并且errno=ETIMEDOUT
- */
-
- int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
- {
- int ret;
- socklen_t addrlen = sizeof(struct sockaddr_in);
-
- if(wait_seconds > 0)
- {
- fd_set accept_fdset; //创建accept文件描述集合
- 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);//EINTR表示信号中断
-
- if(ret == -1)
- return -1;
- else if(ret == 0)
- {
- errno = ETIMEDOUT;// 连接时间超出
- return -1;
- }
- }
- //执行到这里,已经检测到事件
- if(addr != NULL)
- ret = accept(fd,(struct sockaddr*)addr,&addrlen);
- else
- ret = accept(fd,NULL,NULL);
- if(ret== -1)
- ERR_EXIT("accept err");
- return ret;
- }
-
-
-
- //activate_nonblock --- 设置IO为非阻塞模式
- void activate_nonblock(int fd)
- {
- int ret;
- int flags = fcntl(fd,F_GETFL,NULL);
- if( flags == -1)
- ERR_EXIT("fcntl error");
- flags |= O_NONBLOCK;
- ret = fcntl(fd,F_SETFL,flags);
- if(ret == -1)
- ERR_EXIT("fcntl error");
- }
-
- /*
- deactivate_nonblock ----设置IO为阻塞模式
- */
-
- void deactivate_nonblock(int fd)
- {
- int ret;
- int flags = fcntl(fd,F_GETFL,NULL);
- if(flags == -1)
- ERR_EXIT("fcntl error");
-
- flags &= ~O_NONBLOCK;
- ret = fcntl(fd,F_SETFL,flags);
- if( ret == -1)
- ERR_EXIT("fcntl error");
- }
-
- /*
- connect_timeout ----带超市的connect
- addr: 输出参数,返回对方地址
- wait_seconds: 等待超时秒数,如果为0表示正常模式
- 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
- */
-
- int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
- {
- int ret;
- socklen_t addrlen = sizeof(struct sockaddr_in);
-
- if(wait_seconds > 0)
- activate_nonblock(fd);//设置IO为非阻塞模式
-
- ret = connect(fd,(struct sockaddr*)addr,addrlen);
- if(ret < 0 && errno == EINPROGRESS)//Linux 非阻塞connect,错误码:EINPROGRESS,连接正在处理当中
- {
- printf("aaaaaaaa\n");
- 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
- {
- // 这里select使用写集合,因为一旦建立连接,套接字就写
- ret = select(fd+1,NULL,&connect_fdset,NULL,&timeout);
- }while(ret<0 && errno==EINTR);
- if(ret == 0)
- {
- errno = ETIMEDOUT;// 连接超时
- return -1;
- }
- else if(ret <0)
- return -1;
- else if(ret == 1)
- {
- //ret返回为1,可能有两种情况,
- //一种是连接建立成功,
- //一种是套接字产生错误
- // 此时错误信息不会保存至errno变量中(select没出错),因此,
- // 需要调用getsockopt来获取
- // 获取套接字的错误
- printf("bbbbbbbbbbb\n");
- 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)//没有错误
- {
- //printf("dddddddddd\n");
- ret = 0;
- }
- else// 产生错误
- {
- //printf("cccccccccc\n");
- errno = err;
- ret = -1;
- }
- }
- }
- if(wait_seconds > 0)/// 重新置为阻塞模式
- deactivate_nonblock(fd);
-
- return ret;
- }
-
-

- /cli.c
-
- #include "sys.h"
-
- int main()
- {
- int sock;
- if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
- ERR_EXIT("socket err");
-
- struct sockaddr_in servaddr;
- memset(&servaddr,0,sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(5188);
- servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
-
- int ret = connect_timeout(sock,&servaddr,5);
- if(ret == -1 && errno == ETIMEDOUT)
- {
- printf("timeout ... \n");
- return 1;
- }
- else if(ret == -1)
- ERR_EXIT("connect_timeout err");
-
- struct sockaddr_in localaddr;
- socklen_t addrlen = sizeof(localaddr);
-
- if( getsockname(sock,(struct sockaddr *)&localaddr,&addrlen) <0)
- ERR_EXIT("getsockname err");
-
- printf("ip=%s,port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
- return 0;
- }
-
-

- .PHONY:clean all
- CC=gcc
- CFLAGS=-Wall -g
- BIN=srv cli 1 #echoser echocli echocli2 echoser2
- all:$(BIN)
- %.o:%.c
- $(cc) $(CFLAGS) -c $< -o $@ #$< 当前依赖的文件名,$@当前目标的文件名
- clean:
- rm -f *.o $(BIN)
-
-
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。