BufferedReader.read()正在消耗100%的CPU资源

9

我有一个JAVA游戏服务器,每个TCP连接都使用1个线程。(我知道这是不好的,但现在我必须保持这种方式)。在一台(3.2Ghz 6核x2机器,24GB RAM,Windows Server 2003 64位)上,以下是一段代码:

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资源?

我已经尝试在While循环中加入Thread.sleep(1)等操作,但没有任何效果,我猜问题出在BufferedReader.read()方法内部。

有没有人知道这是什么原因?如何解决?


6
我很惊讶的是你在循环中使用字符串拼接,而非这个事实本身。还有,为什么你只读取一个字符? - Jon Skeet
数据包非常小,类似于“AB123”的字符串。所以这并不重要。 - Reacen
2
直到有人向您发起DDOS攻击并发送了一个巨大的字符串,您才会意识到问题的严重性。使用StringBuilder既可以轻松读取多个字符,又可以避免这种情况的发生...为什么不这样做呢? - Jon Skeet
1
每个连接一个线程是一种非常糟糕的做法。每个 CPU 核心一个线程是最佳实践。最多可能需要 40-50 个线程,但三千个就有些疯狂了。 - Mister Smith
我遭受了很多DoS攻击,我在这里展示的代码只是一个干净的版本。我在那个循环中实现了“最大32个字符规则”。但我仍然不知道如何修复这个字符串连接问题,我需要一些代码来理解你所说的。 - Reacen
5个回答

3

1

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

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


Java中的BufferedReader是有缓冲区的(名副其实...),并且从中读取一个字节相对常见。当直接从FileInputStream中读取数据与将其包装在BufferedInputStream中时,您可以观察到性能差异。 - Lauri Piispanen

0

请问您能否提供一些关于线程池或其他可以解决这个线程问题的简单主题的链接? - Reacen

0

看起来你从未关闭 BufferedReader,除非你在 kickPlayer() 方法中尝试关闭它。

每个读取器可能比你意识到的要长寿得多。


在 while 循环结束后或者 finally 块中调用 _in.close()。 - FacilityDerek
捕获并处理IOExceptions。 - FacilityDerek

0

我也卡在了同样的问题上,尝试了很多解决方案但使用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;
                    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接