JEP 353: Reimplement the Legacy Socket API(JDK 13)

1. 簡介

JDK中的java.net.Socket 和 java.net.ServerSocket實現非常古老,可以上溯到JDK 1.0,該實現是混合了JAVA和C代碼,非常難於維護和調試。另外,該實現使用線程堆棧作爲I/O緩衝區,這帶來一系列的移植性和可用性問題。

因此JDK 13中實現了新的java.net.SocketImpl的實現類sun.nio.ch.NioSocketImpl,以替代原實現。

2. 分析

2.1 類圖

Socket.png

2.2 主要方法

2.2.1 create

創建SocketImpl實例時,判斷是否顯式開啓了USE_PLAINSOCKETIMPL,如是則使用舊的PlainSocketImpl,否則使用NioSocketImpl。

    /**
     * Creates an instance of platform's SocketImpl
     */
    @SuppressWarnings("unchecked")
    static <S extends SocketImpl & PlatformSocketImpl> S createPlatformSocketImpl(boolean server) {
        if (USE_PLAINSOCKETIMPL) {
            return (S) new PlainSocketImpl(server);
        } else {
            return (S) new NioSocketImpl(server);
        }
    }

2.2.1 bind

NioSocketImpl

    @Override
    protected void bind(InetAddress host, int port) throws IOException {
        synchronized (stateLock) {
            ensureOpen();
            if (localport != 0)
                throw new SocketException("Already bound");
            NetHooks.beforeTcpBind(fd, host, port);
            Net.bind(fd, host, port);
            // set the address field to the given host address to keep
            // compatibility with PlainSocketImpl. When binding to 0.0.0.0
            // then the actual local address will be ::0 when IPv6 is enabled.
            address = host;
            localport = Net.localAddress(fd).getPort();
        }
    }

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();
    }
  • NioSocketImpl使用了新的對象鎖stateLock替代舊的fdLock,使用時機更加明確。
  • NIO相關的底層操作均被遷移到sun.nio.ch.Net類中的native方法,而非之前散落在各個類的native方法。
  • NioSocketImpl與NIO共享相同的JDK內部基礎類sun.nio.ch.Net,不再需要自己的native代碼,提高了代碼的可維護性。

2.2.2 listen

NioSocketImpl

    @Override
    protected void listen(int backlog) throws IOException {
        synchronized (stateLock) {
            ensureOpen();
            if (localport == 0)
                throw new SocketException("Not bound");
            Net.listen(fd, backlog < 1 ? 50 : backlog);
        }
    }

AbstractPlainSocketImpl

    protected synchronized void listen(int count) throws IOException {
        socketListen(count);
    }
  • NioSocketImpl使用對象鎖stateLock進行併發控制,而AbstractPlainSocketImpl則在native代碼中進行併發控制,難以調試
  • NioSocketImpl增加了對於邊界條件的判斷

2.2.3 accept

NioSocketImpl

/**
     * Accepts a new connection so that the given SocketImpl is connected to
     * the peer. The SocketImpl must be a newly created NioSocketImpl.
     */
    @Override
    protected void accept(SocketImpl si) throws IOException {
        NioSocketImpl nsi = (NioSocketImpl) si;
        if (nsi.state != ST_NEW)
            throw new SocketException("Not a newly created SocketImpl");

        FileDescriptor newfd = new FileDescriptor();
        InetSocketAddress[] isaa = new InetSocketAddress[1];

        // acquire the lock, adjusting the timeout for cases where several
        // threads are accepting connections and there is a timeout set
        ReentrantLock acceptLock = readLock;
        int timeout = this.timeout;
        long remainingNanos = 0;
        if (timeout > 0) {
            remainingNanos = tryLock(acceptLock, timeout, MILLISECONDS);
            if (remainingNanos <= 0) {
                assert !acceptLock.isHeldByCurrentThread();
                throw new SocketTimeoutException("Accept timed out");
            }
        } else {
            acceptLock.lock();
        }

        // accept a connection
        try {
            int n = 0;
            FileDescriptor fd = beginAccept();
            try {
                if (remainingNanos > 0) {
                    // accept with timeout
                    configureNonBlocking(fd);
                    n = timedAccept(fd, newfd, isaa, remainingNanos);
                } else {
                    // accept, no timeout
                    n = Net.accept(fd, newfd, isaa);
                    while (IOStatus.okayToRetry(n) && isOpen()) {
                        park(fd, Net.POLLIN);
                        n = Net.accept(fd, newfd, isaa);
                    }
                }
            } finally {
                endAccept(n > 0);
                assert IOStatus.check(n);
            }
        } finally {
            acceptLock.unlock();
        }

        // get local address and configure accepted socket to blocking mode
        InetSocketAddress localAddress;
        try {
            localAddress = Net.localAddress(newfd);
            IOUtil.configureBlocking(newfd, true);
        } catch (IOException ioe) {
            nd.close(newfd);
            throw ioe;
        }

        // set the fields
        synchronized (nsi.stateLock) {
            nsi.fd = newfd;
            nsi.stream = true;
            nsi.closer = FileDescriptorCloser.create(nsi);
            nsi.localport = localAddress.getPort();
            nsi.address = isaa[0].getAddress();
            nsi.port = isaa[0].getPort();
            nsi.state = ST_CONNECTED;
        }
    }

AbstractPlainSocketImpl

    /**
     * Accepts connections.
     * @param s the connection
     */
    protected void accept(SocketImpl s) throws IOException {
        acquireFD();
        try {
            socketAccept(s);
        } finally {
            releaseFD();
        }
    }
  • NioSocketImpl使用ReentrantLock進行併發控制,而AbstractPlainSocketImpl在native代碼中進行併發控制。
  • NioSocketImpl將大量代碼邏輯遷移到Java代碼中,便於維護和調試。

3. 總結

綜上所述,新的NioSocketImpl類將大量native代碼遷移到Java中,並使用Java內置鎖和顯式鎖進行併發控制,提高了代碼的可維護性。

4. 相關鏈接

OpenJDK 13 源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章