Java TCP 客户端重复连接导致 EMFILE 错误

标签 java tcp

我的 Java 应用程序与服务器建立 TCP 连接,并通过发送和接收消息每秒与其通信。服务器和客户端都在同一台 Mac 上运行。大约 15-20 分钟后,我的服务器崩溃并出现错误“Errno::EMFILE 打开的文件过多”。这是我的客户端代码:

package testtcp;

import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class TestTCP extends  JPanel
{
    JFrame frame = new JFrame("Button Demo");

    ScheduledExecutorService executorService;

    private Socket socket = null;
    private DataInputStream input = null;
    private DataOutputStream output = null;
    private BufferedReader br = null;

    private boolean isMapUpdating = false;


    public TestTCP() 
    {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setPreferredSize(new Dimension(300,300));
        frame.add(this);

        JButton b1 = new JButton("BLACK");
        b1.setPreferredSize(new Dimension(150,50));
        b1.setFocusPainted(false); // get rid of border around text
        add(b1);
        b1.addActionListener((java.awt.event.ActionEvent evt) -> 
        {
            startAcarsConnection();
        });

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

    }


    public void startAcarsConnection()
    {
        start();

    }

    public void start() 
    {
        System.out.println("THREAD START");

        // Default timer rate
        int timerRate = 1;

        executorService = Executors.newSingleThreadScheduledExecutor();

        executorService.scheduleAtFixedRate(new Runnable() 
        {
            @Override
            public void run() 
            {
                // Create new TCP connection if the map is not currently updating
                if(isMapUpdating == false)
                {
                    isMapUpdating = true;

                    communicateWithServer();
                }
            }
        }, 0, timerRate, TimeUnit.SECONDS);
    }


    public void stop() 
    {
        executorService.shutdown();
    }

    public void communicateWithServer()
    {
        // Create a message to the server
        String messageToServer = makeMessageToServer();

        // Connect to the client and receive the response    
        String messageFromServer = connectToClient(messageToServer);

        SwingUtilities.invokeLater(() ->
        {
            messageReceived(messageFromServer);
        });

    }


    public String connectToClient(String messageToServer)
    {
        String data = "";
        // Message from the server that should terminate TCP connection
        String  terminator = "END_IGOCONNECT_DATA";

        try
        {
            // Create socket and streams
            socket = new Socket("192.168.1.2", 7767);
            input = new DataInputStream( socket.getInputStream());
            output = new DataOutputStream( socket.getOutputStream());

            //Send message to the server
            output.writeBytes(messageToServer);


            System.out.println("MESSAGE TO SERVER FROM CONNECT TO CLIENT: "+messageToServer);


            //Read Response
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String s = "";
            int value;

            // Process the message from the server and add to the StringBuilder
            while((value = br.read()) != -1) 
            {
                // converts int to character
                char c = (char)value;

                sb.append(c);

                if(sb.toString().contains(terminator))
                {
                    break;
                }
            }

            // Create the final string
            data = sb.toString();
        }

        catch (UnknownHostException e)
        {
            System.out.println("Sock:"+e.getMessage());

            // Close Connection
            cancelConnection();

            // Pop-up message that the airport was not found
            String message = "Application was not able to establish connection with X-Plane.\n"
                    + "Check whether IP Address and Port number were correctly entered in Settings.\n"
                    + "Check whether connection is not being blocked by your firewall.";
            JOptionPane.showMessageDialog(new JFrame(), message, "TCP Connection Error: UnknownHostException",
            JOptionPane.ERROR_MESSAGE);

            data = "ERROR";
        }

        catch (EOFException e)
        {
            System.out.println("EOF:"+e.getMessage()); 

            // Close Connection
            cancelConnection();

            // Pop-up message that the airport was not found
            String message = "Application was not able to establish connection with X-Plane.\n"
                    + "Check whether IP Address and Port number were correctly entered in Settings.\n"
                    + "Check whether connection is not being blocked by your firewall.";
            JOptionPane.showMessageDialog(new JFrame(), message, "TCP Connection Error: EOFException",
            JOptionPane.ERROR_MESSAGE);

            data = "ERROR";
        }

        catch (IOException e)
        {
            System.out.println("IO:"+e.getMessage());

            // Close Connection
            cancelConnection();

            // Pop-up message that the server was not found
            if(!e.getMessage().equals("Socket closed"))
            {
                String message = "Application was not able to establish connection with X-Plane.\n"
                    + "Check whether IP Address and Port number were correctly entered in Settings.\n"
                    + "Check whether connection is not being blocked by your firewall.";
                JOptionPane.showMessageDialog(new JFrame(), message, "TCP Connection Error: IOException",
                JOptionPane.ERROR_MESSAGE);
            }
            // "Connection reset"

            data = "ERROR";
        }

        finally 
        {
            // TO DO!!! DISABLED FOR NOW!! closeSocketPax();
        }

        return data;
    } 


    public void cancelConnection()
    {

        executorService.shutdown();

        closeSocketPax();

        SwingUtilities.invokeLater(() ->
        {
            System.out.println("Cancel Connection");
        });
    }


    private void closeSocketPax()
    {
        try
        {   
            if(socket!=null) { socket.close();}
            if(input != null) { input.close();}
            if(output != null) { output.close();}
            if(br != null) { br.close();}
        }
        catch (IOException ex) 
        {
            String message = "Error closing socket.";
            JOptionPane.showMessageDialog(new JFrame(), message, "TCP Connection Error: IOException",
            JOptionPane.ERROR_MESSAGE);
        }
        socket = null;
        input = null;
        output = null;
        br = null;
    }

    private String makeMessageToServer()
    {
        return "MESSAGE TO SERVER";
    }

    private void messageReceived(String message)
    {
        System.out.println("MESSAGE RECEIVED: "+message);

        isMapUpdating = false;
    }


    public static void main(String[] args) 
    {    
       new TestTCP();
   }
}

