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

时间:2018-01-14 03:14:33

标签: 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();
   }
}

我已经尝试解决这个问题差不多一个月了! 有没有人在代码中看到问题并知道如何缓解这个问题?非常感谢!

1 个答案:

答案 0 :(得分:1)

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

在您的代码中,您可以:

socket = new Socket("192.168.1.2", 7767);
connectToClient方法中的

。每次执行此操作而不关闭套接字时,都会使用文件描述符。最终达到O / S定义的限制,你会在Mac O / S中得到Errno::EMFILE错误。

你有两个选择来解决这个问题。第一个是你已经注释掉的东西 - 当你完成它时关闭连接。但是,正如您在评论中指出的那样,这种情况非常频繁发生,并且您不希望产生持续打开和关闭的开销。

这带给我们第二选择 - 重用连接。如果您正在设计的协议处理它,套接字可以反复发送数据。通过协议来回发送数据并重用Socket。

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