簡體   English   中英

Java TCP服務器到許多連接

[英]java tcp server to many connections

我編寫了一個簡單的TCP服務器,將一些用戶數據轉換為該服務器,並將其保存在一個簡單的MySQL表中。 如果我彼此之間現在運行超過2000個客戶端,它將停止工作。 運行時,我遇到一些IO error java.io.EOFException您可能還會看到我為此而犯的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水平和技能,並使用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();
            }
        }

非常感謝! 問候

您的run()方法已損壞,但我懷疑問題的部分原因在於您並不總是關閉網絡套接字和流。 特別是,如果在讀取或處理讀取的數據時出現異常,我懷疑您沒有關閉它們。 您應該始終在finally塊(或等效的Java 7)中關閉套接字和流。

另一個潛在的問題是某些連接可能由於另一端未發送數據而停止。 為了解決這個問題,您需要在套接字上設置一個讀取超時...,以便可以關閉與慢速/阻塞客戶端的連接。

最后,甚至嘗試與每個連接的線程並行處理2000+個連接可能是不現實的。 那是很多資源1 我建議您使用固定上限為低數的線程池,如果所有線程都在使用中,則停止接受新連接。


1-每個線程堆棧在HotSpot JVM上至少占用64K內存,並且可能多達1Mb。 然后是線程直接或間接引用的Heap資源,以及維護線程和套接字狀態所需的OS資源。 對於2000個線程,這可能是多個Gb的內存。

對於單個進程,IMHO 2000線程處於較高的位置,而對於2000個數據庫連接,則絕對是如此。

無論您是否達到2000個傳入連接的限制,您的方法都無法擴展。

為了實現可伸縮性,您需要考慮使用資源池-這意味着:

  • 從套接字讀取數據的讀取器線程池,以排隊處理數據。
  • 一個工作線程池,用於處理讀取器線程排隊的數據。
  • 一個工作線程使用的數據庫連接池-可以調整此連接池,以便每個工作線程都有自己的連接,但是重要的是您不能持續打開和關閉數據庫連接。

查看線程池的並發API和IO的NIO API

這種安排將允許您調整服務器以實現所需的吞吐量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM