起因
这两天在做有关网络的编程练习,发现网路已经联通,但是服务器端接收不到数据,排查了好久,功能一个个测试了之后,服务端的代码没有问题了.焦点落在客户端.最后通过查看源代码,发现原来是要按照严格的时序来进行操作.如下:
1. 创建URL实例;
2. 获取URLConnection 实例,如果单纯的只是从服务器读数据,直接从URL实例读就可以了;
3. setDoOutput
设置输出功能,这样就相当于POST
方式;
4. 打开链接服务器的一个连接,这一步其实可以省略;
5. getOutputStream()
获取输出流,此方法会隐式打开与服务端的连接;
6. write()
将数据写到输出流;
7. close()
关闭输出流;
8. getOutputStream()
获取输入流;
9. read()
从输入流读取服务端返回数据;
10. close()
关闭输入流.
这其中,第8步 必须 在第7步 之后,否则数据是无法传输到服务端的.因为在使用getOutputStream()
的同时,会将getOutputStream()
获取的输出流关闭,这就造成实际上并没有把数据写出去,因此服务器端自然无法接收到数据.
好了,TL;DR,如果这是为了知道原因,到这一步也就完了,下面的就没必要看了.
下面是源码分析:
URLConnection#openConnection()
位置:java/net/URL.java
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
//其中 handler 为
URLStreamHandler handler = handlers.get(protocol);
//Handlers 为HashTable的实例
static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
//那么, 既然是HashTable , 有get 就会有put ,我们看看put 在哪?通过寻找发现,handlers.put() 仅仅有一处:在static URLStreamHandler getURLStreamHandler(String protocol)中
static URLStreamHandler getURLStreamHandler(String protocol) {
URLStreamHandler handler = handlers.get(protocol);
if (handler == null) {
boolean checkedWithFactory = false;
// Use the factory (if any)
if (factory != null) {
handler = factory.createURLStreamHandler(protocol);
checkedWithFactory = true;
}
// Try java protocol handler
if (handler == null) {
String packagePrefixList = null;
packagePrefixList
= java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
protocolPathProp,""));
if (packagePrefixList != "") {
packagePrefixList += "|";
}
// REMIND: decide whether to allow the "null" class prefix
// or not.
packagePrefixList += "sun.net.www.protocol";
StringTokenizer packagePrefixIter =
new StringTokenizer(packagePrefixList, "|");
while (handler == null &&
packagePrefixIter.hasMoreTokens()) {
String packagePrefix =
packagePrefixIter.nextToken().trim();
//通过反射获取实际的 URLConnection 的子类
String clsName = packagePrefix + "." + protocol +
".Handler";
Class<?> cls = null;
try {
cls = Class.forName(clsName);
} catch (ClassNotFoundException e) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
cls = cl.loadClass(clsName);
}
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (Exception e) {
// any number of exceptions can get thrown here
}
}
}
synchronized (streamHandlerLock) {
URLStreamHandler handler2 = null;
// Check again with hashtable just in case another
// thread created a handler since we last checked
handler2 = handlers.get(protocol);
if (handler2 != null) {
return handler2;
}
// Check with factory if another thread set a
// factory since our last check
if (!checkedWithFactory && factory != null) {
handler2 = factory.createURLStreamHandler(protocol);
}
if (handler2 != null) {
// The handler from the factory must be given more
// importance. Discard the default handler that
// this thread created.
handler = handler2;
}
// Insert this handler into the hashtable
if (handler != null) {
//将面反射获得的URLConnection 子类的实例以协议作为键存入HashTable<String, URLConnection> 的实例 handlers 中
handlers.put(protocol, handler);
}
}
}
return handler;
}
/**
*http://grepcode.com/project/repository.grepcode.com/java/root/jdk/openjdk/
*/
位置:sun/net/www/protocol/http/Handler.java
//Handler extends java.net.URLStreamHandler
protected java.net.URLConnection openConnection(URL u) throws IOException {
return openConnection(u, (Proxy)null);
}
protected java.net.URLConnection openConnection(URL u, Proxy p) throws IOException {
return new HttpURLConnection(u, p, this);
}
到这里就知道,通过反射,当协议是 URL 的 http 时,返回的 HttpURLConnection.需要说明的是,这个子类的实现并不是java.net.HttpURLConnection
,而是在sun.net.www.protocol.http
下的同名类中实现.其他协议以此类似.
到这里,通过java.net.URL#openConnection()
获得对应的java.net.URLConnection
子类的实例就分析完了.
小结
通过反射的方式,从传入的地址获取的协议取得相对应的具体实现实例,实现了抽象与实现的分离.
下面来看看, java.net.URLConnection#getOutputStream()
java.net.URLConnection#getOutputStream()
位置:sun/net/www/protocol/http/HttpURLConnection.java
public synchronized OutputStream getOutputStream() throws IOException {
try {
//......
//一些检查
//1.找到http.getOutputStream()获取的实例
//http 为sun.net.www.http.HttpClient 一个实例
ps = (PrintStream)http.getOutputStream();
if (streaming()) {
if (strOutputStream == null) {
if (fixedContentLength != -1) {
//获取的是StreamingOutputStream的实例,传入一个 PrintStream的实例,猜测应该是用了装饰模式
//记住这里,一会我们还会接着看
strOutputStream = new StreamingOutputStream (ps, fixedContentLength);
} else if (chunkLength != -1) {
strOutputStream =
new StreamingOutputStream (new ChunkedOutputStream (ps, chunkLength),-1);
}
}
return strOutputStream;
} else {
if (poster == null) {
poster = new PosterOutputStream();
}
return poster;
}
} catch (RuntimeException e) {
disconnectInternal();
throw e;
} catch (IOException e) {
disconnectInternal();
throw e;
}
}
位置:sun/net/www/http/HttpClient.java
public OutputStream getOutputStream() {
//2. 找到serverOutput
return serverOutput;
}
//serverOutput 是 PrintStream 的实例,只有一处赋值
位置:sun/net/NetworkClient.java
public void openServer(String server, int port)
throws IOException, UnknownHostException {
if (serverSocket != null)
closeServer();
serverSocket = doConnect (server, port);
try {
//在这赋值
//3. 找到 serverSocket.getOutputStream()实例
//serverSocket 为java.net.Socket的一个实例(子类实例?待查)
serverOutput = new PrintStream(new BufferedOutputStream(
serverSocket.getOutputStream()),
true, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding +"encoding not found");
}
serverInput = new BufferedInputStream(serverSocket.getInputStream());
}
位置:sun/net/NetworkClient.java
public void openServer(String server, int port)
throws IOException, UnknownHostException {
if (serverSocket != null)
closeServer();
//serverSocket 在这赋值
//4. 找到serverSocket.getOutputStream()的实例
// serverSocket 为java.net.Socket的一个实例
serverSocket = doConnect (server, port);
try {
serverOutput = new PrintStream(new BufferedOutputStream(
serverSocket.getOutputStream()),
true, encoding);
} catch (UnsupportedEncodingException e) {
throw new InternalError(encoding +"encoding not found");
}
serverInput = new BufferedInputStream(serverSocket.getInputStream());
}
位置:sun/net/NetworkClient.java
protected Socket doConnect (String server, int port)
throws IOException, UnknownHostException {
Socket s;
if (proxy != null) {
if (proxy.type() == Proxy.Type.SOCKS) {
s = (Socket) AccessController.doPrivileged(
new PrivilegedAction() {
public Object More ...run() {
return new Socket(proxy);
}});
} else
//说明真的是Socket的实例,不是子类的
s = new Socket(Proxy.NO_PROXY);
} else
s = new Socket();
// Instance specific timeouts do have priority, that means
// connectTimeout & readTimeout (-1 means not set)
// Then global default timeouts
// Then no timeout.
if (connectTimeout >= 0) {
s.connect(new InetSocketAddress(server, port), connectTimeout);
} else {
if (defaultConnectTimeout > 0) {
s.connect(new InetSocketAddress(server, port), defaultConnectTimeout);
} else {
s.connect(new InetSocketAddress(server, port));
}
}
if (readTimeout >= 0)
s.setSoTimeout(readTimeout);
else if (defaultSoTimeout > 0) {
s.setSoTimeout(defaultSoTimeout);
}
return s;
}
位置:java/net/Socket.java
public OutputStream getOutputStream() 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 shutdown");
final Socket s = this;
OutputStream os = null;
try {
//在这里获取OutputStream 实例,
os = AccessController.doPrivileged(
new PrivilegedExceptionAction<OutputStream>() {
public OutputStream run() throws IOException {
//5. 找到impl
//impl 是 java.net.PlainSocketImpl的实例
return impl.getOutputStream();
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
return os;
}
位置:java/net/PlainSocketImpl.java
protected synchronized OutputStream getOutputStream() throws IOException {
//5. 继续追踪PlainSocketImpl里面的impl
//impl有两个可能,TwoStacksPlainSocketImpl或者DualStackPlainSocketImpl的实例,但是两者的个getOuputStream()方法都从AbstractPlainSocketImpl继承而来,并没有复写,因此我们看AbstractPlainSocketImpl中的实现即可.
return impl.getOutputStream();
}
位置:java/net/AbstractPlainSocketImpl.java
protected synchronized OutputStream getOutputStream() throws IOException {
synchronized (fdLock) {
if (isClosedOrPending())
throw new IOException("Socket Closed");
if (shut_wr)
throw new IOException("Socket output is shutdown");
if (socketOutputStream == null)
socketOutputStream = new SocketOutputStream(this);
}
return socketOutputStream;
}
//最终,在次方法中创建了SocketOutputStream的实例并返回
SocketOutputStream#flush()
是从OutputStream#flush()
继承而来,而OutputStream#flush()
是什么也不做的,因此在自己写的程序使用flush()并没有真的写到写到服务器,只有关闭输出流的时候才会真的写到服务器.
public void flush() throws IOException {
}
URLConnection#getInputStream()
与getOutputStream()
类似,getInputStream()
也是通过反射方式获得了实际的HttpURLConnection
实现.
位置:sun/net/www/protocol/http.HttpURLConnection.java
public synchronized InputStream More ...getInputStream() throws IOException {
//........
//一些检测工作
if (streaming() ) {
if (strOutputStream == null) {
getOutputStream();
}
/* make sure stream is closed */
//注意这里,在获取输入流的同时,把输出流关闭了.
strOutputStream.close ();
if (!strOutputStream.writtenOK()) {
throw new IOException ("Incomplete output stream");
}
}
//........
//剩余的一些工作
尘埃落定.