我已经尝试解决这个问题将近一个月了! 有没有人看到代码中的问题并且知道如何缓解该问题?非常感谢!

最佳答案

您创建的每个连接都使用一个文件描述符。在任何操作系统中,您的进程可以拥有的描述符数量都有限制。例如,在 Linux 环境中,我的限制是 1024。不同的 O/S 有不同的限制,但在 Linux 和 Mac O/S 等 Unix 派生环境中,您可以运行 ulimit -n 来查看限制是什么。

在您的代码中:

socket = new Socket("192.168.1.2", 7767);

connectToClient 方法中。每次你这样做并且你不关闭套接字时,你就会用完一个文件描述符。最终您达到了 O/S 定义的限制,并且在 Mac O/S 中出现了 Errno::EMFILE 错误。

您有两种选择来解决这个问题。第一个几乎是您注释掉的内容 - 完成后关闭连接。但是,正如您在评论中指出的那样,这种情况经常发生,您不想承担不断打开和关闭的开销。

这给我们带来了第二个选择——重用连接。如果您正在设计的协议(protocol)处理数据,套接字可以一遍又一遍地发送数据。通过协议(protocol)来回发送数据并重用 Socket。

虽然有一个警告 - 如果您的物理连接以某种方式被切断 - 例如,您从以太网切换到 Wi-Fi - 您的服务仍然需要处理可能的错误。您的代码包含大部分内容,但您可能需要考虑在发生这种情况时关闭并尝试重新连接。

关于Java TCP 客户端重复连接导致 EMFILE 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48246383/

相关文章:

java - 可比较内部的参数 (Java)

java - 在不同的 Web 应用程序之间共享 Session 对象

java - 如何使 thymeleaf spring 安全命名空间可用?

localhost - 修复拨号 tcp 问题

objective-c - 使用 TCP 套接字发送多条小消息或更短消息的速度更快

ssl - 使用 tcpdump 仅捕获 ssl 握手

java - 如何创建通用数组?

java - 在哪里可以下载 JDK 1.6u20 64 位?

objective-c - TCP套接字通信问题

c# - Threading、Winforms、将TCP操作与主窗口窗体分离