套接字IO超时设置和使用select实现超时管理
在涉及套接字IO超时的设置上有一下3种方法:
1、调用alarm,它在指定的时期满时产生SIGALRM信号。这个方法涉及信号的处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用。
程序大概框架如下所示,如果read在5s内被SIGALRM信号中断而返回,则表示超时,否则未超时已读取到数据则取消闹钟。为了在超时时中断read函数,可以用信号处理函数来捕捉SIGALRM信号。
void handler(int sig)
{
return ; //只是用来中断read函数,不需要进行处理
} signal(SIGALRM,handler); alarm(); //开启闹钟
int ret =read(fd,buf,sizeof(buf));
if(ret==- && errno == EINTR) //read被超时中断
{
errno = ETIMEDOUT;
}
else if (ret>) //读到数据
{
alarm(); //关闭闹钟
}
2、使用SO_RCVTIMEO和SO_SNDTIMEO两个套接字选项。此方法的问题在于并非所有的实现都支持这两个套接字选项。此种方法仅适用于套接字描述符。
struct timeval timeout={,}; //设置超时时间为3秒 setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(struct timeval)); //设置套接字选项 int ret=read(fd,buf,sizeof(buf));
if(ret==- && errno == EWOULDBLOCK) //IO超时中断
errno = ETIMEDOUT;
.................
3、在select中阻塞等待的IO(select有内置的时间限制),以此代替直接阻塞在read和write调用上。
(1)利用select封装read超时函数
如果 read_timeout(fd, 0); 则表示不检测超时,函数直接返回为0,此时再调用read 将会阻塞。
当wait_seconds 参数大于0,则进入if 括号执行,将超时时间设置为select函数的超时时间结构体,select会阻塞直到检测到事件发生或者超时。如果select返回-1且errno 为EINTR,说明是被信号中断,需要重启select;如果select返回0表示超时;如果select返回1表示检测到可读事件;否则select返回-1 表示出错。
/**
*read_timeout 读超时检测函数,不含读操作
* fd:文件描述符
*wait_seconds:等待超时秒数,如果为0则表示不检测超时
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
*/
int read_timeout(int fd,unsigned int wait_seconds)
{
int ret=; //默认为0,当wait_seconds==0时,不检测直接返回0
if(wait_seconds>) //需要检测超时
{
fd_set read_fdset; //描述符集合
struct timeval timeout; //超时时间 FD_ZERO(&read_fdset);
FD_SET(fd,&read_fdset); timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret=select(fd+,&read_fdset,NULL,NULL,&timeout);
/*select会阻塞直到检测到事件或则超时,如果超时,select会返回0,
如果检测到事件会返回1,如果异常会返回-1,如果是由于信号中断引起的异常errno==EINTR*/
}while(ret< && errno == EINTR); //如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时 if(ret==) //select超时退出
{
ret=-;
errno= ETIMEDOUT;
}
else if(ret==) //select检测到可读事件
ret= ;
}
return ret;
}
测试程序框架
/***********测试程序***************/
int ret;
ret=read_timeout(fd,);
if(ret==) //检测到套接字可读
{
read(fd,......); //进行读套接字的操作
}
else if(ret==- && errno == ETIMEDOUT) //超时即指定事件内为检测到套接字可读
{
timeout.......... ; //进行超时处理
}
else
{
ERR_EXIT("read_timeout"); //进行错误处理
}
(2)利用select封装write超时函数
write超时函数的封装与read超时函数的封装基本一样,不同的只是把文件描述符加入到写集合write_fdset中,其关心的只是写事件。
/**
*write_timeout 写超时检测函数,不含写操作
* fd:文件描述符
*wait_seconds:等待超时秒数,如果为0则表示不检测超时
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
*/
int write_timeout(int fd,unsigned int wait_seconds)
{
int ret=; //默认为0,当wait_seconds==0时,不检测直接返回0
if(wait_seconds>) //需要检测超时
{
fd_set write_fdset; //描述符集合
struct timeval timeout; //超时时间 FD_ZERO(&write_fdset);
FD_SET(fd,&write_fdset); timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret=select(fd+,NULL,&write_fdset,NULL,&timeout); /*select会阻塞直到检测到事件或则超时,如果超时,select会返回0, 如果检测到事件会返回1,如果异常会返回-1,如果是由于信号中断引起的异常errno==EINTR*/ }while(ret< && errno == EINTR); //如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时 if(ret==) //select超时退出 { ret=-; errno= ETIMEDOUT; } else if(ret==) //select检测到可写事件 ret= ; } return ret; }
测试代码
/***********测试程序***************/
int ret;
ret=write_timeout(fd,);
if(ret==) //检测到描述符可写
{
write(fd,......); //进行写描述符的操作
}
else if(ret==- && errno == ETIMEDOUT) //超时即指定事件内为检测到套接字可写
{
timeout.......... ; //进行超时处理
}
else
{
ERR_EXIT("read_timeout"); //进行错误处理
}
(3)accept超时函数的封装
accept超时函数是带超时的accept 函数,如果能从if (wait_seconds > 0) 括号执行后向下执行,说明select 返回为1,检测到已连接队列不为空,此时再调用accept 不再阻塞,当然如果wait_seconds == 0 则像正常模式一样,accept 阻塞等待,注意,accept 返回的是已连接套接字。
/**
*accept_timeout 带超时的accept函数
*fd:文件描述符
*addr 输出参数,accept返回的对等方的地址结构
*wait_seconds:等待超时秒数,如果为0则表示正常模式
*成功(未超时)返回已连接的套接字,失败返回-1,超时返回-1并且errno=ETIMEDOUT
*/
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
{
int ret=; //默认为0,当wait_seconds==0时,不检测直接返回0
socklen_t addrlen =sizeof(struct sockaddr_in);
if(wait_seconds>) //需要检测超时
{
fd_set accept_fdset; //描述符集合
struct timeval timeout; //超时时间 FD_ZERO(&accept_fdset);
FD_SET(fd,&accept_fdset); timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret=select(fd+,&accept_fdset,NULL,NULL,&timeout);
/*select会阻塞直到检测到事件或则超时,如果超时,select会返回0,
如果检测到事件会返回1,如果异常会返回-1,如果是由于信号中断引起的异常errno==EINTR*/
}while(ret< && errno == EINTR); //如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时 if(ret==-) //失败
{
return -;
}
if(ret==) //select超时退出
{
errno= ETIMEDOUT;
return -;
}
}
//select在指定时间内检测到套接字可连接即三次握手完成或者不需要select检测
if(addr!=NULL)
{
ret=accept(fd,(struct sockaddr *)addr,&addr);
}
else
{
ret=accept{fd,NULL,NULL};
}
if (ret==-) //连接失败
ERR_EXIT("accept"); return ret; //返回连接的套接字
}
(4)connect超时函数的封装
在调用connect前需要使用fcntl 函数将套接字标志设置为非阻塞,如果网络环境很好,则connect立即返回0,不进入if 大括号执行;如果网络环境拥塞,则connect返回-1且errno == EINPROGRESS,表示正在处理。此后调用select与前面3个函数类似,但这里关注的是可写事件,因为一旦连接建立,套接字就可写。还需要注意的是当select 返回1,可能有两种情况,一种是连接成功,一种是套接字产生错误,由这里可知,这两种情况都会产生可写事件,所以需要使用getsockopt来获取一下。退出之前还需重新将套接字设置为阻塞。
/**
* activiate_nonblock 设置IO为非阻塞模式
* fd 文件描述符
*/
void activiate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd,F_GETFL); //获取fd的当前标记
if(flags == -)
ERR_EXIT("fcntl"); flags |= O_NONBLOCK; //与新标记逻辑或
ret = fcntl(fd,F_SETFL,flags); //设置标记
if(ret == -)
ERR_EXIT("fntl");
}
/**
* deactiviate_nonblock 设置IO为阻塞模式
* fd 文件描述符
*/
void deactiviate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd,F_GETFL); //获取fd的当前标记
if(flags == -)
ERR_EXIT("fcntl"); flags &=~O_NONBLOCK; //与新标记逻辑与
ret = fcntl(fd,F_SETFL,flags); //设置标记
if(ret == -)
ERR_EXIT("fntl");
} /**
*connect_timeout 带超时的accept函数
*fd:文件描述符
*addr 要连接的对等方的地址结构
*wait_seconds:等待超时秒数,如果为0则表示正常模式
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
*/
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
{
int ret=; //默认为0,当wait_seconds==0时,不检测直接返回0
socklen_t addrlen =sizeof(struct sockaddr_in);
if(wait_seconds>) //需要检测超时
activiate_nonblock(fd); //设置套接字为非阻塞模式
ret=connect(fd,(struct sockaddr*)addr,addrlen);
if(ret< && 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 = ;
do
{
/*一旦连接建立,套接字就处于可写的状态*/
ret=select(fd+,NULL,connect_fdset,NULL,&timeout);
/*select会阻塞直到检测到事件或则超时,如果超时,select会返回0,
如果检测到事件会返回1,如果异常会返回-1,如果是由于信号中断引起的异常errno==EINTR*/
}while(ret< && errno == EINTR); //如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时 if(ret==-) //失败
{
return -;
}
else if(ret==) //select超时退出
{
errno= ETIMEDOUT;
return -;
}
else if(ret==)
{
/*ret为1有两种情况,一种是连接建立成功,一种是套接字产生错误
此时错误信息不回保存在errno变量中,因此,需要调用getsockopt函数来获取。*/
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);//获取套接字的错误放在err中
if(sockoptret == -) //调用getsockopt失败
{
return -;
}
if(err==) //表示没有错误即套接字建立连接成功
ret=;
else //套接字产生错误
{
errno=err;
ret=-;
}
}
} if(wait_seconds>)
{
deactiviate_nonblock(fd); //重新将套接字设为阻塞模式
}
return ret;
}
connect超时测试程序
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<sys/wait.h> //*进程用的头文件*/
#include<netinet/in.h>
#include<arpa/inet.h>
#include <unistd.h> //fcntl的头文件
#include <fcntl.h>
#include <sys/time.h> #define MAXBYTEMUN 1024 /**
* activiate_nonblock 设置IO为非阻塞模式
* fd 文件描述符
*/
void activiate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd,F_GETFL); //获取fd的当前标记
if(flags == -)
perror("fcntl"); flags |= O_NONBLOCK; //与新标记逻辑或
ret = fcntl(fd,F_SETFL,flags); //设置标记
if(ret == -)
perror("fntl");
}
/**
* deactiviate_nonblock 设置IO为阻塞模式
* fd 文件描述符
*/
void deactiviate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd,F_GETFL); //获取fd的当前标记
if(flags == -)
perror("fcntl"); flags &=~O_NONBLOCK; //与新标记逻辑与
ret = fcntl(fd,F_SETFL,flags); //设置标记
if(ret == -)
perror("fntl");
} /**
*connect_timeout 带超时的accept函数
*fd:文件描述符
*addr 要连接的对等方的地址结构
*wait_seconds:等待超时秒数,如果为0则表示正常模式
*成功(未超时)返回0,失败返回-1,超时返回-1并且errno=ETIMEDOUT
*/
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
{
int ret=; //默认为0,当wait_seconds==0时,不检测直接返回0
socklen_t addrlen =sizeof(struct sockaddr_in);
if(wait_seconds>) //需要检测超时
activiate_nonblock(fd); //设置套接字为非阻塞模式
ret=connect(fd,(struct sockaddr*)addr,addrlen);
if(ret< && 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 = ;
do
{
/*一旦连接建立,套接字就处于可写的状态*/
ret=select(fd+,NULL,connect_fdset,NULL,&timeout);
/*select会阻塞直到检测到事件或则超时,如果超时,select会返回0,
如果检测到事件会返回1,如果异常会返回-1,如果是由于信号中断引起的异常errno==EINTR*/
}while(ret< && errno == EINTR); //如果是有信号引起的异常则继续阻塞select,直到检测到事件或则超时 if(ret==-) //失败
{
return -;
}
else if(ret==) //select超时退出
{
errno= ETIMEDOUT;
return -;
}
else if(ret==)
{
/*ret为1有两种情况,一种是连接建立成功,一种是套接字产生错误
此时错误信息不回保存在errno变量中,因此,需要调用getsockopt函数来获取。*/
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);//获取套接字的错误放在err中
if(sockoptret == -) //调用getsockopt失败
{
return -;
}
if(err==) //表示没有错误即套接字建立连接成功
ret=;
else //套接字产生错误
{
errno=err;
ret=-;
}
}
} if(wait_seconds>)
{
deactiviate_nonblock(fd); //重新将套接字设为阻塞模式
}
return ret;
} /***********测试程序***************/
int main(int argc,char *argv[])
{
int sock_fd,numbytes,maxfd,fd_stdin,nready;
// char buf[MAXBYTEMUN];
struct hostent;
struct sockaddr_in client_addr;//客户机的地址信息
ssize_t ret;
char recvbuf[]={''},sendbuf[]={''};
fd_set rset;
int stdineof; if(argc!=)
{
fprintf(stderr,"usage: client IPAddress\n"); //执行客户端程序时,输入客户端程序名称和其IP地址
exit();
} /*创建套接字*/
sock_fd=socket(AF_INET,SOCK_STREAM,);//采用IPv4协议
if(sock_fd==-)
{
perror("creat socket failed");
exit();
} /*服务器地址参数*/
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons();
client_addr.sin_addr.s_addr=inet_addr(argv[]);
bzero(&client_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 /*连接到服务器*/
ret=connect_timeout(sock_fd,(struct sockaddr*)&client_addr,)
if(ret==- && errno ==ETIMEDOUT)
{
perror("connect timedout\n");
exit();
}
else if(ret==-)
perror("connect error\n") if((numbytes=recv(sock_fd,recvbuf,MAXBYTEMUN,))==-)
{
perror("receive failed");
exit();
} recvbuf[numbytes]='\0';//在字符串末尾加上\0,否则字符串无法输出
printf("Received: %s\n",recvbuf);
return ;
}
因为是在本机上测试,所以不会出现超时的情况,但出错的情况还是可以看到的,比如不要启动服务器端程序,而直接启动客户端程序,会报错如下
在connect_timeout函数里面打印输出信息,可以跟踪程序的执行
同样只开启客户端程序会出现如下结果
如果开启服务端之后在开启客户端,出现如下结果
根据这些打印输出的结果可以看到connect_timeout函数的执行路劲
套接字IO超时设置和使用select实现超时管理的更多相关文章
- windows下的套接字IO模型
一般情况下,IO操作的行为受两种因素的影响: IO操作对象的类型(阻塞还是非阻塞) 获取IO操作结果的方式(同步还是异步). 同步就是指操作的发起和操作结果的获取由调用者完成. 异步指操作发起由调用方 ...
- select实现超时(套接字IO超时设置)
实现超时的三种方式: 1.SIGALARM信号 void handler(int sig) { return 0; } signal(SIGALRM,handler); alarm(5); int ...
- Socket编程实践(9) --套接字IO超时设置方法
引:超时设置3种方案 1. alarm超时设置方法 //代码实现: 这种方式较少用 void sigHandlerForSigAlrm(int signo) { return ; } signal(S ...
- Unix网络编程 高级IO套接字设置超时
我们知道.对于一个套接字的读写(read/write)操作默认是堵塞的.假设当前套接字还不可读/写,那么这个操作会一直堵塞下去,这样对于一个须要高性能的server来说,是不能接受的.所以,我们能够在 ...
- UNIX网络编程——设置套接字超时
在涉及套接字的I/O操作上设置超时的方法有以下3种: 调用alarm,它在指定超时期时产生SIGALRM信号.这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm ...
- Linux编程---套接字
网络相关的东西差点儿都是建立在套接字之上.所以这个内容对于程序猿来说还是蛮重要的啊. 事实上套接字也就是一个特殊的设备文件而已,我始终不能明确为什么要叫套接字.这么个奇怪的名字.只是还是就这样算了吧. ...
- Java Socket:飞鸽传书的网络套接字
在古代,由于通信不便利,一些聪明的人就利用鸽子会飞且飞得比较快.会辨认方向的优点,对其进行了驯化,用来进行消息的传递——也就是所谓的“飞鸽传书”.而在 Java 中,网络套接字(Socket)扮演了同 ...
- socket - Linux 套接字
总览 #include <sys/socket.h> mysocket = socket(int socket_family, int socket_type, int protocol) ...
- linux学习笔记之套接字
一.基础知识. 1:套接字基础. 1,是通信端点的抽象. 2,在UNIX类系统中被当作是一种文件描述符. 3,套接字通信域. 域 描述 AF_INET IPV4因特网域 AF_INET6 IPV6因特 ...
随机推荐
- Bitcask 存储模型
Bitcask 存储模型 Bitcask 是一个日志型.基于hash表结构的key-value存储模型,以Bitcask为存储模型的K-V系统有 Riak和 beansdb新版本. 日志型数据存储 何 ...
- Lua 之os库
标准os库 os.rename(oldname, newname) 文件重命名: os.remove(filename) 删除一个文件 os.execute(cmd) os.execute可运行一条系 ...
- 抓取网站数据不再是难事了,Fizzler(So Easy)全能搞定
首先从标题说起,为啥说抓取网站数据不再难(其实抓取网站数据有一定难度),SO EASY!!!使用Fizzler全搞定,我相信大多数人或公司应该都有抓取别人网站数据的经历,比如说我们博客园每次发表完文章 ...
- 密钥文件snk
1.(what)是什么? 由一个程序集的标识组成并通过公钥和数字签名(针对该程序集生成)加强的名称,其中的标识包括程序集的简单文 本名称.版本号和区域性信息(如果提供的话). 2.(why)为什么 ...
- android控件库(2)-仿Google Camera 的对焦效果
一直很喜欢Google Camera的自动对焦效果,今日闲来无事,自己做了个: 废话不多说,代码才是王道: package com.example.test.view; import com.exam ...
- ManualResetEvent详解
原文来自:http://www.cnblogs.com/tianzhiliang/archive/2011/03/04/1970726.html 1. 源码下载: 下载地址:http://files. ...
- 使用cachemanager做缓存(Session的缓存)
1.我在这里直接用 cachemanager.redis 往redis里面存储缓存数据2.步骤 1)下载CacheManager.Redis(包含了CacheManager.Core) 下载Stack ...
- 【AngularJS】—— 7 模块化
AngularJS有几大特性,比如: 1 MVC 2 模块化 3 指令系统 4 双向数据绑定 那么本篇就来看看AngularJS的模块化. 首先先说一下为什么要实现模块化: 1 增加了模块的可重用性 ...
- 学习(主题或切入点)checklist1
业务+技术+架构+运维+管理 技术学习:http://www.runoob.com/mongodb/mongodb-query.html 一.技术篇补充学习列表 1,mongodb(o) 2,red ...
- 跨Controllers传数据
今天遇到两个问题,第一个是跨controller传值,后一个是比较简单的linq数据库查询问题.先描述以下问题我有一个入库单和一个入库明细,入库的逻辑是先填写入库单在填入库明细.两者要么同时完成,要么 ...