https://www.cnblogs.com/rockyching2009/p/11032229.html
一、背景
connect()是会阻塞的。
这意味着,作为客户端去连服务器等了好久都得不到相应,业务处理被推迟,更有甚者等到黄花谢了等来个失败(ETIMEDOUT)。
二、分析及方案
除了超时,其他connect()异常基本上立刻就可以得到反馈,这种处理起来也容易。
超时异常之所以让人头疼是因为超时时间太长,在默认配置的Linux下这个时间有一分多钟。
看下『UNIX Network Programming Volume 1』中4.3节对connect()的超时异常描述:
1 2 3 4 5 6 7 8 9 10 | In the case of a TCP socket, the connect function initiates TCP's three-way handshake. The function returns only when the connection is established or an error occurs. There are several different error returns possible. 1. If the client TCP receives no response to its SYN segment, ETIMEDOUT is returned. 4.4 BSD, for example, sends one SYN when connect is called, another 6 seconds later, and another 24 seconds later. If no response is received after a total of 75 seconds, the error is returned. 1. 如果TCP客户没有收到SYN分节的响应,则返回ETIMEDOUT。例如在4.4BSD中,当调用函数 connect 时,发出一个SYN, 若无响应,等待6秒之后再发一个;若仍无响应,24秒钟之后再发一个。若总共等待了75秒钟之后仍未响应,则返回错误。 |
如果可以随意设置超时时间就好了。
现实是美好的,确实可以做到随心所欲,那就是非阻塞调用。
对于非阻塞socket,超时到来后connect()返回EINPROGRESS异常。此外在connect()的man page上还提供了一个极好的处理方式:
1 2 3 4 5 | The socket is nonblocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure). |
三、方案实现
非阻塞+getsockopt(),具体实现可以参看Java的底层代码,贴下来:
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 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | openjdk-8u40-src-b25-10_feb_2015\openjdk\jdk\src\solaris\native\java\net\PlainSocketImpl.c /* * inetAddress is the address object passed to the socket connect * call. * * Class: java_net_PlainSocketImpl * Method: socketConnect * Signature: (Ljava/net/InetAddress;I)V */ JNIEXPORT void JNICALL Java_java_net_PlainSocketImpl_socketConnect(JNIEnv *env, jobject this , jobject iaObj, jint port, jint timeout) { jint localport = (*env)->GetIntField(env, this , psi_localportID); int len = 0; /* fdObj is the FileDescriptor field on this */ jobject fdObj = (*env)->GetObjectField(env, this , psi_fdID); jclass clazz = (*env)->GetObjectClass(env, this ); jobject fdLock; jint trafficClass = (*env)->GetIntField(env, this , psi_trafficClassID); /* fd is an int field on iaObj */ jint fd; SOCKADDR him; /* The result of the connection */ int connect_rv = -1; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException" , "Socket closed" ); return ; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (IS_NULL(iaObj)) { JNU_ThrowNullPointerException(env, "inet address argument null." ); return ; } /* connect */ if (NET_InetAddressToSockaddr(env, iaObj, port, ( struct sockaddr *)&him, &len, JNI_TRUE) != 0) { return ; } setDefaultScopeID(env, ( struct sockaddr *)&him); #ifdef AF_INET6 if (trafficClass != 0 && ipv6_available()) { NET_SetTrafficClass(( struct sockaddr *)&him, trafficClass); } #endif /* AF_INET6 */ if (timeout <= 0) { connect_rv = NET_Connect(fd, ( struct sockaddr *)&him, len); #ifdef __solaris__ if (connect_rv == JVM_IO_ERR && errno == EINPROGRESS ) { /* This can happen if a blocking connect is interrupted by a signal. * See 6343810. */ while (1) { #ifndef USE_SELECT { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; connect_rv = NET_Poll(&pfd, 1, -1); } #else { fd_set wr, ex; FD_ZERO(&wr); FD_SET(fd, &wr); FD_ZERO(&ex); FD_SET(fd, &ex); connect_rv = NET_Select(fd+1, 0, &wr, &ex, 0); } #endif if (connect_rv == JVM_IO_ERR) { if ( errno == EINTR) { continue ; } else { break ; } } if (connect_rv > 0) { int optlen; /* has connection been established */ optlen = sizeof (connect_rv); if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, ( void *)&connect_rv, &optlen) <0) { connect_rv = errno ; } if (connect_rv != 0) { /* restore errno */ errno = connect_rv; connect_rv = JVM_IO_ERR; } break ; } } } #endif } else { /* * A timeout was specified. We put the socket into non-blocking * mode, connect, and then wait for the connection to be * established, fail, or timeout. */ SET_NONBLOCKING(fd); /* no need to use NET_Connect as non-blocking */ connect_rv = connect(fd, ( struct sockaddr *)&him, len); /* connection not established immediately */ if (connect_rv != 0) { int optlen; jlong prevTime = JVM_CurrentTimeMillis(env, 0); if ( errno != EINPROGRESS) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException" , "connect failed" ); SET_BLOCKING(fd); return ; } /* * Wait for the connection to be established or a * timeout occurs. poll/select needs to handle EINTR in * case lwp sig handler redirects any process signals to * this thread. */ while (1) { jlong newTime; #ifndef USE_SELECT { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; errno = 0; connect_rv = NET_Poll(&pfd, 1, timeout); } #else { fd_set wr, ex; struct timeval t; t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&wr); FD_SET(fd, &wr); FD_ZERO(&ex); FD_SET(fd, &ex); errno = 0; connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t); } #endif if (connect_rv >= 0) { break ; } if ( errno != EINTR) { break ; } /* * The poll was interrupted so adjust timeout and * restart */ newTime = JVM_CurrentTimeMillis(env, 0); timeout -= (newTime - prevTime); if (timeout <= 0) { connect_rv = 0; break ; } prevTime = newTime; } /* while */ if (connect_rv == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException" , "connect timed out" ); /* * Timeout out but connection may still be established. * At the high level it should be closed immediately but * just in case we make the socket blocking again and * shutdown input & output. */ SET_BLOCKING(fd); JVM_SocketShutdown(fd, 2); return ; } /* has connection been established */ optlen = sizeof (connect_rv); if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, ( void *)&connect_rv, &optlen) <0) { connect_rv = errno ; } } /* make socket blocking again */ SET_BLOCKING(fd); /* restore errno */ if (connect_rv != 0) { errno = connect_rv; connect_rv = JVM_IO_ERR; } } /* report the appropriate exception */ if (connect_rv < 0) { #ifdef __linux__ /* * Linux/GNU distribution setup /etc/hosts so that * InetAddress.getLocalHost gets back the loopback address * rather than the host address. Thus a socket can be * bound to the loopback address and the connect will * fail with EADDRNOTAVAIL. In addition the Linux kernel * returns the wrong error in this case - it returns EINVAL * instead of EADDRNOTAVAIL. We handle this here so that * a more descriptive exception text is used. */ if (connect_rv == JVM_IO_ERR && errno == EINVAL) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException" , "Invalid argument or cannot assign requested address" ); return ; } #endif if (connect_rv == JVM_IO_INTR) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException" , "operation interrupted" ); #if defined(EPROTO) } else if ( errno == EPROTO) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ProtocolException" , "Protocol error" ); #endif } else if ( errno == ECONNREFUSED) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException" , "Connection refused" ); } else if ( errno == ETIMEDOUT) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException" , "Connection timed out" ); } else if ( errno == EHOSTUNREACH) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException" , "Host unreachable" ); } else if ( errno == EADDRNOTAVAIL) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException" , "Address not available" ); } else if (( errno == EISCONN) || ( errno == EBADF)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException" , "Socket closed" ); } else { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException" , "connect failed" ); } return ; } (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); /* set the remote peer address and port */ (*env)->SetObjectField(env, this , psi_addressID, iaObj); (*env)->SetIntField(env, this , psi_portID, port); /* * we need to initialize the local port field if bind was called * previously to the connect (by the client) then localport field * will already be initialized */ if (localport == 0) { /* Now that we're a connected socket, let's extract the port number * that the system chose for us and store it in the Socket object. */ len = SOCKADDR_LEN; if (JVM_GetSockName(fd, ( struct sockaddr *)&him, &len) == -1) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException" , "Error getting socket name" ); } else { localport = NET_GetPortFromSockaddr(( struct sockaddr *)&him); (*env)->SetIntField(env, this , psi_localportID, localport); } } } |
参考资料
1、Linux man
2、《UNIX网络编程(卷1)》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!