下面程序包含read_timeout、write_timeout、accept_timeout、connect_timeout 四个函数封装:

/* read_timeout - 读超时检测函数,不含读操作
* fd:文件描述符
* wait_seconds:等待超时秒数, 如果为0表示不检测超时;
* 成功(未超时)返回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会阻塞直到检测到事件或者超时
// 如果select检测到可读事件发送,则此时调用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表示不检测超时;
* 成功(未超时)返回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_timeout - 带超时的accept
* fd: 套接字
* addr: 输出参数,返回对方地址
* 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;
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;
}
} if (addr != NULL)
ret = accept(fd, (struct sockaddr *)addr, &addrlen);
else
ret = accept(fd, NULL, NULL);
if (ret == -1)
ERR_EXIT("accpet error"); return ret;
} /* activate_nonblock - 设置IO为非阻塞模式
* fd: 文件描述符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
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为阻塞模式
* fd: 文件描述符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
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
* 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;
socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > 0)
activate_nonblock(fd); 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
{
/* 一旦连接建立,套接字就可写 */
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来获取 */
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;
else//套接字错误
{
errno = err;
ret = -1;
}
}
} if (wait_seconds > 0)
deactivate_nonblock(fd); return ret;
}

下面来解析一下这些函数的封装:

1、read_timeout :如注释所写,这只是读超时检测函数,并不包含读操作,如果从此函数成功返回,则此时调用read将不再阻塞,测试代码可以这样写:

int ret;
ret = read_timeout(fd, 5);
if (ret == 0)
read(fd, buf, sizeof(buf));
else if (ret == -1 && errno == ETIMEOUT)
printf("timeout...\n");
else
ERR_EXIT("read_timeout");

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

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

2、write_timeout :此函数跟read_timeout 函数类似,只是select 关心的是可写事件,不再赘述。

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

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

我们可以写个小程序测试一下connect_timeout 函数,客户端程序如下:

#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 <fcntl.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0) /* activate_nonblock - 设置IO为非阻塞模式
* fd: 文件描述符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
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为阻塞模式
* fd: 文件描述符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
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
* 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;
socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > 0)
activate_nonblock(fd); 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
{
/* 一旦连接建立,套接字就可写 */
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来获取 */
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;
else//套接字错误
{
errno = err;
ret = -1;
}
}
} if (wait_seconds > 0)
deactivate_nonblock(fd); return ret;
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket"); 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("192.168.2.103"); 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"); struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if (getsockname(sock, (struct sockaddr *)&localaddr, &addrlen) < 0)
ERR_EXIT("getsockname"); printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); return 0;
}

因为是在本机上测试,所以不会出现超时的情况,但出错的情况还是可以看到的,比如不要启动服务器端程序,而直接启动客户端程序,输出如下:

huangcheng@ubuntu:~$ ./cli
connect_timeout: Connection refused

很明显是connect_timeout 函数返回了-1,我们也可以推算出connect_timeout 函数中,select返回1,但却是套接字发生错误的情况,errno = ECONNREFUSED,所以打印出Connection refused。

UNIX网络编程——使用select 实现套接字I/O超时的更多相关文章

  1. [转载] 读《UNIX网络编程 卷1:套接字联网API》

    原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/ 文章写的很清楚, 适合初学者 最近看了<UNIX网 ...

  2. 《Unix网络编程卷1:套接字联网API》读书笔记

    第一部分:简介和TCP/IP 第1章:简介 第2章:传输层:TCP.UDP和SCTP TCP:传输控制协议,复杂.可靠.面向连接协议 UDP:用户数据报协议,简单.不可靠.无连接协议 SCTP:流控制 ...

  3. UNIX 网络编程笔记-CH3:套接字编程简介

    IPv4套接字地址结构 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* length of ...

  4. 《UNIX网络编程 卷1:套接字联网API》读书笔记(一):网络编程简介

    概述 要编写通过计算机网络通信的程序,首先要确定这些程序相互通信所用的协议.大多数网络是按照划分成客户和服务器来组织的.本章及后续章节的焦点是TCP/IP协议族,也可称为网际协议族.下图为客户与服务器 ...

  5. UNIX网络编程——经常使用的套接字选项

    1.设置/获取套接字选项 int setsockopt(int socket, int level, int option_name, const void *option_value, sockle ...

  6. UNIX网络编程 第3章 套接字编程简介

    套接字结构类型和相关的格式转换函数

  7. UNIX网络编程——使用select函数编写客户端和服务器

    首先看原先<UNIX网络编程--并发服务器(TCP)>的代码,服务器代码serv.c: #include<stdio.h> #include<sys/types.h> ...

  8. 网络编程初识和socket套接字

    网络的产生 不同机器上的程序要通信,才产生了网络:凡是涉及到倆个程序之间通讯的都需要用到网络 软件开发架构 软件开发架构的类型:应用类.web类 应用类:qq.微信.网盘.优酷这一类是属于需要安装的桌 ...

  9. 网络编程(socket,套接字)

    服务端地址不变 ip + mac 标识唯一一台机器 ip +端口 标识唯一客户端应用程序 套接字: 网络编程   网络编程 一.python提供了两个级别访问的网络服务 低级别的网络服务支持基本的 S ...

随机推荐

  1. [Apio2009][bzoj1179]Atm

    题意:一个n个点m条单向边的图,每个点有权值,给定出发点和p个可以停止的点,你可以随便走一条路径从出发点走到一个可以停止的点,但是每个点的点权只能计算一次,求能得到的最大权值. n,m<=500 ...

  2. 什么是Hash?

    什么是Hash? Hash中文翻译为散列,又成为"哈希",是一类函数的统称,其特点是定义域无限,值域有限.把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换 ...

  3. SpringMVC中url-pattern /和/*的区别

    http://blog.csdn.net/u010648555/article/details/51612030

  4. js去掉最后一个字符

    console.log(("0,1,2,3,4,5,".slice(0,-1)))

  5. h5 网页版的微博微信QQ登录

    一:微博 1,先说微博吧,首先你的去http://open.weibo.com/wiki/先注册账号,通过验证审核.然后的创建网页应用.微博审核不通过的原因就是域名和网站地址,一定要按实际写的.一定要 ...

  6. jdk1.7和jdk1.8区别

    转自:http://www.2cto.com/kf/201307/225968.html 本文是我学习了解了jdk7和jdk8的一些新特性的一些资料,有兴趣的大家可以浏览下下面的内容. 官方文档:ht ...

  7. Hibernate给表和字段设置前后缀及分隔符

    在<一口一口吃掉Hibernate(一)--使用SchemaExport生成数据表>中介绍了如何生成数据表.但是这只是最基本的.hibernate在生成或者操作数据库时,会受一些限制.比如 ...

  8. 关于一些基础的Java问题的解答(四)

    16. Java面向对象的三个特征与含义 java中的面向对象的三大基本特征分别是:封装.继承.多态: 封装:把过程和数据包围起来,对数据的访问只能通过已定义的界面,主要是方便类的修改 继承:对象的一 ...

  9. Tomcat常用参数的配置

    1.修改端口号 Tomcat端口配置在server.xml文件的Connector标签中,默认为8080,可根据实际情况修改. 修改端口号 2.解决URL中文参数乱码 在server.xml文件的Co ...

  10. TypeScript知识点

    TypeScript介绍 TypeScript为JavaScript的超集(ECMAScript6), 这个语言添加了基于类的面向对象编程.TypeScript作为JavaScript很大的一个语法糖 ...