SSL Socket连接偶发创建失败咨询:执行停滞无报错排查
Socket连接偶发停滞无响应,REST调用却正常
问题背景与代码实现
我通过继承Thread的类尝试与指定地址建立Socket连接,重写的run方法及相关代码如下:
private String headerForGetConnection() { return String.format( "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nx-sessioncookie: %s\r\nAccept: application/x-rtsp-tunnelled\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", addressAttributes(), this.USER_AGENT, this.session_cookie ); } private String addressAttributes() { return "/img/video.sav?MAC=" + this.camera.getDevice().getMac() + "&GUID=" + "GUID value"; } @Override public void run() { super.run(); int count = 0; int MAX_CONNECTION_TRY = 3; while (count < MAX_CONNECTION_TRY && !shouldCloseConnection) { TCLog.d(TAG, "counting value " + count); count++; socket = null; bufferedInputStream = null; printWriter = null; try { TCLog.d(TAG, "media serverAddress " + serverAddress + " serverPort == " + serverPort); SocketFactory socketFactory = SSLSocketFactory.getDefault(); socket = socketFactory.createSocket(serverAddress, serverPort); TCLog.d(TAG, "Media server socket value after connecting to address and port == " + socket); if (null == bufferedInputStream) { bufferedInputStream = new BufferedInputStream(socket.getInputStream()); } } catch(UnknownHostException e) { TCLog.d(TAG, "value UnknownHostException" + e.getMessage()); handleError(e.getMessage()); } catch(IOException e) { TCLog.d(TAG, "value IOException " + e.getMessage()); handleError(e.getMessage()); } try { printWriter = new PrintWriter(socket.getOutputStream()); printWriter.println(serverHeader); printWriter.flush(); isFirstMessage = true; } catch(IOException e) { TCLog.d(TAG, "value IOException while writing " + e.getMessage()); handleError(e.getMessage()); } while (!shouldCloseConnection) { try { String dataToSend = null; byte[] readingData = new byte[BUFFER_SIZE]; int read; byte[] data = null; if ((read = bufferedInputStream.read(readingData)) != -1) { TCLog.d(TAG, "OTHER read value " + read); data = Arrays.copyOfRange(readingData, 0, read); dataToSend = new String(data); } if (null != dataToSend) { TCLog.d(TAG, "message from server " + dataToSend); if (isFirstMessage) { if (!dataToSend.contains("200 OK")) { if (dataToSend.contains("303")) { TCLog.d(TAG, "when there is redirect"); TCLog.d(TAG, "splitting string value"); String[] videos = dataToSend.split("https://"); TCLog.d(TAG, "values 1 " + videos[0]); TCLog.d(TAG, "value 2 " + videos[1]); String redirectUrl = videos[1]; videos = redirectUrl.split(":"); redirectUrl = videos[0]; TCLog.d(TAG, "redirect URL is " + redirectUrl); isRedirectUrl = true; this.serverAddress = redirectUrl; Reflector.address = this.serverAddress; break; } else if (dataToSend.contains("HTTP/1.0 4")) { isRedirectUrl = false; break; } } else { this.isFirstMessage = false; if (null != listener) { this.listener.getConnectionConnectedSuccessfully(); } } } else { if (null != listener) { listener.getConnectionRawData(data, dataToSend.trim()); } } } } catch(IOException e) { TCLog.d(TAG, "value IOException while reading " + e.getMessage()); handleError(e.getMessage()); } finally { TCLog.d(TAG, "inside finally block"); if (isFirstMessage && !isRedirectUrl) { handleError("Unable to play video"); if (null != printWriter) { printWriter.close(); } try { TCLog.d(TAG, "inside finally try block. Closing connections"); if (null != bufferedInputStream) { bufferedInputStream.close(); } if (null != socket && socket.isConnected()) { socket.close(); } } catch(IOException e) { TCLog.d(TAG, "error while closing streams " + e.getMessage()); } } } } if (!isRedirectUrl) { break; } } if (count >= MAX_CONNECTION_TRY && !shouldCloseConnection) { handleError("Unable to establish connection"); } }
问题现象
程序偶发出现异常:打印media serverAddress日志后,Socket创建后的代码完全停滞执行,无任何报错日志,但有时连接又能成功完成。
补充信息
Socket连接成功后需发送特定GET请求,我将该请求转为API/REST调用时,每次都能收到服务器响应(即使有10-12秒延迟),怀疑并非服务器配置问题。
排查思路与解决建议
1. 给Socket设置超时时间
当前代码创建Socket时未设置连接超时和读取超时,一旦服务器端响应缓慢或网络异常,createSocket或后续的read操作会无限阻塞。添加超时设置:
socket = socketFactory.createSocket(serverAddress, serverPort); socket.setSoTimeout(15000); // 设置15秒读取超时 // 或者创建时直接指定连接超时 socket = socketFactory.createSocket(serverAddress, serverPort, InetAddress.getLocalHost(), 0, 15000);
超时后会抛出SocketTimeoutException,可捕获处理避免线程永久挂起。
2. 修复异常处理漏洞
- 如果
createSocket抛出异常,后续printWriter初始化逻辑仍会执行,此时socket为null会触发未捕获的NPE,需将printWriter初始化移到createSocket成功后的try块内。 - 线程终止标志
shouldCloseConnection需声明为volatile,确保线程能及时感知终止信号,避免可见性问题导致的无效阻塞。
3. 排查SSL握手阻塞
使用SSLSocket时,握手阶段可能因证书验证、加密套件协商等问题阻塞。可开启SSL调试日志定位问题:
System.setProperty("javax.net.debug", "ssl");
同时可给SSL握手添加超时控制,避免长时间阻塞。
4. 检查BufferedInputStream阻塞逻辑
bufferedInputStream.read(readingData)是阻塞式操作,读取前可先检查socket.isClosed()状态,结合超时设置,避免在已关闭的Socket上持续等待。
5. 验证线程状态与资源泄漏
停滞时用jstack工具查看线程状态,确认是WAITING还是BLOCKED状态,定位具体阻塞点。同时检查是否存在Socket资源未正确关闭导致的连接池耗尽问题。
6. 改用成熟HTTP客户端替代手动Socket实现
REST调用使用的成熟HTTP客户端(如OkHttp)会自动处理超时、重定向、连接池等逻辑,可直接改用这类库替代手动Socket实现,减少底层问题排查成本。
内容的提问来源于stack exchange,提问作者Arun Mehra
相关产品推荐
相关产品推荐

