Java8:如何为SSLSocket.startHandshake()执行超时

Java8:如何为SSLSocket.startHandshake()执行超时,java,multithreading,sockets,java-8,handshake,Java,Multithreading,Sockets,Java 8,Handshake,我们有下面的线程来执行SSLHandshake,但在一些边缘情况下,我注意到((SSLSocket)clientSocket)被永久阻止,它不会进入下一个块,而循环代码,其中SSL\u握手\u超时为1500毫秒,并且工作正常,我想知道是否添加clientSocket.setSortimeout(90000)将修复此问题,还是应以其他方式处理 MainServerHandshakeThread public class MainServerHandshakeThread implements co

我们有下面的线程来执行SSLHandshake,但在一些边缘情况下,我注意到
((SSLSocket)clientSocket)
被永久阻止,它不会进入下一个
块,而
循环代码,其中SSL\u握手\u超时为
1500
毫秒,并且工作正常,我想知道是否添加
clientSocket.setSortimeout(90000)
将修复此问题,还是应以其他方式处理

MainServerHandshakeThread

public class MainServerHandshakeThread implements com.ssltunnel.utilities.threading.Shutdown, Runnable {
    private final Socket clientSocket;
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(MainServerHandshakeThread.class.getName());
    private boolean done;

    public MainServerHandshakeThread(Socket clientSocket) {
        this.clientSocket = clientSocket;        
    }

    private void handshake() throws CertificateExpiredException, InterruptedException, IOException {

        long start = System.currentTimeMillis();

        ((SSLSocket) clientSocket).setNeedClientAuth(true);
        MainServerHandshakeHandler handshake = new MainServerHandshakeHandler();
        ((SSLSocket) clientSocket).addHandshakeCompletedListener(handshake);
        ((SSLSocket) clientSocket).startHandshake();


        while (!handshake.isDone() && !done) {
            Thread.sleep(10);
            long duration = System.currentTimeMillis() - start;
            if (duration>SSL_HANDSHAKE_TIMEOUT) {
                done = true;
                LOG.warn("Handshake timeout");
            }
        }
        long stop = System.currentTimeMillis();        
        serialNumber = handshake.getSerialNumber();
        LOG.info("MainServer Handshake Handshake done in ms: " + ((stop - start))+" For serialNumber "+serialNumber );        

    }

