Java TCP套接字:数据传输速度很慢

我设置了一个带有ServerSocket的服务器,用客户机连接到它。 它们通过交换机直接联网,ping时间<1ms。

现在,我尝试通过套接字的输出流将“大量”数据从客户端推送到服务器。 转移0.6Gb需要23分钟。 我可以通过scp在几秒钟内推送一个更大的文件。

知道我可能做错了什么吗? 我基本上只是循环并在套接字上调用writeInt。 速度问题与数据的来源无关,即使我只是发送一个恒定的整数而不是从磁盘读取。

我尝试将两侧的发送和接收缓冲区设置为4Mb,没有骰子。 我为读写器使用缓冲流,没有骰子。

我错过了什么吗?

编辑:代码

这是我制作套接字的地方

System.out.println("Connecting to " + hostname); serverAddr = InetAddress.getByName(hostname); // connect and wait for port assignment Socket initialSock = new Socket(); initialSock.connect(new InetSocketAddress(serverAddr, LDAMaster.LDA_MASTER_PORT)); int newPort = LDAHelper.readConnectionForwardPacket(new DataInputStream(initialSock.getInputStream())); initialSock.close(); initialSock = null; System.out.println("Forwarded to " + newPort); // got my new port, connect to it sock = new Socket(); sock.setReceiveBufferSize(RECEIVE_BUFFER_SIZE); sock.setSendBufferSize(SEND_BUFFER_SIZE); sock.connect(new InetSocketAddress(serverAddr, newPort)); System.out.println("Connected to " + hostname + ":" + newPort + " with buffers snd=" + sock.getSendBufferSize() + " rcv=" + sock.getReceiveBufferSize()); // get the MD5s try { byte[] dataMd5 = LDAHelper.md5File(dataFile), indexMd5 = LDAHelper.md5File(indexFile); long freeSpace = 90210; // ** TODO: actually set this ** output = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); input = new DataInputStream(new BufferedInputStream(sock.getInputStream())); 

这是我在服务器端连接的地方:

  ServerSocket servSock = new ServerSocket(); servSock.setSoTimeout(SO_TIMEOUT); servSock.setReuseAddress(true); servSock.bind(new InetSocketAddress(LDA_MASTER_PORT)); int currPort = LDA_START_PORT; while (true) { try { Socket conn = servSock.accept(); System.out.println("Got a connection. Sending them to port " + currPort); clients.add(new MasterClientCommunicator(this, currPort)); clients.get(clients.size()-1).start(); Thread.sleep(500); LDAHelper.sendConnectionForwardPacket(new DataOutputStream(conn.getOutputStream()), currPort); currPort++; } catch (SocketTimeoutException e) { System.out.println("Done listening. Dispatching instructions."); break; } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } 

