当必须接受许多连接时,我在客户端/服务器类型应用程序中使用 Java 套接字时遇到问题

发布于 2024-10-31 23:04:23 字数 6803 浏览 6 评论 0原文

首先,感谢您的阅读。这是我第一次以用户身份使用 stackoverflow,尽管我一直在阅读它并找到有用的解决方案:D。顺便说一句,抱歉,如果我解释得不够清楚,我知道我的英语不是很好。

我的基于套接字的程序有一个奇怪的行为,并且存在一些性能问题。客户端和服务器通过以多线程方式将序列化对象读/写到对象输入和输出流中来相互通信。让我向您展示代码基础知识。我已将其简化为更具可读性,并且故意省略了完整的异常处理。服务器的工作方式如下:

服务器:

// (...)

public void serve() {
    if (serverSocket == null) {
        try {
            serverSocket = (SSLServerSocket) SSLServerSocketFactory
                                    .getDefault().createServerSocket(port);
            serving = true;
            System.out.println("Waiting for clients...");
            while (serving) {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("Client accepted.");
                //LjServerThread class is below
                new LjServerThread(clientSocket).start();
            }
        } catch (Exception e) {
            // Exception handling code (...)
        }
    }
}

public void stop() {
    serving = false;
    serverSocket = null;
}

public boolean isServing() {
    return serving;
}

LjServerThread 类,每个客户端创建一个实例:

private SSLSocket clientSocket;
private String IP;
private long startTime;

public LjServerThread(SSLSocket clientSocket) {
        this.clientSocket = clientSocket;
        startTime = System.currentTimeMillis();
        this.IP = clientSocket.getInetAddress().getHostAddress();
}

public synchronized String getClientAddress() {
    return IP;
}

@Override
public void run() {
    ObjectInputStream in = null;
    ObjectOutputStream out = null;
    //This is my protocol handling object, and as you will see below,
    //it works processing the object received and returning another as response.
    LjProtocol protocol = new LjProtocol();
    try {
        try {
            in = new ObjectInputStream(new BufferedInputStream(
                                     clientSocket.getInputStream()));
            out = new ObjectOutputStream(new BufferedOutputStream(
                                    clientSocket.getOutputStream()));
            out.flush();
        } catch (Exception ex) {
            // Exception handling code (...)
        }
        LjPacket output;
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            // When the object received is the finish mark, 
            // protocol.processMessage()object returns null.
            if (output == null) {
                break;
            }
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        System.out.println("Client " + IP + " finished successfully.");
    } catch (Exception ex) {
        // Exception handling code (...)
    } finally {
        try {
            out.close();
            in.close();
            clientSocket.close();
        } catch (Exception ex) {
            // Exception handling code (...)
        } finally {
            long stopTime = System.currentTimeMillis();
            long runTime = stopTime - startTime;
            System.out.println("Run time: " + runTime);
        }
    }
}

并且客户端是这样的:

    private SSLSocket socket;

    @Override
    public void run() {
        LjProtocol protocol = new LjProtocol();
        try {
            socket = (SSLSocket) SSLSocketFactory.getDefault()
                     .createSocket(InetAddress.getByName("here-goes-hostIP"),
                                                                       4444);
        } catch (Exception ex) {

        }
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
        out = new ObjectOutputStream(new BufferedOutputStream(
                                         socket.getOutputStream()));
        out.flush();
        in = new ObjectInputStream(new BufferedInputStream(
                                          socket.getInputStream()));
        LjPacket output;
        // As the client is which starts the connection, it sends the first 
        //object.
        out.writeObject(/* First object */);
        out.flush();
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        } catch (EOFException ex) {
            // If all goes OK, when server disconnects EOF should happen.
            System.out.println("suceed!");
        } catch (Exception ex) {
            // (...)
        } finally {
            try {
                // FIRST STRANGE BEHAVIOUR:
                // I have to comment the "out.close()" line, else, Exception is
                // thrown ALWAYS.
                out.close();
                in.close();
                socket.close();
            } catch (Exception ex) {
                System.out.println("This shouldn't happen!");
            }
        }
    }
}

