【问题标题】:BufferedReader.read() eating 100% of CPUBufferedReader.read() 占用 100% 的 CPU
【发布时间】:2011-11-09 00:44:31
【问题描述】:

我有一个 JAVA 游戏服务器,每个 TCP 连接使用 1 个线程。 (我知道这很糟糕,但我现在必须保持这种状态)。在(3.2Ghz 6cor x2 机器,24GB RAM,windows server 2003 64bits)上,这是一段代码:

public void run()
{
    try
    {   
        String packet = "";
        char charCur[] = new char[1];

        while(_in.read(charCur, 0, 1)!=-1 && Server.isRunning)
        {
            if (charCur[0] != '\u0000' && charCur[0] != '\n' && charCur[0] != '\r')
            {
                packet += charCur[0];
            }else if(!packet.isEmpty())
            {
                parsePlayerPacket(packet);
                packet = "";
            }
        }

    }catch(Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
        try{
            kickPlayer();
        }catch(Exception e){e.printStackTrace();};

        Server.removeIp(_ip);
    }
}

大约 12 小时或更长时间的服务器正常运行(以及大约 3.000 名玩家连接)后,服务器开始永远占用所有 12 个 CPU 的 100%,直到我手动重新启动 JAVA 应用程序。所以游戏开始非常糟糕,我的玩家开始抱怨。

我已经尝试分析应用程序,这是我想出的:

所以我猜测问题来自这里:

while(_in.read(charCur, 0, 1)!=-1 && Server.isRunning)

知道变量“_in”是套接字输入的读取器:(_in = new BufferedReader(new InputStreamReader(_socket.getInputStream())))。

到底为什么 _in.read() 在服务器运行时间很长之后会占用这么多 CPU?

我试过放一个 Thread.sleep(1);还有更多在 While 循环中,但不执行任何操作,我猜问题出在 BufferedReader.read() 方法内部。

有没有人知道是什么原因造成的?以及如何解决?

【问题讨论】:

  • 令我感到惊讶的是,事实并非如此,而是您在循环中使用字符串连接。 为什么你一次只能读一个字符?
  • 数据包是非常小的字符串,例如:“AB123”。所以没关系。
  • 直到有人向您发起 DDOS 攻击,您最终会收到一个巨大的字符串。读取多个字符和 使用 StringBuilder 非常容易......为什么不这样做呢?
  • 每个连接一个线程是一种非常糟糕的做法。每个核心一个线程是最佳的。也许最多 40-50,但 3k 简直是疯了。
  • 我遇到过很多 DoS 攻击,我这里展示的代码只是一个干净的版本。我在该循环中实施了“最多 32 个字符的规则”。但是我仍然不知道如何修复这个字符串连接,我需要一些代码来知道你在说什么

标签: java multithreading sockets tcp profiling


【解决方案1】:

我也遇到了同样的问题,我也尝试了很多解决方案,但 read(byte) 没有运气。但是当我尝试使用 readLine(), 时效果很好。 @Reacen 你找到其他答案了吗,请告诉我。

            public void run() {
            try {
                InputStream input = clientSocket.getInputStream();
                BufferedReader bf = new BufferedReader(new InputStreamReader(input));

                while (isRunning) {
                    if (mainServer.isStopped()) {
                        disconnect();
                    }
                    if (clientSocket.isClosed()) {
                        isRunning = false;
                        break;
                    }

                    // New Code Receive commands from device
                    String result = null;
                    try {
                        result = bf.readLine();
                        if (result == null) {
                            disconnect();
                        } else {
                            Pattern pattern = Pattern.compile("(?<=\\[).*(?=\\])");
                            Matcher matcher = pattern.matcher(result);
                            if (matcher.find()) {
                                result = matcher.group(0);
                            }
                        }
                    } catch (SocketTimeoutException e) {
                        logger.debug("Socket Read Timeout: " + remoteAddress);
                    } catch (SocketException e) {
                        isRunning = false;
                        break;
                    }
                    if (result == null || result.trim().length() == 0) {
                        continue;
                    }

【讨论】:

    【解决方案2】:

    这是您之前问题的重复:An infinite loop somewhere in my code。请不要提出新问题,而是使用编辑功能。

    话虽如此,3000 个线程肯定很多,很可能会导致过多的上下文切换。与其为每个连接启动一个新线程,不如考虑在 Java 中使用非阻塞 IO 工具。示例可以在这里找到:http://download.oracle.com/javase/1.4.2/docs/guide/nio/example/index.html

    【讨论】:

      【解决方案3】:

      您似乎也从未关闭过 BufferedReader,除非您在 kickPlayer() 方法中尝试它。

      每个读者的寿命可能比你想象的要长。

      【讨论】:

      • 在 while 循环后或 finally 块中调用 _in.close()。
      • 捕获并处理 IOExceptions。
      【解决方案4】:

      '每个 TCP 连接 1 个线程' '大约 3000 名玩家连接'

      = 3.000 个线程?!

      我的猜测:一次可以重复复制一个字节的最大线程数约为 3.000。这听起来并不奇怪。

      解决方案:减少线程,一次读取更多字节。

      您可以使用 executorService。 javadoc中有一个简单的例子:http://download.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html

      【讨论】:

      • 你能把我链接到一些关于线程池的简单主题吗?或者什么可以解决这个线程问题?
      【解决方案5】:

      我不知道为什么调用速度很慢,但我永远不会在紧密循环中一次读取一个字节。谁知道内部函数有什么样的开销。

      我会读取流中当前可用的所有数据并对其进行解析。 这将需要一个缓冲区和一些额外的簿记,但无论如何比从流中逐字节读取要快。

      【讨论】:

      • Java BufferedReader 是缓冲的(nomen est omen...),从一个字节一次读取一个字节是相对常见的。您可以观察直接从 FileInputStream 读取与将其包装在 BufferedInputStream 中时的性能差异。
      猜你喜欢
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 2022-12-15
      • 2020-03-22
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多