    @Override
    public void run() {
        try {
            handshake();
        } catch (CertificateExpiredException ex) {
            LOG.error("Client Certificate Expired", ex.getMessage());
            SocketUtils.closeQuietly(clientSocket);
        }
        catch (InterruptedException ex) {
            LOG.error("Interrupted waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        } 
        catch (IOException ex) {
            LOG.error("IO Error waiting for handshake", ex);
            SocketUtils.closeQuietly(clientSocket);
        }
        finally {
            LOG.debug("Handshake thread is done");
            done = true;
        }

    }

    @Override
    public void shutdown() {
        if (clientSocket!=null) {
            SocketUtils.closeQuietly(clientSocket);
        }
    }
}
总结一下评论(主要来自@user207421):是的,如果握手过程在“一段时间”(但不一定是指定的超时1)后没有完成,那么通过设置套接字超时将足以触发
SocketTimeoutException
(IOException的子类)

这一点很简单,因为
setSoTimeout()
在SSL握手下面的套接字级别工作:执行的握手协议涉及从套接字
输入
/
输出流
进行多次读/写,这将触发超时本身。换句话说:它本身不是“握手超时”,而是握手本身执行的所有读取操作的“读取超时”

另外,请注意,您不需要自己调用
startHandshake()
:当您第一次尝试从
SSLSocket
读取或写入时,JVM将自动执行此操作(这通常是在您从
SSLServerSocket
获取此类套接字之后提前执行的操作)



1:setSoTimeout(timeout)指定的超时是针对单个用户的。因此,握手过程可以在执行的
read()
次数乘以指定的
timeout
值后超时(在最坏的情况下)。

大多数SSLSocket实现都支持setHandshakeTimeout(),但它不是类定义的一部分。 我建议你看看Android代码的例子。这是一个很好的例子,说明了如何应对: 及

检查他们如何尝试设置握手超时(如果存在):

    private void setHandshakeTimeout(SSLSocket sslSocket, int timeout) {

    try {

        // Most implementations of SSLSocket support setHandshakeTimeout(), but it is not

        // actually part of the class definition. We will attempt to set it using reflection.

        // If the particular implementation of SSLSocket we are using does not support this

        // function, then we will just have to use the default handshake timeout.

        sslSocket.getClass().getMethod("setHandshakeTimeout", int.class).invoke(sslSocket,

                timeout);

    } catch (Exception e) {

        LogUtils.w(LogUtils.TAG, e, "unable to set handshake timeout");

    }

}
看看他们是如何建立联系的:

    SSLSocket sslsock = (SSLSocket)

        ((sock != null) ? sock : createSocket());



    if ((localAddress != null) || (localPort > 0)) {



        // we need to bind explicitly

        if (localPort < 0)

            localPort = 0; // indicates "any"



        InetSocketAddress isa =

            new InetSocketAddress(localAddress, localPort);

        sslsock.bind(isa);

    }



    int connTimeout = HttpConnectionParams.getConnectionTimeout(params);

    int soTimeout = HttpConnectionParams.getSoTimeout(params);



    InetSocketAddress remoteAddress;

    if (nameResolver != null) {

        remoteAddress = new InetSocketAddress(nameResolver.resolve(host), port);

    } else {

        remoteAddress = new InetSocketAddress(host, port);

    }



    sslsock.connect(remoteAddress, connTimeout);



    sslsock.setSoTimeout(soTimeout);

    try {

        hostnameVerifier.verify(host, sslsock);

        // verifyHostName() didn't blowup - good!

    } catch (IOException iox) {

        // close the socket before re-throwing the exception

        try { sslsock.close(); } catch (Exception x) { /*ignore*/ }

        throw iox;

    }



    return sslsock;
SSLSocket SSLSocket=(SSLSocket)
((sock!=null)?sock:createSocket());
如果((localAddress!=null)| |(localPort>0)){
//我们需要明确地绑定
如果(本地端口<0)
localPort=0;//表示“任意”
InetSocketAddressISA=
新的InetSocketAddress(localAddress,localPort);
sslsock.bind(isa);
}
int conntTimeout=HttpConnectionParams.getConnectionTimeout(params);
int-soTimeout=HttpConnectionParams.getSoTimeout(params);
InetSocketAddress远程地址;
if(namesolver!=null){
remoteAddress=新的InetSocketAddress(namesolver.resolve(主机),端口);
}否则{
remoteAddress=新的InetSocketAddress(主机、端口);
}
连接(远程地址,连接超时);
sslsock.setSoTimeout(soTimeout);
试一试{
验证(主机,sslsock);
//verifyHostName()没有爆炸-很好!
}捕获(iox异常){
//在重新引发异常之前关闭套接字
尝试{sslsock.close();}捕获(异常x){/*忽略*/}
抛出iox;
}
返回sslsock;

希望有帮助。

根据:此方法对于连接上的初始握手是同步的,并在协商握手完成时返回。最好不要显式执行初始握手,让它自动完成,并在套接字上设置读取超时。注意“非阻塞超时”在术语上是矛盾的。@user207421对socket world来说是个新手,我真的不明白你说的“让它自动完成”是什么意思,如果你能将其发布为answer@RanPaul:他的意思是“根本不要调用
startHandshake()
”。当您第一次尝试读取或写入套接字时,JVM将自动调用它。再加上一个超时(正如你所建议的),应该没问题。顺便问一下,你有没有试过你所建议的?设置
setSoTimeout()
?感谢您的响应,因为我们在服务器端缓存套接字连接,所以我们不能延迟
握手
,而依赖JVM自动进行握手,我们必须执行握手manually@RanPaul没问题,这是一个简化泛型的工具:当你得到一个
套接字时,实际上,您可能会得到一个
SSLSocket
(它是一个子类),因此在读/写时不需要担心SSL的细节。手动调用
startHandshake()
没有什么错,只是不是强制性的。您仍然可以调用
setSoTimeout()
并使握手过程超时。