好吧,如您所见,在服务器端处理接受的客户端的 LjServerThread 类会测量所需的时间。 .. 通常,需要 75 - 120 毫秒(其中 x 是 IP):

  • 客户端 x 成功完成。
  • 运行时间:82
  • 客户端 x 成功完成。
  • 运行时间:80
  • 客户端 x 成功完成。
  • 运行时间:112
  • 客户端 x 成功完成。
  • 运行时间:88
  • 客户端 x 成功完成。
  • 运行时间:90
  • 客户端 x 成功完成。
  • 运行时间:84

但突然之间,并且没有可预测的模式(至少对我来说):

  • 客户端 x 成功完成。
  • 运行时间:15426

有时达到25秒! 有时,一小群线程会变慢一点,但这并不让我担心:

  • Client x 成功完成。
  • 运行时间:239
  • 客户端 x 成功完成。
  • 运行时间:243

为什么会发生这种情况?这可能是因为我的服务器和客户端在同一台机器上,具有相同的 IP? (为了进行此测试,我在同一台计算机上执行服务器和客户端,但它们通过互联网使用我的公共 IP 连接)。

这就是我测试这个的方法,我在 main() 中向服务器发出这样的请求:

    for (int i = 0; i < 400; i++) {
        try {
            new LjClientThread().start();
            Thread.sleep(100);
        } catch (Exception ex) {
            // (...)
        }
    }

如果我在没有“Thread.sleep(100)”的情况下循环执行它,我会得到一些连接重置异常(7或8个连接重置为400,或多或少),但我想我明白为什么会发生:当 serverSocket.accept() 接受连接时,需要花费很少的时间才能再次到达 serverSocket.accept() 。在此期间,服务器无法接受连接。会不会是因为这个?如果没有,为什么? 400 个连接同时到达我的服务器的情况很少见,但这种情况有可能发生。如果没有“Thread.sleep(100)”,计时问题也会更糟。

提前致谢!


更新:

多么愚蠢,我在本地主机中测试了它......并且它没有给出任何问题!有或没有“Thread.sleep(100)”,都没关系,它工作得很好!为什么!因此,正如我所看到的,我关于为什么抛出连接重置的理论是不正确的。这让事情变得更加奇怪了!我希望有人能帮助我...再次感谢! :)


更新 (2):

我发现不同操作系统中的行为明显不同。我通常在 Linux 中进行开发,我解释的行为是关于我的 Ubuntu 10.10 中发生的情况。在 Windows 7 中,当我在连接之间暂停 100 毫秒时,一切都很好,并且所有线程都很快,没有人花费超过 150 毫秒左右的时间(没有连接速度慢的问题!)。这不是 Linux 中发生的情况。但是,当我删除“Thread.sleep(100)”时,所有连接都失败并抛出异常,而不是仅某些连接获得连接重置异常(在 Linux 中,只有其中一些连接,400 个连接中只有 6 个左右)都失败了)。

唷!我刚刚发现不仅操作系统,JVM 环境也有一点影响!没什么大不了的,但值得注意。我之前在 Linux 中使用 OpenJDK,现在使用 Oracle JDK,我发现当我减少连接之间的睡眠时间时,它会更早开始失败(50 毫秒 OpenJDK 工作正常,没有抛出异常,但使用 Oracle 的 JDK 时,它会更早地失败)很多睡眠时间为 50 毫秒,而 100 毫秒则效果很好)。

First of all, thanks for reading. This is my first time in stackoverflow as user, although I've always read it and found useful solutions :D. By the way, sorry if I'm not clear enough explaining myself, I know that my English isn't very good.

My socket based program is having a strange behaviour, and some performance issues. The client and server communicate with each other by reading/writing serialized objects into object input and output streams, in a multi-threaded way. Let me show you the code basics. I have simplified it to be more readable and a complete exception handling for example is intentionally ommited. The server works like this:

Server:

// (...)

