【问题标题】:Socket opens a pipe but doesn't close it套接字打开管道但不关闭它
【发布时间】:2015-12-10 04:28:02
【问题描述】:

我正在监听一个套接字并使用了 readLine() 函数。 如果我看到我的程序打开的文件描述符的数量,我会看到当我调用 readLine() 函数时,会打开两个文件描述符(管道)。 (可以在/proc//fd中看到)

如果发生套接字超时异常,即使在关闭 Buffered Reader 之后,管道仍保持打开状态。 如何关闭它?

这是我的程序:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.StringTokenizer;

// Test case code for file descriptor leak.
// The following should produce somewhere near 150 sockets in CLOSE_WAIT state.
// The problem appears to be in   sun.nio.ch.SocketAdapter.SocketInputStream.read(ByteBuffer);

public class TestClose implements Runnable {

public static final String SMTP_HOSTNAME = "10.10.10.59";

    public void run()
    {
    InetSocketAddress sockAddr = new InetSocketAddress(SMTP_HOSTNAME, 5269);
            SocketChannel sChannel = null;
            Socket socket = null;
            String result = null;
             BufferedReader lineRdr = null;
             InputStreamReader is  = null;
            try
            {
                    sChannel = SocketChannel.open();
                    sChannel.socket().connect(sockAddr);


                    sChannel.socket().setSoTimeout(20000);
                    socket = sChannel.socket();

                    is = new InputStreamReader(socket.getInputStream());
                   lineRdr = new BufferedReader(is);



                    do
                    {
            // before performing the first readline the channel is unregistered
                            System.err.println("before first readline: isOpen = "+sChannel.isOpen()+" isRegistered="+sChannel.isRegistered());                                

                            result = lineRdr.readLine();                            

                            System.err.println("<- "+result);

            // after performing it is registered.
                            System.err.println("after first readline: isOpen = "+sChannel.isOpen()+" isRegistered="+sChannel.isRegistered());

                    } while(result != null && result.length() > 0 && result.matches("^[1-5][0-9]{2}-"));

                    if(result == null || result.length() == 0)
                    {
                            System.err.println("Received truncated response from SMTP server " + sockAddr.getHostName());
                            return;
                    }

                    // Tokenize the last line result
                    //
                    StringTokenizer t = new StringTokenizer(result);
                    int rc = Integer.parseInt(t.nextToken());
                    if(rc != 220) return;

                    //
                    // Send the QUIT command causing the server side to close its end of the connection
                    //
                    String cmd = "QUIT\r\n";
                    socket.getOutputStream().write(cmd.getBytes());
                    System.err.println("-> "+cmd);
                    do
                    {
                            result = lineRdr.readLine();
                            System.err.println("<- "+result);
                    } while(result != null && result.length() > 0 && result.matches("^[1-5][0-9]{2}-"));

                    if(result == null || result.length() == 0)
                    {
                            System.err.println("Received truncated response from SMTP server " + sockAddr.getHostName());
                            return;
                    }

            }
    catch (Exception e) {
        System.out.println("result "+result);
        e.printStackTrace();
    }
            finally
            {
        try {

                //socket.getInputStream().close();
               lineRdr.close();
               lineRdr = null;
               is.close();
            is = null;

               System.err.println("before close: isOpen = "+sChannel.isOpen()+" isRegistered="+sChannel.isRegistered());

            System.err.println("Closing SMTP socket channel "+sChannel);
            System.err.println("channel.socket().isConnected = "+ sChannel.socket().isConnected());
             System.err.println("channel.socket().isclose = "+ sChannel.socket().isClosed());
             System.err.println("channel.socket().isConnected = "+ sChannel.socket().isConnected());
            if (sChannel != null) {
                if(sChannel.socket().isClosed()== false){
                            sChannel.socket().shutdownOutput();
                     sChannel.socket().close();
                    }
                   // sChannel.shutdownOutput();
                    sChannel.close();
                    System.err.println("Closed SMTP socket channel "+sChannel);

                // The socket is still connected here.
                    System.err.println("channel.socket().isConnected = "+sChannel.socket().isConnected());
                    System.err.println("channel.socket().isclose = "+ sChannel.socket().isClosed());
            }
        }
        catch (Exception e) {
           System.err.println("Exception on close:");
           e.printStackTrace();
        }
            }

            return;
    }


    public static void main(String[] args) {
    TestClose test = new TestClose();
    while(true) {
    for(int i = 0; i < 1; i++) {
     // this bug seems only to appear if different threads are reading the channels
            Thread thread = new Thread(test);
        thread.start();
        try {thread.join(); } catch(InterruptedException e) {   }
    }

    System.err.println("Going to sleep.... run netstat -an | grep CLOSE_WAIT ");

            try { Thread.sleep(10000); } catch(InterruptedException e) {}
    }
    }
}

【问题讨论】:

    标签: java linux sockets pipe bufferedreader


    【解决方案1】:

    我看到 readLine() 函数打开了两个文件描述符 (管道)。


    是的,在 socketTimeout 设置为非零值后,SocketInputStream 开始在readLine() 内使用非阻塞读取,并且可以(如果套接字中不存在输入)每个读取线程最多打开两个管道。

    如果发生套接字超时异常,即使在关闭 Buffered 之后 读者,管道保持打开状态。如何关闭它?


    您不必这样做,在您的情况下,管道将在下一次 GC 之后关闭,此时选择器(保存文件描述符 id)将由 sun.misc.Cleaner 关闭。 Cleaner 类基于PhantomReference,其清理工作在 ReferenceHandler 线程中进行。

    【讨论】:

    • 所以说socket打开管道会更准确。
    • 准确地说Selector打开了管道,Selector实例是通过SelectorProvider.openSelector()创建的,Socket持有SelectorProvider的实例。但我认为说the socket opens the pipes. 是不正确的。无论如何,恕我直言,这是不必要的信息,因为很多细节不能使整个画面清晰,令人困惑。
    • 在答案中我指向SocketAdaptor.SocketInputStream,它决定创建选择器(并以这种方式打开管道)。我还描述了取决于它将这样做的条件。在此之后,我回答了这个问题。 我希望这会有所帮助
    猜你喜欢
    • 2011-07-19
    • 2012-01-17
    • 2014-09-18
    • 2016-02-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 2018-09-09
    • 2016-02-15
    相关资源
    最近更新 更多