C++客户端如何解决Connect超时导致的阻塞问题

这几天发现一个现象,客户端正常连接服务器时候,connect显然不会出现问题。

在异常情况下,如果是服务器出现异常,connect能够立即返回失败;

但是当客户端出现异常的情况下,分为两种情况:

  1. 一种是不插网线,客户端没有获得ip地址,在这种情况下,connect也可以立即返回错误;

  2. 二是但是当客户端插上网线,但是连接网络失败,也就是说能够获取到ip地址,但是和服务器是ping不通的。

这种情况下connect就可能会发生阻塞。
因为按照《UNIX 网络编程》中讲解,connect的在进行三次握手,如果失败情况,需要等待75s的超市时间的。

我们这里主要讨论第二种情况如何解决,可以让connect快速返回结果,不至于阻塞等待超长的时间。

如下是我的代码:

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

/******************************
* Time out for connect()
******************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>


#define TIME_OUT_TIME 20 //connect超时时间20秒

bool setBlockOpt(int m_fd,bool blocked)
{

#ifndef WIN32
int flags;
flags = fcntl(m_fd, F_GETFL, 0);
if(flags < 0)
{
return false;
}
if(blocked)
{
printf("Set BLOCK !!!\n");
flags &= ~O_NONBLOCK;
}
else
{
printf("Set NONBLOCK !!!\n");
flags |= O_NONBLOCK;
}
if(fcntl(m_fd, F_SETFL, flags) < 0)
{
return false;
}

#else
u_long ulValue;
if(blocked)
{
ulValue = 1;
}
else
{
ulValue = 0;
}
int n = ioctlsocket(m_fd, FIONBIO, &ulValue);
if (n != 0)
{
return false;
}
#endif
return true;
}

int connectWithTimeout(int m_fd,int timeout)
{
int selectFlag = -1;
int error=-1, len;
len = sizeof(int);
bool ret = false;
int connectFlag = -1;

const char* m_ip = "115.239.210.27";
int m_port = 80;

if("" == m_ip || 0 > m_port)
{
return -1;
}

if(m_fd < 0 && "" != m_ip && m_port >=0)
{
m_fd = socket(AF_INET, SOCK_STREAM, 0);
if(m_fd < 0)
{
return -1;
}
}

if(m_fd < 0)
{
return -1;
}

struct sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons((unsigned short)m_port);
servAddr.sin_addr.s_addr = inet_addr(m_ip);

setBlockOpt(m_fd,false); //设置为非阻塞模式
if( (connectFlag= connect(m_fd, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0))
{
if(errno != EINPROGRESS)
{
goto done;
}
}
else
{
ret = true;
goto done;
}
timeval tm;
tm.tv_sec = timeout/1000;
tm.tv_usec = timeout%1000;
fd_set rest, west;
FD_ZERO(&rest);
FD_ZERO(&west);
FD_SET(m_fd, &rest);
FD_SET(m_fd, &west);

if( (selectFlag = select(m_fd+1, &rest, &west, NULL, &tm)) > 0)
{
//如果套接口及可写也可读,需要进一步判断
if(FD_ISSET(m_fd, &rest) && FD_ISSET(m_fd, &west))
{
if(getsockopt(m_fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) < 0)
{
printf("getsockopt error!!!\n");
}
else
{
if(error == 0)
{
ret = true;
}
else
{
printf("connect getsockopt error!!! %d\n",error);
}
}
}
//如果套接口可写不可读,则链接完成
else if(FD_ISSET(m_fd, &west) && !FD_ISSET(m_fd, &rest))
{
ret = true;
}
}
else if(selectFlag == 0)
{
printf("connect select timeout!!!\n");
}
else
{
printf("connect select error!!!\n");
}

done:
setBlockOpt(m_fd,true);// 设置为阻塞模式
if(!ret)
{
return -1;
}
return 0;
}


int main(int argc,char* argv[])
{
if(argc <= 1)
{
printf("input error!!!\n");
exit(1);
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
exit(1);
}
if(connectWithTimeout(sockfd,atoi(argv[1])) == 0)
{
printf("connect sucess!!!\n");
}
else
{
printf("connect filed!!!\n");
}
close(sockfd);


return 0;
}