0

我编写了一个简单的 TCP 服务器来将一些用户数据传输给它,并将其保存在一个简单的 MySQL 表中。如果我现在一个接一个地运行超过 2000 个客户端,它就会停止工作。在跑步时我会得到一些IO error java.io.EOFException你可能还会看到我为此犯的错误。但最重要的是我明白了

 IO error java.net.SocketException: Connection reset
    Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Unknown Source)
    at Server.main(Server.java:49)

应该有足够的内存,但线程仍在运行,我看不出我在哪里犯了错误,他们没有被终止。所以我运行了多达 3900 个线程。所以这里是服务器的一部分:

try {
    // create new socket
    ServerSocket sock = new ServerSocket(port);
    textArea.setText(textArea.getText() + "Server started\n");
    while (true) {
        // accept the connection
            Socket newsock = sock.accept();
        // handle the action
        Thread t = new ThreadHandler(newsock);
            newsock.setSoTimeout(2000); // adding client timeout
        t.start();
        }
    } catch (Exception e) {

猜的真简单。这是我处理套接字的方式:

class ThreadHandler extends Thread {
    private Socket socket;
    private MySQLConnection sqlConnection;

    ThreadHandler(Socket s) {
        socket = s;
        sqlConnection = new MySQLConnection();
    }

    public void run() {
        try {
            DataOutputStream out = new DataOutputStream(
                    socket.getOutputStream());
            DataInputStream in = new DataInputStream(new BufferedInputStream(
                    socket.getInputStream()));
            Server.textArea.append((new Date()) + "\nClient connected IP: " + socket.getInetAddress().toString()+"\n");

            int firstLine = in.readInt(); // get first line for switch

            switch (firstLine) {
            case 0:
                // getting the whole objekt for the database in own lines!
                String name2 = in.readUTF();
                int level2 = in.readInt();
                int kp2 = in.readInt();
                String skill = in.readUTF();

                LeadboardElement element2 = new LeadboardElement();
                element2.setName(name2);
                element2.setLevel(level2);
                element2.setKillPoints(kp2);
                element2.setSkill(skill);
                sqlConnection.saveChaToLeadboard(element2);
                break;
                //case 1 return the top10
###.... shorten here the rest of the cases
                out.close();
            in.close();
            //close this socket
            socket.close();
                    Server.textArea.append("Client disconnected IP: " + socket.getInetAddress().toString()+ "\n" + (new Date())
                            + "\n----------------------------------------------------\n");
            // autoscrolldown
            Server.textArea.setCaretPosition(Server.textArea.getDocument()
                    .getLength());
         } catch (Exception e) {
            System.out.println("IO error " + e);
            try {
                socket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }finally{
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

saveChaToLeadboard只需获取名称级别 kp 和技能并使用 a将其preparedStatement保存到我的 MySQL 表中。我希望你能帮助我,我只是没有看到它的错误。我想我需要在某个地方加入它,但如果我在它的末尾加入一个连接(在 socket.close() 之后),它仍然会做同样的事情。

这里保存到数据库方法:

public void saveChaToLeadboard(LeadboardElement element) {
        try {
            // load driver
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection(this.databaseURL
                    + DATABASE_NAME, this.user, this.password);
            // insert values into the prep statement
            preparedStatement = connection
                    .prepareStatement(PREP_INSERT_STATEMENT);
            preparedStatement.setString(1, element.getName());
            preparedStatement.setInt(2, element.getLevel());
            preparedStatement.setInt(3, element.getKillPoints());
            if(!element.getSkill().equalsIgnoreCase("")){
                preparedStatement.setString(4, element.getSkill());
            }else{
                preparedStatement.setString(4, null);
            }
            // execute
            preparedStatement.executeUpdate();
            connection.close();

        } catch (Exception e) {
            Server.textArea.append(e.getMessage() + "\n");
            Server.textArea.setCaretPosition(Server.textArea.getDocument()
                    .getLength());
            try {
                connection.close();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }

非常感谢!问候

4

2 回答 2

3

你的run()方法被破坏了,但我怀疑部分问题是你并不总是关闭网络套接字和流。特别是,如果在读取或处理您读取的数据时出现异常,我怀疑您没有关闭它们。finally您应该始终在一个块(或 Java 7 等效项)中关闭套接字和流。

另一个潜在的问题是,由于另一端没有发送数据,一些连接可能会停止。为了解决这个问题,您需要在套接字上设置读取超时......以便可以关闭与慢速/卡住客户端的连接。

最后,甚至尝试使用每个连接的线程并行处理 2000 多个连接可能是不现实的。这是很多资源1。我建议你使用一个固定上限在几百以下的线程池,如果所有线程都在使用中,则停止接受新连接。


1 - 每个线程堆栈在 HotSpot JVM 上占用至少 64K 的内存,并且可能多达 1Mb。然后是线程直接或间接引用的堆资源,以及维护线程和套接字状态所需的操作系统资源。对于 2000 个线程,这可能是多个 Gb 的内存。

于 2013-02-13T09:49:15.533 回答
0

恕我直言,2000 个线程对于单个进程来说偏高,而 2000 个数据库连接肯定是。

无论您是否达到 2000 个传入连接的限制,您的方法都无法扩展。

为了实现可扩展性,您需要考虑使用资源池 - 这意味着:

  • 从套接字中读取数据的读取器线程池,将数据排队等待处理。
  • 一个工作线程池,处理由读取器线程排队的数据。
  • 工作线程使用的数据库连接池 - 可以调整此连接池,以便每个工作线程都有自己的连接,但重要的是您不要不断打开和关闭数据库连接。

查看线程池的并发 API和 IO 的NIO API

这种安排将允许您调整服务器以实现所需的吞吐量。

于 2013-02-13T09:31:25.713 回答