美文网首页
JDK源码-Socket套接字系列

JDK源码-Socket套接字系列

作者: 薛云龙 | 来源:发表于2017-09-21 19:47 被阅读72次
    套接字基本流程

    ServerSocket

    //指定端口的构造器
    public ServerSocket(int port) throws IOException {
            this(port, 50, null);
        }
    上个构造器调用了该方法:port指定了服务器端socket监听的端口
    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
            setImpl();
            if (port < 0 || port > 0xFFFF)
                throw new IllegalArgumentException(
                           "Port value out of range: " + port);
            if (backlog < 1)
              backlog = 50;
            try {
                bind(new InetSocketAddress(bindAddr, port), backlog);
            } catch(SecurityException e) {
                close();
                throw e;
            } catch(IOException e) {
                close();
                throw e;
            }
        }
    backlog指服务器端处理请求的队列最大的长度,默认为50.
    InetSocketAddress是SocketAddress的实现类.封装了hostname,InetAddress,port等参数.
    

    想了解InetAddress可以参看这篇文章:http://www.jianshu.com/p/0ba3a391de03

    • 通过bind方法,对该ip+port进行监听
    public void bind(SocketAddress endpoint, int backlog) throws IOException {
            if (isClosed())//判断socket是否关闭
                throw new SocketException("Socket is closed");
            if (!oldImpl && isBound())//判断socket的监听状态
                throw new SocketException("Already bound");
            if (endpoint == null)
                endpoint = new InetSocketAddress(0);
            if (!(endpoint instanceof InetSocketAddress))
                throw new IllegalArgumentException("Unsupported address type");
            InetSocketAddress epoint = (InetSocketAddress) endpoint;
            if (epoint.isUnresolved())
                throw new SocketException("Unresolved address");
            if (backlog < 1)
              backlog = 50;
            try {
                SecurityManager security = System.getSecurityManager();
                if (security != null)
                    security.checkListen(epoint.getPort());
                getImpl().bind(epoint.getAddress(), epoint.getPort());
                getImpl().listen(backlog);
                bound = true;
            } catch(SecurityException e) {
                bound = false;
                throw e;
            } catch(IOException e) {
                bound = false;
                throw e;
            }
        }
    这里调用SocketImpl的bind方法: getImpl().bind(epoint.getAddress(), epoint.getPort());
    SocketImpl的抽象实现类:AbstractPlainSocketImpl:
    protected synchronized void bind(InetAddress address, int lport)
            throws IOException
        {
           synchronized (fdLock) {
                if (!closePending && (socket == null || !socket.isBound())) {
                    NetHooks.beforeTcpBind(fd, address, lport);
                }
            }
            socketBind(address, lport);
            if (socket != null)
                socket.setBound();
            if (serverSocket != null)
                serverSocket.setBound();
        }
    
    
    • 通过accept方法来接收socket连接:
    public Socket accept() throws IOException {
            if (isClosed())
                throw new SocketException("Socket is closed");
            if (!isBound())
                throw new SocketException("Socket is not bound yet");
            Socket s = new Socket((SocketImpl) null);
            implAccept(s);
            return s;
        }
    protected final void implAccept(Socket s) throws IOException {
            SocketImpl si = null;
            try {
                if (s.impl == null)
                  s.setImpl();
                else {
                    s.impl.reset();
                }
                si = s.impl;
                s.impl = null;
                si.address = new InetAddress();
                si.fd = new FileDescriptor();
                //接收socket
                getImpl().accept(si);
    
                SecurityManager security = System.getSecurityManager();
                if (security != null) {
                    security.checkAccept(si.getInetAddress().getHostAddress(),
                                         si.getPort());
                }
            } catch (IOException e) {
                if (si != null)
                    si.reset();
                s.impl = si;
                throw e;
            } catch (SecurityException e) {
                if (si != null)
                    si.reset();
                s.impl = si;
                throw e;
            }
            s.impl = si;
            s.postAccept();
        }
    这里getImpl().accept(si)调用了默认抽象实现类AbstractPlainSocketImpl的accept:
    protected void accept(SocketImpl s) throws IOException {
            //先获取fd
            acquireFD();
            try {
                //该方法还是调用了本地方法:PlainSocketImpl:native void socketAccept(SocketImpl s) throws IOException;
                socketAccept(s);
            } finally {
                releaseFD();
            }
        }
    //添加synchronized锁,保证多线程情况的数据一致性
    FileDescriptor acquireFD() {
            synchronized (fdLock) {
                fdUseCount++;
                return fd;
            }
        }
    
    • 需要特别注意的AbstractPlainSocketImpl有两个参数:fdLock对象是为了给每个操作socket的方法,添加同步锁synchronized锁,fdUseCount用来记录当前服务器端socket保持连接的线程数.因为在关闭ServerSocket的时候,需要将socket中的数据清空之后,才能关闭.
     /* number of threads using the FileDescriptor */
        protected int fdUseCount = 0;
    
        /* lock when increment/decrementing fdUseCount */
        protected final Object fdLock = new Object();
    
    protected void close() throws IOException {
            synchronized(fdLock) {
                if (fd != null) {
                    if (!stream) {
                        ResourceManager.afterUdpClose();
                    }
                    if (fdUseCount == 0) {
                        if (closePending) {
                            return;
                        }
                        closePending = true;
                        /*
                         * We close the FileDescriptor in two-steps - first the
                         * "pre-close" which closes the socket but doesn't
                         * release the underlying file descriptor. This operation
                         * may be lengthy due to untransmitted data and a long
                         * linger interval. Once the pre-close is done we do the
                         * actual socket to release the fd.
                         */
                        try {
                            socketPreClose();
                        } finally {
                            socketClose();
                        }
                        fd = null;
                        return;
                    } else {
                        /*
                         * If a thread has acquired the fd and a close
                         * isn't pending then use a deferred close.
                         * Also decrement fdUseCount to signal the last
                         * thread that releases the fd to close it.
                         */
                        if (!closePending) {
                            closePending = true;
                            fdUseCount--;
                            socketPreClose();
                        }
                    }
                }
            }
        }
    

    相关文章

      网友评论

          本文标题:JDK源码-Socket套接字系列

          本文链接:https://www.haomeiwen.com/subject/aignsxtx.html