public void serve() {
    if (serverSocket == null) {
        try {
            serverSocket = (SSLServerSocket) SSLServerSocketFactory
                                    .getDefault().createServerSocket(port);
            serving = true;
            System.out.println("Waiting for clients...");
            while (serving) {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("Client accepted.");
                //LjServerThread class is below
                new LjServerThread(clientSocket).start();
            }
        } catch (Exception e) {
            // Exception handling code (...)
        }
    }
}

public void stop() {
    serving = false;
    serverSocket = null;
}

public boolean isServing() {
    return serving;
}

LjServerThread class, one instance created per client:

private SSLSocket clientSocket;
private String IP;
private long startTime;

public LjServerThread(SSLSocket clientSocket) {
        this.clientSocket = clientSocket;
        startTime = System.currentTimeMillis();
        this.IP = clientSocket.getInetAddress().getHostAddress();
}

public synchronized String getClientAddress() {
    return IP;
}

@Override
public void run() {
    ObjectInputStream in = null;
    ObjectOutputStream out = null;
    //This is my protocol handling object, and as you will see below,
    //it works processing the object received and returning another as response.
    LjProtocol protocol = new LjProtocol();
    try {
        try {
            in = new ObjectInputStream(new BufferedInputStream(
                                     clientSocket.getInputStream()));
            out = new ObjectOutputStream(new BufferedOutputStream(
                                    clientSocket.getOutputStream()));
            out.flush();
        } catch (Exception ex) {
            // Exception handling code (...)
        }
        LjPacket output;
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            // When the object received is the finish mark, 
            // protocol.processMessage()object returns null.
            if (output == null) {
                break;
            }
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        System.out.println("Client " + IP + " finished successfully.");
    } catch (Exception ex) {
        // Exception handling code (...)
    } finally {
        try {
            out.close();
            in.close();
            clientSocket.close();
        } catch (Exception ex) {
            // Exception handling code (...)
        } finally {
            long stopTime = System.currentTimeMillis();
            long runTime = stopTime - startTime;
            System.out.println("Run time: " + runTime);
        }
    }
}

And, the client, is like this:

    private SSLSocket socket;

    @Override
    public void run() {
        LjProtocol protocol = new LjProtocol();
        try {
            socket = (SSLSocket) SSLSocketFactory.getDefault()
                     .createSocket(InetAddress.getByName("here-goes-hostIP"),
                                                                       4444);
        } catch (Exception ex) {

        }
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
        out = new ObjectOutputStream(new BufferedOutputStream(
                                         socket.getOutputStream()));
        out.flush();
        in = new ObjectInputStream(new BufferedInputStream(
                                          socket.getInputStream()));
        LjPacket output;
        // As the client is which starts the connection, it sends the first 
        //object.
        out.writeObject(/* First object */);
        out.flush();
        while (true) {
            output = protocol.processMessage((LjPacket) in.readObject());
            out.writeObject(output);
            out.flush();
            out.reset();
        }
        } catch (EOFException ex) {
            // If all goes OK, when server disconnects EOF should happen.
            System.out.println("suceed!");
        } catch (Exception ex) {
            // (...)
        } finally {
            try {
                // FIRST STRANGE BEHAVIOUR:
                // I have to comment the "out.close()" line, else, Exception is
                // thrown ALWAYS.
                out.close();
                in.close();
                socket.close();
            } catch (Exception ex) {
                System.out.println("This shouldn't happen!");
            }
        }
    }
}

Well, as you see, the LjServerThread class which handles accepted clients in the server side, measures the time it takes... Normally, it takes between 75 - 120 ms (where the x is the IP):

  • Client x finished successfully.
  • Run time: 82
  • Client x finished successfully.
  • Run time: 80
  • Client x finished successfully.
  • Run time: 112
  • Client x finished successfully.
  • Run time: 88
  • Client x finished successfully.
  • Run time: 90
  • Client x finished successfully.
  • Run time: 84

But suddenly, and with no predictable pattern (at least for me):

  • Client x finished successfully.
  • Run time: 15426

