一、使用alarm 函数设置超时

C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
 
void handler( 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);
.................

程序大概框架如上所示,如果read在5s内被SIGALRM信号中断而返回,则表示超时,否则未超时已读取到数据,取消闹钟。但这种方法不常用,因为有时可能在其他地方使用了alarm会造成混乱。

二、使用套接字选项SO_SNDTIMEO、SO_RCVTIMEO

C++ Code 
1
2
3
4
5
6
 
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,  5);

int ret = read(sock, buf,  sizeof(buf));
if (ret == - 1 && errno == EWOULDBLOCK)
    errno = ETIMEOUT;
..........

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

三、使用select 实现超时

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

C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
 
/*************************************************************************
    > File Name: sysutil.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Sat 02 Mar 2013 10:53:06 PM CST
 ************************************************************************/

#include  "sysutil.h"

/* 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将不再阻塞,测试代码可以这样写:
C++ Code 
1
2
3
4
5
6
7
8
 
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 函数,客户端程序如下:

C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
#include  "sysutil.h"

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( "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");

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

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

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_timeout 
connect_timeout: Connection refused

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

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

《UNP》

转载于:https://www.cnblogs.com/snake-hand/archive/2013/06/11/3132321.html

linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时相关推荐

  1. linux网络编程Internet Socket地址,套接字,和函数

    文章内容节选<linux/UNIX 系统网络编程> Internet domain socket地址有两种:IPv4 IPv6 IPv4被存储在结构体中, 该结构体在 netinet/in ...

  2. Linux网络编程之Socket套接字

    一.Socket到底是什么 socket 这个英文单词的原意是"插口""插槽", 在网络编程中,它的意思是可以通过插口接入的方式,快速完成网络连接和数据收发.你 ...

  3. linux网络编程之Socket编程

    (1)socket套接字 1)在linux环境下,socket用于表示进程间网络通信的特殊文件类型,其本质是内核借助缓冲区形成的伪文件(不占磁盘空间,除此之外还有二进制文件,管道,字符文件). 2)伪 ...

  4. linux网络编程之socket编程(六)

    经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:"真爱生活,珍惜生命",好了,言归正传. ...

  5. linux网络编程之socket:使用fork并发处理多个client的请求

    在回射客户/服务器程序中,服务器只能处理一个客户端的请求,如何同时服务多个客户端呢?在未讲到select/poll/epoll等高级IO之前,比较老土的办法是使用fork来实现.网络服务器通常用for ...

  6. linux多网卡网络编程,Linux网络编程之Socket初探

    Socket由来 Socket 的英文原意就是"孔"或"插座",现在,作为 BSD UNIX 的进程通讯机制,取其后一种意义.一起看下网络编程里说的socket ...

  7. linux网络编程之socket(十):shutdown 与 close 函数 的区别

    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据 ...

  8. Linux网络编程之socket文件传输示例

    本文所述示例程序是基于Linux平台的socket网络编程,实现文件传输功能.该示例是基于TCP流协议实现的socket网络文件传输程序.采用C语言编写.最终能够实现传输任何格式文件的文件传输程序. ...

  9. 网络编程之socket

    网络编程之socket 看到本篇文章的题目是不是很疑惑,what is this?,不要着急,但是记住一说网络编程,你就想socket,socket是实现网络编程的工具,那么什么是socket,什么是 ...

最新文章

  1. swift_025(Swift 的自动引用计数(ARC)
  2. python读取nii文件_python实现批量nii文件转换为png图像
  3. typedef VS #define —— C语言中的 关键字 与 C指令
  4. treasure what you have now
  5. unix 登录mysql_实例分析mysql用户登录。
  6. linux vi 复制一个单词_vi或vim的快速操作技巧你知道吗?
  7. Android如何获取Wifi名称即SSID
  8. db2 程序连接字符串
  9. Atitit 延迟绑定架构法attilax总结
  10. docker 集群swarm搭建
  11. 巧用京东物流分享链接批量查询多个京东快递的物流信息
  12. 一分钟教程:绘制中国地图
  13. Microsoft Edge浏览器重新打开主页没有变化但会重新打开一个淘宝天猫页面的解决办法
  14. 下载keep运动软件_Keep健身软件官方最新版下载-Keep健身软件下载v6.40.0安卓版-西西软件下载...
  15. Source Insight 使用
  16. 面向对象,面向对象的优点
  17. Task 1 知识图谱介绍
  18. 我的苹果电脑中毒了?mac也会中病毒?喜闻乐见(附杀毒软件测试)
  19. LEHDLAB-实验记录
  20. 全球致盲眼疾排名第一能轻松治疗,第二名却很难处理...

热门文章

  1. golang中的死锁
  2. NBIOT 移远BC28模块+stm32开发板例程、教程(打通TCP、COAP协议)
  3. python三:if...else
  4. thinkphp-比较标签-eq
  5. hdu1280 前m大的数(数组下标排序)
  6. MySQL中TIMESTAMPDIFF和TIMESTAMPADD函数的用法
  7. 应对电信劫持强行插入广告的处理
  8. Debian | 软件安装升级点滴记录
  9. Linux基础学习笔记-第五课:文件权限
  10. (笔试题)将数组分成两组,使两组的和的差的绝对值最小