使用javax.net.ssl.SSLSocket时性能降低100倍

使用javax.net.ssl.SSLSocket时性能降低100倍,java,apache,sockets,ssl,encryption,Java,Apache,Sockets,Ssl,Encryption,在我的主要项目中,我在服务器和客户端之间传输大量二进制数据 服务器是带有ArchLinux的odroid xu4。 该客户端当前是MacbookPro。但是,在HP上使用Windows时也会出现此问题 我花了一些时间才发现javax.net.ssl加密在通过TCP java套接字发送数据时会导致性能大幅下降 所以,我创建了一个测试环境——由客户端、服务器和路由器组成——以及一个IntelliJ项目,用于测量性能统计数据 该测试仅测量将5兆字节的文件从客户端传输到服务器所需的时间。建立连接所需的时

在我的主要项目中,我在服务器和客户端之间传输大量二进制数据

服务器是带有ArchLinux的odroid xu4。 该客户端当前是MacbookPro。但是,在HP上使用Windows时也会出现此问题

我花了一些时间才发现javax.net.ssl加密在通过TCP java套接字发送数据时会导致性能大幅下降

所以,我创建了一个测试环境——由客户端、服务器和路由器组成——以及一个IntelliJ项目,用于测量性能统计数据

该测试仅测量将5兆字节的文件从客户端传输到服务器所需的时间。建立连接所需的时间(SSL握手)大约需要2秒,这仍然可以,也不是问题。但是,停止传输文件的时间会导致以下结果:

未加密的套接字:200ms

使用一个简单的SCP-命令:200ms

加密为TLSv1的套接字:22秒

下面我展示了用于测试的客户机和服务器类:server

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

public class Server {

    protected final static String keyPath = "/home/*****************/trust/keystore.keystore";

    public static void main(String[] args) throws Exception{
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        KeyStore keyStore = KeyStore.getInstance("JKS");

        keyStore.load(new FileInputStream(keyPath), "*****************".toCharArray());
        keyManagerFactory.init(keyStore, "*****************".toCharArray());

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(keyManagerFactory.getKeyManagers(), null, null);

        SSLServerSocketFactory factory = context.getServerSocketFactory();
        SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(00000);

        serverSocket.setEnabledProtocols(new String[] {"SSLv3", "TLSv1"});

        SSLSocket socket = (SSLSocket) serverSocket.accept();
        socket.setTcpNoDelay(true);
        socket.setReceiveBufferSize(1024*1024*5);
        socket.setSendBufferSize(1024*1024*5);

        socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
            @Override
            public void handshakeCompleted(HandshakeCompletedEvent handshakeCompletedEvent) {
                System.out.println("CipherSuite: " + handshakeCompletedEvent.getCipherSuite());
                System.out.println("CipherSuite: " + handshakeCompletedEvent.getSession().getCipherSuite());
                System.out.println("Protocol: " + handshakeCompletedEvent.getSession().getProtocol());
            }
        });

        int size = 16*1024;
        byte[] buf = new byte[size];
        InputStream in = socket.getInputStream();

        int count;
        long sum = 0;
        long startTime = System.currentTimeMillis();
        while ((count = in.read(buf)) > -1) {
            sum += count;
        }
        System.out.println();
        long stopTime = System.currentTimeMillis();

        long elapsedTime = stopTime - startTime;
        System.out.println("Size:\t" + size + "\tTime:\t" + elapsedTime + "\tRead:\t" + sum/1024/1024);
    }
}
客户端类:

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;

public class Client {

    public static void main(String[] args) throws Exception{
        System.setProperty("javax.net.ssl.trustStore", Client.class.getResource("truststore.truststore").getPath());
        System.setProperty("javax.net.ssl.trustStorePassword", "*****************");

        SSLSocketFactory factory =  (SSLSocketFactory) SSLSocketFactory.getDefault();

        InetAddress address = InetAddress.getByName("192.168.***.***");
        //Socket socket = new Socket(address, 55552);
        SSLSocket socket = (SSLSocket) factory.createSocket(address, 55552);
        socket.setTcpNoDelay(true);
        socket.setReceiveBufferSize(1024*1024*5);
        socket.setSendBufferSize(1024*1024*5);

        for(String chipher : socket.getEnabledCipherSuites()) System.out.println(chipher);

        String path = "/Users/harald/Desktop/SpeedTest/file5M.img";
        File file = new File(path);

        int size = 16*1024;
        byte[] buf = new byte[size];
        InputStream in = new FileInputStream(file);

        int count;
        OutputStream out = socket.getOutputStream();
        long startTime = System.currentTimeMillis();
        while ((count = in.read(buf)) > -1) {
            out.write(buf, 0, count);
        }
        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("Time:\t" + elapsedTime + "\tRead:\t");
        out.close();
    }
}
可见,我将SendBufferSize和ReceiveBufferSize设置为5MB。这给了我一个提示,发送和加密5MB文件所需的时间非常依赖于客户端和服务器的cpu。客户端需要大约9秒来加密数据并将其放入套接字。 客户端具有3,3 GHz Intel Core i7

odroid单板计算机的cpu当然较弱,因此需要22秒来接收和加密文件。Linux命令Top告诉我,在传输文件时,一个内核总是100%的工作负载


结论是javax.net.ssl库中可能存在一些糟糕的实现问题,或者我的代码中忘记了一些东西,这导致了这个问题

第一种解决方案是使用org.apache.conn.ssl而不是javax.net。 然而,我目前认为SSLSockets只有一个客户端实现。如果有机会创建SSLServerSocket,我就不需要再使用javax了。但是,性能没有通过apaches sslsockets进行测试

第二个解决方案可能是使用另一个密码套件,它具有较少的cpu工作负载。但我认为这还不够

我真的希望有人能帮我解决这个问题,或者知道如何编写bug报告,如果这是一个实现问题的话


我很期待Harald。

诊断问题可能很困难,但我会首先避免弄乱接收和发送缓冲区,并且不更改tcpNoDelay设置。正常的缓冲区大小和nagle设置应该可以很好地用于发送文件。我的猜测是,选择的密码套件对于服务器来说特别差。您可能会尝试类似POLY1305-CHACHA的密码套件,它可能具有更高的性能。您知道如何设置此类密码套件吗?实际上,Oracle Java可能还不支持这些密码套件。您可以尝试使用RC4密码套件,但RC4被认为不够安全,已被弃用。至少你可以用RC4来衡量性能,看看它是否更好。密码套件在列表中列出。编辑实际上,在bctls Provider中,CHA-CHA密码套件显然是可用的。您是否设法增加了缓冲区大小?是的,但问题更深层。这一切都依赖于javas加密的底层实现和硬件加速的缺失。我用C语言重新编程,使用OpenSSL,我达到了与SCP相同的速度。