【问题标题】:Why does socket.isOutputShutdown() returns false even if the socket is closed?为什么即使套接字关闭,socket.isOutputShutdown() 也会返回 false?
【发布时间】:2015-04-12 14:06:06
【问题描述】:

Java Socket API 告诉我关闭套接字也会关闭套接字的InputStreamOutputStream

Socket API Javadoc 和 Input/OutputStream API 信息都没有定义(我还没有找到)“shutdown”对于OutputStreamInputStream 的确切含义,但是我一直假设关闭或关闭它们会使它们进入“shutdown”状态。

但是,在我成功调用客户端套接字的close() 方法后(调用isClosed() 返回true),如果我随后调用该套接字的isInputShutdown()isOutputShutdown() 方法,结果是false .

我确信在调用 Socket 的 close() 时,两个流都没有缓冲未读/未发送的数据。

我假设我不明白“关闭”对于套接字的输入/输出流和/或何时发生关闭意味着什么。

【问题讨论】:

  • 我相信你的意思是 isOutputShutdown ?
  • 谢谢@Jean-François Savard - 我编辑了问题以在这两个地方将“isInputStreamShutdown”和“isOutputStreamShutdown”更改为正确的方法名称。

标签: java sockets


【解决方案1】:

即使在调用 socket.getOutputStream().close() 之后,socket.isOutputShutdown() 调用也会返回 false。 此外,如果套接字尚未连接,socket.isOutputShutdown() 将返回 false。

socket.isOutputShutdown() ONLY 的调用返回 true,而 socket.shutdownOutput() 之前已被调用过。 您是对的,该文档在这一点上有点误导。

this thread 中,更详细地解释了socket.shutdownOutput() 的使用。基本上你可以用它来创建一个半封闭的连接(意味着你的一端确定不再发送数据,但愿意从另一端监听更多的数据)。