Sometimes reaches 25 seconds!
Ocasionally a small group of threads go a little slower but that doesn't worry me much:

  • Client x finished successfully.
  • Run time: 239
  • Client x finished successfully.
  • Run time: 243

Why is this happening? Is this perhaps because my server and my client are in the same machine, with the same IP? (To do this tests I execute the server and the client in the same machine, but they connect over internet, with my public IP).

This is how I test this, I make requests to the server like this in main():

    for (int i = 0; i < 400; i++) {
        try {
            new LjClientThread().start();
            Thread.sleep(100);
        } catch (Exception ex) {
            // (...)
        }
    }

If I do it in loop without "Thread.sleep(100)", I get some connection reset exceptions (7 or 8 connections resetted out of 400, more or less), but I think I understand why it happens: when serverSocket.accept() accepts a connection, a very small amount of time has to be spent to reach serverSocket.accept() again. During that time, the server cannot accept connections. Could it be because of that? If not, why? It would be rare 400 connections arriving to my server exactly at the same time, but it could happen. Without "Thread.sleep(100)", the timing issues are worse also.

Thanks in advance!


UPDATED:

How stupid, I tested it in localhost... and it doesn't give any problem! With and without "Thread.sleep(100)", doesn't matter, it works fine! Why! So, as I can see, my theory about why the connection reset is beeing thrown is not correct. This makes things even more strange! I hope somebody could help me... Thanks again! :)


UPDATED (2):

I have found sightly different behaviours in different operating systems. I usually develop in Linux, and the behaviour I explained was about what was happening in my Ubuntu 10.10. In Windows 7, when I pause 100ms between connections, all its fine, and all threads are lighting fast, no one takes more than 150ms or so (no slow connection issues!). This is not what is happening in Linux. However, when I remove the "Thread.sleep(100)", instead of only some of the connections getting the connection reset exception, all of them fail and throw the exception (in Linux only some of them, 6 or so out of 400 were failing).

Phew! I've just find out that not only the OS, the JVM enviroment has a little impact also! Not a big deal, but noteworthy. I was using OpenJDK in Linux, and now, with the Oracle JDK, I see that as I reduce the sleep time between connections, it starts failing earlier (with 50 ms OpenJDK works fine, no exceptions are thrown, but with Oracle's one quite a lot with 50ms sleep time, while with 100ms works fine).

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

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

发布评论

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

评论(2

烟花肆意 2024-11-07 23:04:24

有两点我觉得你可以进一步考虑研究一下。抱歉这里有点含糊,但这就是我的想法。

1) 在底层,在 TCP 级别,很少有与平台相关的东西控制通过套接字发送/接收数据所需的时间。不一致的延迟可能是由于 tcp_syn_retries 等设置造成的。您可能有兴趣查看这里 http://www.frozentux.net /ipsysctl-tutorial/chunkyhtml/tcpvariables.html#AEN370

2)您计算的执行时间不仅是完成执行所花费的时间,还包括完成最终化之前的时间,这不能保证当对象准备好最终化时立即发生。

Two points I think you may further consider researching. Sorry for a bit vague here but this is what I think.

1) Under-the-hood, at tcp level there are few platform dependent things control the amount of time it takes to send/receive data across a socket. The inconsistent delay could be because of the settings such as tcp_syn_retries. You may be interested to look at here http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html#AEN370

2)Your calculated execution time is not only the amount of time it took to complete the execution but includes the time until the finalization is done which is not guaranteed to happen immediately when an object is ready for finalization.

若有似无的小暗淡 2024-11-07 23:04:23

服务器套接字有一个队列,用于保存传入的连接尝试。如果该队列已满,客户端将遇到连接重置错误。如果没有 Thread.sleep(100) 语句,所有客户端都会尝试相对同时进行连接,这会导致其中一些客户端遇到连接重置错误。

The server socket has a queue that holds incoming connection attempts. A client will encounter a connection reset error if that queue is full. Without the Thread.sleep(100) statement, all of your clients are trying to connect relatively simultaneously, which results in some of them encountering the connection reset error.

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