在我的主项目中,我在服务器和客户端之间传输大量二进制数据。
服务器是带有ArchLinux的odroid xu4。 客户端目前是MacbookPro。但是,在HP上使用Windows时也会出现此问题。
我花了一些时间才发现 javax.net.ssl加密导致通过TCP java套接字发送数据时性能大幅下降。
因此,我创建了一个测试环境 - 包括客户端,服务器和路由器 - 以及IntelliJ中用于测量性能统计数据的项目。
该测试仅测量将 5兆字节文件从客户端传输到服务器所需的时间。建立连接所需的时间 - SSL握手 - 大约需要2秒,这仍然没问题,也不是问题。但是,停止传输文件的时间会导致以下结果:
没有加密的套接字: 200ms 。
使用简单的 SCP - 命令: 200ms 。
使用 TLSv1 加密的套接字: 22秒。
在下面我展示了我用于测试的客户端和服务器类:服务器
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。但是,使用apache sslsockets并没有表现出来。
第二个解决方案可能是使用另一个密码套件,它具有较少的CPU工作负载。但我认为这还不够。
我真的希望有人可以帮我解决这个问题,或者知道如何编写错误报告,如果这是一个实现问题。
我期待着Harald。