Java TCP套接字:数据传输缓慢

发布于 2024-07-29 19:29:00 字数 3370 浏览 6 评论 0原文

我使用 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);
        }
    }

到目前为止似乎非常简单......

I set up a server with a ServerSocket, connect to it with a client machine. They're directly networked through a switch and the ping time is <1ms.

Now, I try to push a "lot" of data from the client to the server through the socket's output stream. It takes 23 minutes to transfer 0.6Gb. I can push a much larger file in seconds via scp.

Any idea what I might be doing wrong? I'm basically just looping and calling writeInt on the socket. The speed issue doesn't matter where the data is coming from, even if I'm just sending a constant integer and not reading from disk.

I tried setting the send and receive buffer on both sides to 4Mb, no dice. I use a buffered stream for the reader and writer, no dice.

Am I missing something?

EDIT: code

Here's where I make the socket

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()));

Here's where I do the server-side connection:

    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();
        }
    }

Alright, here's where I'm shipping over ~0.6Gb of data.

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);
        }
    }

It seems pretty straightforward so far...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

︶ ̄淡然 2024-08-05 19:29:00

当您传输大量数据时,您不想写入单个字节。

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();
    }
}

这在我的机器上在短短 19 秒内复制了 1 GiB 的数据。 这里的关键是使用 InputStream.read

You do not want to write single bytes when you are transferring large amounts of data.

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();
    }
}

This copies 1 GiB of data in short over 19 seconds on my machine. The key here is using the InputStream.read and OutputStream.write methods that accept a byte array as parameter. The size of the buffer is not really important, it just should be a bit larger than, say, 5. Experiment with BUFFER_SIZE above to see how it effects the speed but also keep in mind that it probably is different for every machine you are running this program on. 64 KiB seem to be a good compromise.

一直在等你来 2024-08-05 19:29:00

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

这是这个故事的奇怪寓意:

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

如果我将套接字包装在 BufferedOutputStream/BufferedInputStream 中,生活就会很棒。 原始写入就可以了。

但是将套接字包装在 DataInputStream/DataOutputStream 中,甚至使用 DataOutputStream(BufferedOutputStream(sock.getOutputStream())) 速度非常慢。

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

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

Hey, I figured I'd follow up for anyone that was interested.

Here's the bizarre moral of the story:

NEVER USE DataInputStream/DataOutputStream and sockets!!

If I wrap the socket in a BufferedOutputStream/BufferedInputStream, life is great. Writing to it raw is just fine.

But wrap the socket in a DataInputStream/DataOutputStream, or even have DataOutputStream(BufferedOutputStream(sock.getOutputStream())) is EXTREMELY SLOW.

An explanation for that would be really interesting to me. But after swapping everything in and out, this is what's up. Try it yourself if you don't believe me.

Thanks for all the quick help, though.

哭了丶谁疼 2024-08-05 19:29:00

也许您应该尝试以块(帧)的形式发送数据,而不是单独写入每个字节。 并使帧与 TCP 数据包大小保持一致,以获得最佳性能。

Maybe you should try sending ur data in chunks(frames) instead of writing each byte seperately. And align your frames with the TCP packet size for best performance.

淡写薰衣草的香 2024-08-05 19:29:00

您可以尝试通过环回执行此操作吗,然后它应该立即传输数据。

如果需要几分钟,则说明您的应用程序存在问题。 如果只是通过互联网发送数据很慢,则可能是您的网络链接速度很慢。

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

Can you try doing this over loopback, it should then transfer the data in second.

If it takes minutes, there is something wrong with your application. If is only slow sending data over the internet it could be you network link which is slow.

My guess is that you have a 10 Mb/s network between your client and your server and this is why your transfer is going slowly. If this is the case, try using a DeflatoutOutputStream and an InflatorInputStream for your connection.

梦途 2024-08-05 19:29:00

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

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

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

How are you implementing the receiving end? Please post your receiving code as well.

Since TCP is a reliable protocol, it will take steps to make sure the client is able to receive all of the data sent by the sender. This means that if your client cannot get the data out of the data receive buffer in time, then the sending side will simply stop sending more data until the client has a chance to read all the bytes in the receiving buffer.

If your receiving side is reading data one byte at a time, then your sender probably will spend a lot of time waiting for the receiving buffer to clear, hence the long transfer times. I'll suggest changing your receiving code to reading as many bytes as possible in each read operation . See if that will solve your problem.

与风相奔跑 2024-08-05 19:29:00

由于我还无法对此网站发表评论,因此我必须在这里写下对@Erik 的回答。

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

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

它将把原始流包装在一个更高效的 BufferedOutputStream 中,然后将其包装到一个 DataOutputStream 中提供了额外的好功能,如 writeInt()、writeLong() 等。

Since I cannot yet comment on this site, I must write answer to @Erik here.

The problem is that DataOutputStream doesn't buffer. The whole Stream-thing in Java is based on decorators design pattern. So you could write

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

It will wrap the original stream in a BufferedOutputStream which is more efficient, which is then wrapped into a DataOutputStream which offers additional nice features like writeInt(), writeLong() and so on.

上课铃就是安魂曲 2024-08-05 19:29:00

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

@Erik: using DataXxxputStream is not the problem here. Problem is you were sending data in too small chunks. Using a buffer solved your problem because even you would write bit by bit the buffer would solve the problem.
Bombe's solution is much nicer, generic and faster.

时光无声 2024-08-05 19:29:00

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

I was using PrintWriter to send data. I removed that and sent data with BufferedOutputStream.send(String.getBytes()) and got about 10x faster sending.

硬不硬你别怂 2024-08-05 19:29:00

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

You should download a good packet sniffer. I'm a huge fan of WireShark personally and I end up using it every time I do some socket programming. Just keep in mind you've got to have the client and server running on different systems in order to pick up any packets.

孤云独去闲 2024-08-05 19:29:00

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

尝试-Xmx1g

How is your heap size set? I had a similar problem recently with the socket transfer of large amounts of data and just by looking at JConsole I realized that the application was spending most of its time doing full GCs.

Try -Xmx1g

北座城市 2024-08-05 19:29:00

要尝试的事情:

  • 发送数据时 CPU 是否处于 100%? 如果是这样,请使用 VisualVM 并进行 CPU 分析以查看时间花在哪里
  • 使用 java.nio 中的 SocketChannel - 这些通常更快,因为它们可以更轻松地使用本机 IO - 当然这仅在您的操作受 CPU 限制时才有帮助
  • 如果它不受 CPU 限制,而是网络级别出了问题。 使用数据包嗅探器来分析这一点。

Things to try:

  • Is the CPU at 100% while the data is being sent? If so, use visualvm and do a CPU profiling to see where the time is spent
  • Use a SocketChannel from java.nio - these are generally faster since they can use native IO more easily - of course this only helps if your operation is CPU bound
  • If it's not CPU bound, there's something going wrong at the network level. Use a packet sniffer to analyze this.
极度宠爱 2024-08-05 19:29:00

使用字节缓冲区发送数据

USe Byte buffer for sending the data

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文