【讨论】:

    【解决方案2】:

    shutdownOutput() 导致在任何待处理数据之后发送一个 FIN,对等方将视为流的结尾。

    如果你调用了shutdownOutput()isOutputShutdown() 将返回true. 在所有其他情况下,无论套接字或连接的其他状态如何,它都会返回 false。

    同样适用于:

    • shutdownInput()isInputShutdown()
    • close()isClosed()
    • connect() 或构造带有参数的 new Socket(...)isConnected()

    除非您的应用程序调用相关的 API,否则这些 API 都不会神奇地更改状态。唯一的例外是isBound(),如果您调用bind()connect(),或构造带有参数的new Socket(...),或通过ServerSocket.accept(). 获得一个,则可以实现这一点

    而且它们都不会根据对等体连接所做的操作来改变状态。具体来说,@ 987654337@ 并不意味着对等方关闭了连接。这意味着关闭了这个套接字。

    【讨论】:

      【解决方案3】:

      TL;DR

      无论你做什么,在调用isOutputShutdown 之后获得true 的唯一方法是在之前调用shutdownOutput。无论Socket 的状态如何。输出确实需要直接关闭才能让方法返回true,而不仅仅是通过继承关闭。


      解决这个问题的最好方法是查看源代码;)

      让我们从java.net.Socket中搜索方法isOutputShutdown开始:

      public boolean isOutputShutdown() {
          return shutOut;
      }
      

      它只是一个访问器,好吧,然后让我们搜索shutOut。然后我们会注意到,只有在调用shutdownOutput 方法时该值才设置为true(这是合乎逻辑的)

      public void shutdownOutput() throws IOException
      {
          if (isClosed())
              throw new SocketException("Socket is closed");
          if (!isConnected())
              throw new SocketException("Socket is not connected");
          if (isOutputShutdown())
              throw new SocketException("Socket output is already shutdown");
          getImpl().shutdownOutput();
          shutOut = true;
      }
      

      注意这里我们抛出一个SocketException 如果套接字关闭,到目前为止,请确认您认为关闭Socket 也会关闭它的流。现在让我们看一下getImpl 以查找它引用的内容,并从它的引用中检查方法shutdownOutput

      SocketImpl getImpl() throws SocketException {
          if (!created)
              createImpl(true);
          return impl;
      }
      

      该方法的 javadoc 指定它返回附加到此套接字的SocketImpl。必要时创建它,但我怀疑它在我们的调试中相当重要。 SocketImpl 只是一个抽象类,我们必须找到真正覆盖shutdownOutput 方法的实现。

      现在我们可以发现实现是使用工厂来获取instance

      factory.createSocketImpl()
      

      让我们看看SocketImplFactory。那么这个类是一个带有一行SocketImpl createSocketImpl();的接口。那么SicketImpl的实例是如何给出的,shutdownOutput方法真正定义在哪里呢?

      让我们看一下AbstractPlainSocketImpl(默认套接字创建),它扩展了我们的SocketImpl,Steven B. Byrne 先生在顶部的 javadoc 中声明这是默认套接字实现。所以,我认为从这里我们应该能够很好地了解shutdownOutput 到底在做什么。

      /**
       * Shutdown read-half of the socket connection;
       */
      protected void shutdownInput() throws IOException {
        if (fd != null) {
            socketShutdown(SHUT_RD);
            if (socketInputStream != null) {
                socketInputStream.setEOF(true);
            }
            shut_rd = true;
        }
      }
      
      /**
       * Shutdown write-half of the socket connection;
       */
      protected void shutdownOutput() throws IOException {
        if (fd != null) {
            socketShutdown(SHUT_WR);
            shut_wr = true;
        }
      }
      

      很有趣,不是吗?为了我们的调试,让我们考虑fd在我们的例子中不为空,直接转到socketShutdown

      但是等等! SHUT_WR 是什么?

      好问题,正如@EJP 在评论中所说,它们是 Berkeley Sockets API 的长期组成部分。他们只是指定如何进行(0 表示读取,1 表示写入)

      public final static int SHUT_RD = 0;
      public final static int SHUT_WR = 1;
      

      现在回到socketShutdown

      abstract void socketShutdown(int howto)
          throws IOException;
      

      再一次,真可惜,我坚信我们拥有它......

      所以让我们进入扩展AbstractPlainSocketImpl 的类PlainSocketImpl

      native void socketShutdown(int howto) throws IOException;
      

      我们可以找到java.net.PlainSocketImpl.chere的源代码。现在让我们看一下我们方法的代码

      Java_java_net_PlainSocketImpl_socketShutdown(JNIEnv *env, jobject this,
                                                 jint howto)
      {
      
        jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
        jint fd;
      
        /*
         * WARNING: THIS NEEDS LOCKING. ALSO: SHOULD WE CHECK for fd being
         * -1 already?
         */
        if (IS_NULL(fdObj)) {
            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
                            "socket already closed");
            return;
        } else {
            fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
        }
        JVM_SocketShutdown(fd, howto);
      }
      

      现在我想你已经很好地了解了该方法是如何真正进行关闭流的。

      “关闭”对于 OutputStream 或 InputStream 意味着什么

      但是为什么 isOutputShutdown 返回 false 而 isClosed 返回 true 呢?

      考虑到 shutOut 布尔值仅在 shutdownOutput 方法结束时设置为 true

      public void shutdownOutput() throws IOException
      {
          if (isClosed())
              throw new SocketException("Socket is closed");
          if (!isConnected())
              throw new SocketException("Socket is not connected");
          if (isOutputShutdown())
              throw new SocketException("Socket output is already shutdown");
          getImpl().shutdownOutput();
          shutOut = true;
      }
      

      这意味着默认关闭Socket 不会将该布尔值设置为true。但是即使该方法返回false,它实际上是关闭的,因为如果SocketStream是基于关闭的,它就无法打开。

      【讨论】:

      • @Jean-François Savard(和其他所有人)非常感谢您剖析创建行为的代码。我不会说 Socket 通信状态机的这部分实现得很好,但我现在确实理解得更好了。
      猜你喜欢
      • 2021-08-15
      • 1970-01-01
      • 1970-01-01
      • 2017-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-26
      • 1970-01-01
      相关资源
      最近更新 更多