Java 网络编程时序问题

起因

这两天在做有关网络的编程练习,发现网路已经联通,但是服务器端接收不到数据,排查了好久,功能一个个测试了之后,服务端的代码没有问题了.焦点落在客户端.最后通过查看源代码,发现原来是要按照严格的时序来进行操作.如下:
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");
    }
}
            //........
            //剩余的一些工作

尘埃落定.

扫描二维码关注公众号,回复: 10953511 查看本文章
发布了45 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZM_Yang/article/details/70602270