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,它实际上是关闭的,因为如果Socket和Stream是基于关闭的,它就无法打开。