好的,这是我运送超过0.6Gb数据的地方。

 public static void sendTermDeltaPacket(DataOutputStream out, TIntIntHashMap[] termDelta) throws IOException { long bytesTransferred = 0, numZeros = 0; long start = System.currentTimeMillis(); out.write(PACKET_TERM_DELTA); // header out.flush(); for (int z=0; z < termDelta.length; z++) { out.writeInt(termDelta[z].size()); // # of elements for each term bytesTransferred += 4; } for (int z=0; z < termDelta.length; z++) { for (int i=0; i < termDelta[z].size(); i++) { out.writeInt(1); out.writeInt(1); } } 

到目前为止看起来非常简单……

传输大量数据时, 希望写入单个字节。

 import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Transfer { public static void main(String[] args) { final String largeFile = "/home/dr/test.dat"; // REPLACE final int BUFFER_SIZE = 65536; new Thread(new Runnable() { public void run() { try { ServerSocket serverSocket = new ServerSocket(12345); Socket clientSocket = serverSocket.accept(); long startTime = System.currentTimeMillis(); byte[] buffer = new byte[BUFFER_SIZE]; int read; int totalRead = 0; InputStream clientInputStream = clientSocket.getInputStream(); while ((read = clientInputStream.read(buffer)) != -1) { totalRead += read; } long endTime = System.currentTimeMillis(); System.out.println(totalRead + " bytes read in " + (endTime - startTime) + " ms."); } catch (IOException e) { } } }).start(); new Thread(new Runnable() { public void run() { try { Thread.sleep(1000); Socket socket = new Socket("localhost", 12345); FileInputStream fileInputStream = new FileInputStream(largeFile); OutputStream socketOutputStream = socket.getOutputStream(); long startTime = System.currentTimeMillis(); byte[] buffer = new byte[BUFFER_SIZE]; int read; int readTotal = 0; while ((read = fileInputStream.read(buffer)) != -1) { socketOutputStream.write(buffer, 0, read); readTotal += read; } socketOutputStream.close(); fileInputStream.close(); socket.close(); long endTime = System.currentTimeMillis(); System.out.println(readTotal + " bytes written in " + (endTime - startTime) + " ms."); } catch (Exception e) { } } }).start(); } } 

这会在我的机器上短时间内复制1 GiB数据。 这里的关键是使用InputStream.read和OutputStream.write方法接受字节数组作为参数。 缓冲区的大小并不重要,它应该比比如说大一点.5。尝试上面的BUFFER_SIZE,看看它如何影响速度,但也要记住,对于你运行的每台机器,它可能都不同这个节目。 64 KiB似乎是一个很好的妥协。

嘿,我想我会跟进任何有兴趣的人。

这是故事的奇异道德:

永远不要使用DataInputStream / DataOutputStream和套接字!!

如果我将套接字包装在BufferedOutputStream / BufferedInputStream中,那么生活就很棒。 写它原始就好了。

但是将套接字包装在DataInputStream / DataOutputStream中,或者甚至让DataOutputStream(BufferedOutputStream(sock.getOutputStream()))非常慢。

对此的解释对我来说真的很有趣。 但在将所有内容交换进去之后,这就是最新情况。 如果你不相信我自己尝试一下。

不过,感谢您的所有快速帮助。

也许你应该尝试以块(帧)发送ur数据,而不是单独写每个字节。 并将帧与TCP数据包大小对齐以获得最佳性能。

你可以尝试通过环回来做这个,它应该在第二个传输数据。

如果需要几分钟,那么您的应用程序就会出现问题。 如果只是通过互联网发送数据很慢,那么网络链接可能很慢。

我猜你的客户端和服务器之间有一个10 Mb / s的网络,这就是你的传输速度很慢的原因。 如果是这种情况,请尝试使用DeflatoutOutputStream和InflatorInputStream进行连接。

你是如何实现接收端的? 请发布您的接收代码。

由于TCP是一种可靠的协议,因此需要采取措施确保客户端能够接收发送方发送的所有数据。 这意味着如果您的客户端无法及时从数据接收缓冲区中获取数据,则发送方将停止发送更多数据,直到客户端有机会读取接收缓冲区中的所有字节为止。

如果您的接收方一次只读取一个字节的数据,那么您的发送方可能会花费大量时间等待接收缓冲区清除,因此传输时间较长。 我建议在每次读取操作中将接收代码更改为尽可能多的字节读取 。 看看这是否能解决您的问题。

由于我还不能评论这个网站,我必须在这里写@Erik的答案。

问题是DataOutputStream没有缓冲。 Java中的整个Stream-thing基于装饰器设计模式。 所以你可以写

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

它将原始流包装在更有效的BufferedOutputStream中,然后将其包装到DataOutputStream中,DataOutputStream提供了诸如writeInt(),writeLong()等其他更好的function。

@Erik:使用DataXxxputStream不是问题所在。 问题是你是以太小的块发送数据。 使用缓冲区解决了您的问题,因为即使您一点一点地写缓冲区也可以解决问题。 Bombe的解决方案更好,更通用,更快捷。

你应该下载一个好的数据包嗅探器。 我个人是WireShark的忠实粉丝,每次进行套接字编程时我都会使用它。 请记住,您必须让客户端和服务器在不同的系统上运行才能获取任何数据包。

要尝试的事情:

  • 数据发送时CPU是否为100%? 如果是这样,请使用visualvm并执行CPU分析以查看花费的时间
  • 使用java.nio中的SocketChannel – 这些通常更快,因为它们可以更容易地使用本机IO – 当然,这只有在操作受CPU限制时才有用
  • 如果它不受CPU限制,则网络级别出现问题。 使用数据包嗅探器来分析它。

我正在使用PrintWriter发送数据。 我删除了它并使用BufferedOutputStream.send(String.getBytes())发送数据,并且发送速度提高了大约10倍。

你的堆大小是如何设置的? 我最近遇到了类似的问题,大量数据的套接字传输,只是通过查看JConsole我意识到应用程序JConsole部分时间来做完整的GC。

试试-Xmx1g

使用字节缓冲区发送数据