You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android应用连接Servlet遭遇SSL握手失败问题求助

解决Android SSL握手失败(WRONG_VERSION_NUMBER)问题

从你的代码和Logcat日志来看,核心问题是SSL协议版本不匹配导致的握手失败,错误提示OPENSSL_internal:WRONG_VERSION_NUMBER已经把问题点得很清楚了。我帮你拆解下原因和解决方案:

可能的原因

  • 你用https://访问了一个只支持http的服务器端口(本地服务器可能没配置SSL证书,直接开的HTTP服务)
  • 服务器端启用了HTTPS,但使用的SSL协议版本过旧(比如SSLv3),而Android 7.1默认禁用了这类不安全的旧协议
  • 服务器的SSL配置和Android端支持的协议套件不兼容

分步解决方案

1. 先排查最常见的低级错误:HTTP/HTTPS混淆

如果你的本地服务器只是普通的HTTP Servlet(没有配置SSL证书),直接把代码里的请求地址从:

URL url = new URL("https://192.168.0.7:9999/WebS/welcome/test");

改成:

URL url = new URL("http://192.168.0.7:9999/WebS/welcome/test");

这是最容易踩的坑——用HTTPS协议去连HTTP端口,直接就会触发版本不匹配的错误。

2. 服务器确实用了HTTPS?调整Android端SSL协议支持

如果服务器配置了HTTPS但协议版本偏旧,你可以自定义SSLSocketFactory来兼容支持的协议版本。比如在打开URLConnection之前添加以下配置:

// 创建SSLContext,支持TLSv1.0、TLSv1.1、TLSv1.2
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, new SecureRandom());

// 强制转换为HttpsURLConnection并设置自定义SocketFactory
HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
httpsConn.setSSLSocketFactory(new SSLSocketFactoryWrapper(sslContext.getSocketFactory()));

// 内部类:包装SSLSocketFactory,指定允许的协议
private static class SSLSocketFactoryWrapper extends SSLSocketFactory {
    private final SSLSocketFactory delegate;

    SSLSocketFactoryWrapper(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        Socket socket = delegate.createSocket(s, host, port, autoClose);
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.0", "TLSv1.1", "TLSv1.2"});
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        Socket socket = delegate.createSocket(host, port);
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.0", "TLSv1.1", "TLSv1.2"});
        return socket;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        Socket socket = delegate.createSocket(host, port, localHost, localPort);
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.0", "TLSv1.1", "TLSv1.2"});
        return socket;
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        Socket socket = delegate.createSocket(host, port);
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.0", "TLSv1.1", "TLSv1.2"});
        return socket;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        Socket socket = delegate.createSocket(address, port, localAddress, localPort);
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.0", "TLSv1.1", "TLSv1.2"});
        return socket;
    }
}

3. 额外修复代码里的两个小问题

你的代码还有两个潜在问题,不修复会导致后续报错:

  • Toast.makeText(MainActivity.this, in.toString(), Toast.LENGTH_LONG).show(); 不能在子线程调用,会触发线程异常,需要用runOnUiThread包裹
  • in.toString()获取的是BufferedReader对象的内存地址,不是服务器返回的实际内容,应该循环读取响应

修改后的execute方法片段:

BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
    response.append(line);
}
final String responseStr = response.toString();
// 回到主线程显示Toast
runOnUiThread(() -> Toast.makeText(MainActivity.this, responseStr, Toast.LENGTH_LONG).show());
in.close();

总结

先从最简单的HTTP/HTTPS切换开始排查,这大概率能解决你的问题;如果确实是SSL协议版本问题,再用自定义SSLSocketFactory的方式兼容。另外记得修复子线程调用Toast和读取响应的错误。

内容的提问来源于stack exchange,提问作者girish

火山引擎 最新活动