From BIO to NIO series —— BIO source code interpretation

知秋z發表於2019-04-09

This article will explain how BIO evolve into NIO, and this will be the basic knowledge of Reactor Netty library.

ps: Chinese version of this series:juejin.im/post/5c6531…

Introduction

We can use a demo to demonstrate how to use BIO

//Server
public class BIOServer {
    public void initBIOServer(int port)
    {
        ServerSocket serverSocket = null;//Server Socket
        Socket socket = null;//Client socket
        BufferedReader reader = null;
        String inputContent;
        int count = 0;
        try {
            serverSocket = new ServerSocket(port);
            System.out.println(stringNowTime() + ": serverSocket 		   started");
            while(true)
            {
                socket = serverSocket.accept();
                System.out.println(stringNowTime() + ": id " + socket.hashCode()+ "'s Clientsocket connected");
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while ((inputContent = reader.readLine()) != null) {
                    System.out.println("Received id is" + socket.hashCode() + "  "+inputContent);
                    count++;
                }
                System.out.println("id is" + socket.hashCode()+ " 's Clientsocket "+stringNowTime()+"over");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                reader.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String stringNowTime()
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(new Date());
    }

    public static void main(String[] args) {
        BIOServer server = new BIOServer();
        server.initBIOServer(8888);

    }
}
// Client
public class BIOClient {

    public void initBIOClient(String host, int port) {
        BufferedReader reader = null;
        BufferedWriter writer = null;
        Socket socket = null;
        String inputContent;
        int count = 0;
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            socket = new Socket(host, port);
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            System.out.println("clientSocket started: " + stringNowTime());
            while (((inputContent = reader.readLine()) != null) && count < 2) {
                inputContent = stringNowTime() + ": 第" + count + "條訊息: " + inputContent + "\n";
                writer.write(inputContent);//Send message to Server Client
                writer.flush();
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
                reader.close();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String stringNowTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(new Date());
    }

    public static void main(String[] args) {
        BIOClient client = new BIOClient();
        client.initBIOClient("127.0.0.1", 8888);
    }

}
複製程式碼

From above demo, we can see they have some operations based on Server which are serverSocket = new ServerSocket(port);serverSocket.accept() , and Socket socket = new Socket(host, port); which based on Client. Because both of them have operations of reading and writing in Socket which we use Stream to achieve this and it has to use Buffer.

Bind in ServerSocket

Thus, to learn about the design of bind, we can check it from its related source code. At first, we can check ServerSocket.java. From the constructor method of it, it will invoke the bind method in final.

//java.net.ServerSocket#ServerSocket(int)
public ServerSocket(int port) throws IOException {
    this(port, 50, null);
}
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;
    }
}
複製程式碼

From the BIO demo and source code in above, the parameter endpoint won't be null. At the same time, it will belong to InetSocketAddress type. The size of backlog is 50. Thus, what we focus is on methods getImpl().bind(epoint.getAddress(), epoint.getPort()); :

public void bind(SocketAddress endpoint, int backlog) throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!oldImpl && isBound())
        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());
        // What we focus
        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;
    }
}
複製程式碼

From the implementation of the constructor, we can see setImpl(), we got default value of factory is null. So we need to focus on SocksSocketImpl and create a object and set ServerSocket. This can be checked in java.net.SocketImpl. So getImplwill be clear,then we can focus on the Socket. Because different Operation Systems use different implementations for Sockets. Now we will introduce the implementation in Windows.

/**
* The factory for all server sockets.
*/
private static SocketImplFactory factory = null;
private void setImpl() {
    if (factory != null) {
        impl = factory.createSocketImpl();
        checkOldImpl();
    } else {
        // No need to do a checkOldImpl() here, we know it's an up to date
        // SocketImpl!
        impl = new SocksSocketImpl();
    }
    if (impl != null)
        impl.setServerSocket(this);
}
/**
* Get the {@code SocketImpl} attached to this socket, creating
* it if necessary.
*
* @return  the {@code SocketImpl} attached to that ServerSocket.
* @throws SocketException if creation fails.
* @since 1.4
*/
SocketImpl getImpl() throws SocketException {
    if (!created)
        createImpl();
    return impl;
}
/**
* Creates the socket implementation.
*
* @throws IOException if creation fails
* @since 1.4
*/
void createImpl() throws SocketException {
    if (impl == null)
        setImpl();
    try {
        impl.create(true);
        created = true;
    } catch (IOException e) {
        throw new SocketException(e.getMessage());
    }
}
複製程式碼

Then we check the implementation of bind() in SocksSocketImpl, it will invoke local method bind0.

//java.net.AbstractPlainSocketImpl#bind
/**
* Binds the socket to the specified address of the specified local port.
* @param address the address
* @param lport the port
*/
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();
}

//java.net.PlainSocketImpl#socketBind
@Override
void socketBind(InetAddress address, int port) throws IOException {
    int nativefd = checkAndReturnNativeFD();

    if (address == null)
        throw new NullPointerException("inet address argument is null.");

    if (preferIPv4Stack && !(address instanceof Inet4Address))
        throw new SocketException("Protocol family not supported");

    bind0(nativefd, address, port, useExclusiveBind);
    if (port == 0) {
        localport = localPort0(nativefd);
    } else {
        localport = port;
    }

    this.address = address;
}
//java.net.PlainSocketImpl#bind0
static native void bind0(int fd, InetAddress localAddress, int localport,
                             boolean exclBind)
        throws IOException;
複製程式碼

We also need to know multi-threads only can achieve the result of logic process for the operations. For receiving data, it still need to receive them one by one. So it will be blocked when read and write in the Demo at first. From this, we can know multi-threads can't solve this problem. Now we need to know why it will happen in accept. The meaning of accept is to ask System whether it has new Socket information sent from Port. And this operation is based on the level of Operation System. If System didn't find any Socket connection from any port, then it will await. Blocking will happen. This is based on the synchronized IO on Operation System level.

Accept in ServerSocket

Then we can see ServerSocket.accept method:

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;
}
複製程式碼

At the beginning, it just did some determination on its status. Then Socket will be new and alter implAccept method. So we check it:

/**
* Subclasses of ServerSocket use this method to override accept()
* to return their own subclass of socket.  So a FooServerSocket
* will typically hand this method an <i>empty</i> FooSocket.  On
* return from implAccept the FooSocket will be connected to a client.
*
* @param s the Socket
* @throws java.nio.channels.IllegalBlockingModeException
*         if this socket has an associated channel,
*         and the channel is in non-blocking mode
* @throws IOException if an I/O error occurs when waiting
* for a connection.
* @since   1.1
* @revised 1.4
* @spec JSR-51
*/
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();
    getImpl().accept(si);  // <1>
    SocketCleanable.register(si.fd);   // raw fd has been set

    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();
}
複製程式碼

After accept at getImpl, accept can be found in AbstractPlainSocketImpl.

//java.net.AbstractPlainSocketImpl#accept
/**
* Accepts connections.
* @param s the connection
*/
protected void accept(SocketImpl s) throws IOException {
			acquireFD();
			try {
    			socketAccept(s);
			} finally {
    			releaseFD();
			}
		}
複製程式碼

From above code, it invoked socketAccept(). Since there're difference in implementation of Socket. Therefore,socketAccept() was used in PlainSocketImpl in Windows.

// java.net.PlainSocketImpl#socketAccept
@Override
void socketAccept(SocketImpl s) throws IOException {
    int nativefd = checkAndReturnNativeFD();

    if (s == null)
        throw new NullPointerException("socket is null");

    int newfd = -1;
    InetSocketAddress[] isaa = new InetSocketAddress[1];
    if (timeout <= 0) {  //<1>
        newfd = accept0(nativefd, isaa); // <2>
    } else {
        configureBlocking(nativefd, false);
        try {
            waitForNewConnection(nativefd, timeout);
            newfd = accept0(nativefd, isaa);  // <3>
            if (newfd != -1) {
                configureBlocking(newfd, true);
            }
        } finally {
            configureBlocking(nativefd, true);
        }
    } // <4>
    /* Update (SocketImpl)s' fd */
    fdAccess.set(s.fd, newfd);
    /* Update socketImpls remote port, address and localport */
    InetSocketAddress isa = isaa[0];
    s.port = isa.getPort();
    s.address = isa.getAddress();
    s.localport = localport;
    if (preferIPv4Stack && !(s.address instanceof Inet4Address))
        throw new SocketException("Protocol family not supported");
}
//java.net.PlainSocketImpl#accept0
 static native int accept0(int fd, InetSocketAddress[] isaa) throws IOException;
複製程式碼

We should place emphasis on the section from <1> to <4> . For <2> and <3>, accept0() was executed and this is a native method which means it will interact with Operation System to listen the specific port whether it has any connections sent by clients. Because of this, it will make the server become blocked. So it means it sill also make the blocking true on programming level. But this can be avoided, we can refactor it to non-blocking mode. But we can't change blocking status caused by accept0() temporarily. so in another way, we can know that block in System level is caused by Synchronization. In order to solve this, we can set a Timeout value for the connection to optimize the accept() method. At <1>, we can determine the value of timeout is less than or equal to 0, accept0() will be directly invoked. Then it will always be blocked mode. But when we set timeout larger than 0, program will return until time we set is out. By the way, if newfd is -1, it means accept() didn't find any data received from underlying. So where can we set timeout value? So just check the following code, we can use setSoTimeOut via ServerSocket:

/**
* Enable/disable {@link SocketOptions#SO_TIMEOUT SO_TIMEOUT} with the
* specified timeout, in milliseconds.  With this option set to a non-zero
* timeout, a call to accept() for this ServerSocket
* will block for only this amount of time.  If the timeout expires,
* a <B>java.net.SocketTimeoutException</B> is raised, though the
* ServerSocket is still valid.  The option <B>must</B> be enabled
* prior to entering the blocking operation to have effect.  The
* timeout must be {@code > 0}.
* A timeout of zero is interpreted as an infinite timeout.
* @param timeout the specified timeout, in milliseconds
* @exception SocketException if there is an error in
* the underlying protocol, such as a TCP error.
* @since   1.1
* @see #getSoTimeout()
*/
public synchronized void setSoTimeout(int timeout) throws SocketException {
if (isClosed())
    throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_TIMEOUT, timeout);
}
複製程式碼

After that, we can check AbstractPlainSocketImpl and see setOption in getImpl, timeout value was set.

//java.net.AbstractPlainSocketImpl#setOption
public void setOption(int opt, Object val) throws SocketException {
    if (isClosedOrPending()) {
        throw new SocketException("Socket Closed");
    }
    boolean on = true;
    switch (opt) {
        /* check type safety b4 going native.  These should never
            * fail, since only java.Socket* has access to
            * PlainSocketImpl.setOption().
            */
    case SO_LINGER:
        if (val == null || (!(val instanceof Integer) && !(val instanceof Boolean)))
            throw new SocketException("Bad parameter for option");
        if (val instanceof Boolean) {
            /* true only if disabling - enabling should be Integer */
            on = false;
        }
        break;
    case SO_TIMEOUT: //<1>
        if (val == null || (!(val instanceof Integer)))
            throw new SocketException("Bad parameter for SO_TIMEOUT");
        int tmp = ((Integer) val).intValue();
        if (tmp < 0)
            throw new IllegalArgumentException("timeout < 0");
        timeout = tmp;
        break;
    case IP_TOS:
            if (val == null || !(val instanceof Integer)) {
                throw new SocketException("bad argument for IP_TOS");
            }
            trafficClass = ((Integer)val).intValue();
            break;
    case SO_BINDADDR:
        throw new SocketException("Cannot re-bind socket");
    case TCP_NODELAY:
        if (val == null || !(val instanceof Boolean))
            throw new SocketException("bad parameter for TCP_NODELAY");
        on = ((Boolean)val).booleanValue();
        break;
    case SO_SNDBUF:
    case SO_RCVBUF:
        if (val == null || !(val instanceof Integer) ||
            !(((Integer)val).intValue() > 0)) {
            throw new SocketException("bad parameter for SO_SNDBUF " +
                                        "or SO_RCVBUF");
        }
        break;
    case SO_KEEPALIVE:
        if (val == null || !(val instanceof Boolean))
            throw new SocketException("bad parameter for SO_KEEPALIVE");
        on = ((Boolean)val).booleanValue();
        break;
    case SO_OOBINLINE:
        if (val == null || !(val instanceof Boolean))
            throw new SocketException("bad parameter for SO_OOBINLINE");
        on = ((Boolean)val).booleanValue();
        break;
    case SO_REUSEADDR:
        if (val == null || !(val instanceof Boolean))
            throw new SocketException("bad parameter for SO_REUSEADDR");
        on = ((Boolean)val).booleanValue();
        break;
    case SO_REUSEPORT:
        if (val == null || !(val instanceof Boolean))
            throw new SocketException("bad parameter for SO_REUSEPORT");
        if (!supportedOptions().contains(StandardSocketOptions.SO_REUSEPORT))
            throw new UnsupportedOperationException("unsupported option");
        on = ((Boolean)val).booleanValue();
        break;
    default:
        throw new SocketException("unrecognized TCP option: " + opt);
    }
    socketSetOption(opt, on, val);
}
複製程式碼

Since this code is too long, we just need focus on the code related to timeout at <1>. This code just means it afferents timeout in setOption to global variable timeout in AbstractPlainSocketImpl.

Therefore, we achieved accept() has become non-blocking. However, for read(), it is still blocked mode. So we also need to change it to non-blocking by refactoring codes.

Demo for implementation of non-blocking in accept()

Before we do that, we need to know about the meanings of Synchronization, Asynchronization and Blocking and non-blocking.

For Synchronization and Asynchronization, they belong to System level. It means when system receive IOrequest from Program, System won't respond until IO resources are ready. However, for Asynchronization, it will return a signal to Server, this will be used to determine how to deal with the content when IO resources are ready.

Blocking and Non-Blcoking is Programming Level. When program requests IO operation for System. It won't stop Blocking until the IO resources are ready. For Non-Blocking, program will still run, but it will check the status of IO resources.

For BIO, the most common we known is Synchronization withBlocking mode. Synchronization means System will wait until IO resources are ok. Blocking is Program will wait until IO resources are ready. In detail, Blocking in Program is caused by accept() and read(). So we can refactor them to non-blcoking. But we can't change this on System level.

For NIO, it is Asynchronization with Non-Blocking mode. It is used to optimize accept() and read(). In order to solve this, Channel and Buffer are imported. So just check the following demo for how to solve the blocking problem for accept():

public class BIOProNotB {

    public void initBIOServer(int port) {
        ServerSocket serverSocket = null;//服務端Socket
        Socket socket = null;//客戶端socket
        ExecutorService threadPool = Executors.newCachedThreadPool();
        ClientSocketThread thread = null;
        try {
            serverSocket = new ServerSocket(port);
            serverSocket.setSoTimeout(1000);
            System.out.println(stringNowTime() + ": serverSocket started");
            while (true) {
                try {
                    socket = serverSocket.accept();
                } catch (SocketTimeoutException e) {
                    //執行到這裡表示本次accept是沒有收到任何資料的,服務端的主執行緒在這裡可以做一些其他事情
                    System.out.println("now time is: " + stringNowTime());
                    continue;
                }
                System.out.println(stringNowTime() + ": id為" + socket.hashCode() + "的Clientsocket connected");
                thread = new ClientSocketThread(socket);
                threadPool.execute(thread);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String stringNowTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return format.format(new Date());
    }

    class ClientSocketThread extends Thread {
        public Socket socket;

        public ClientSocketThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            BufferedReader reader = null;
            String inputContent;
            int count = 0;
            try {
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while ((inputContent = reader.readLine()) != null) {
                    System.out.println("Received idis" + socket.hashCode() + "  " + inputContent);
                    count++;
                }
                System.out.println("id is" + socket.hashCode() + "'s Clientsocket " + stringNowTime() + "read finished");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    reader.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        BIOProNotB server = new BIOProNotB();
        server.initBIOServer(8888);
    }
}
複製程式碼

We set a timeout value for our ServerSocket, when we invoke accept(), it will be awake every 1 second instead of waiting. It changed the disadvantage of Blocking which it will return data/information, when it received any connections from Clients. Now, we can check the result:

2019-01-02 17:28:43:362: serverSocket started
now time is: 2019-01-02 17:28:44:363
now time is: 2019-01-02 17:28:45:363
now time is: 2019-01-02 17:28:46:363
now time is: 2019-01-02 17:28:47:363
now time is: 2019-01-02 17:28:48:363
now time is: 2019-01-02 17:28:49:363
now time is: 2019-01-02 17:28:50:363
now time is: 2019-01-02 17:28:51:364
now time is: 2019-01-02 17:28:52:365
now time is: 2019-01-02 17:28:53:365
now time is: 2019-01-02 17:28:54:365
now time is: 2019-01-02 17:28:55:365
now time is: 2019-01-02 17:28:56:365 // <1>
2019-01-02 17:28:56:911: id is 1308927845's Clientsocket connected
now time is: 2019-01-02 17:28:57:913 // <2>
now time is: 2019-01-02 17:28:58:913

複製程式碼

From the above result, we can see when there's no connection from Clients. It will print results ofSystem.out.println("now time is: " + stringNowTime()); Another thing is also worth of noticing. Please see the result at <1> and <2>. We can find the time value at <2> isn't 17:28:57:365. This means if accept() return message normally, program won't catch the exception.

Demo for implementation of non-blocking in read()

Now, we have finished the implementation of non-blocking inaccept(). Then we need to think the way for non-blocking implementation in read(). We can use similar way to do this. When we use read() method, it will invoke java.net.AbstractPlainSocketImpl#getInputStream:

/**
* Gets an InputStream for this socket.
*/
protected synchronized InputStream getInputStream() throws IOException {
synchronized (fdLock) {
    if (isClosedOrPending())
        throw new IOException("Socket Closed");
    if (shut_rd)
        throw new IOException("Socket input is shutdown");
    if (socketInputStream == null)
        socketInputStream = new SocketInputStream(this);
}
return socketInputStream;
}
複製程式碼

In this, it created a new SocketInputStream, and it will put AbstratPlainSocketImpl into this. So when we read data, we will invoke the following method:

public int read(byte b[], int off, int length) throws IOException {
    return read(b, off, length, impl.getTimeout());
}

int read(byte b[], int off, int length, int timeout) throws IOException {
    int n;

    // EOF already encountered
    if (eof) {
        return -1;
    }

    // connection reset
    if (impl.isConnectionReset()) {
        throw new SocketException("Connection reset");
    }

    // bounds check
    if (length <= 0 || off < 0 || length > b.length - off) {
        if (length == 0) {
            return 0;
        }
        throw new ArrayIndexOutOfBoundsException("length == " + length
                + " off == " + off + " buffer length == " + b.length);
    }

    // acquire file descriptor and do the read
    FileDescriptor fd = impl.acquireFD();
    try {
        n = socketRead(fd, b, off, length, timeout);
        if (n > 0) {
            return n;
        }
    } catch (ConnectionResetException rstExc) {
        impl.setConnectionReset();
    } finally {
        impl.releaseFD();
    }

    /*
        * If we get here we are at EOF, the socket has been closed,
        * or the connection has been reset.
        */
    if (impl.isClosedOrPending()) {
        throw new SocketException("Socket closed");
    }
    if (impl.isConnectionReset()) {
        throw new SocketException("Connection reset");
    }
    eof = true;
    return -1;
}
private int socketRead(FileDescriptor fd,
                           byte b[], int off, int len,
                           int timeout)
        throws IOException {
        return socketRead0(fd, b, off, len, timeout);
}
複製程式碼

For socketRead(), it also set timeout. At the same time, timeout will also be put into SocketInputStream when we new it and it's controlled by AbstractPlainSocketImpl. So we just need to set serverSocket.setSoTimeout(1000) easily. We need to refactor the code in Server Side. Timeout was set twice. But they are different The first time is on ServerSocket. The second is on Socket which Clients connected.

public class BIOProNotBR {

    public void initBIOServer(int port) {
        ServerSocket serverSocket = null;//Server Socket
        Socket socket = null;//Client socket
        ExecutorService threadPool = Executors.newCachedThreadPool();
        ClientSocketThread thread = null;
        try {
            serverSocket = new ServerSocket(port);
            serverSocket.setSoTimeout(1000);
            System.out.println(stringNowTime() + ": serverSocket started");
            while (true) {
                try {
                    socket = serverSocket.accept();
                } catch (SocketTimeoutException e) {
                    //In there, accept() didn't receive any data, so the main thread in Server can do other things.
                    System.out.println("now time is: " + stringNowTime());
                    continue;
                }
                System.out.println(stringNowTime() + ": id is" + socket.hashCode() + " 's Clientsocket connected");
                thread = new ClientSocketThread(socket);
                threadPool.execute(thread);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String stringNowTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        return format.format(new Date());
    }

    class ClientSocketThread extends Thread {
        public Socket socket;

        public ClientSocketThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            BufferedReader reader = null;
            String inputContent;
            int count = 0;
            try {
                socket.setSoTimeout(1000);
            } catch (SocketException e1) {
                e1.printStackTrace();
            }
            try {
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    try {
                        while ((inputContent = reader.readLine()) != null) {
                            System.out.println("Received id is" + socket.hashCode() + "  " + inputContent);
                            count++;
                        }
                    } catch (Exception e) {
                        //In there, read() didn't get any data, thread can do other things.
                        System.out.println("Not read data: " + stringNowTime());
                        continue;
                    }
                    //It read data, and server can return the respond to Clients.
                    System.out.println("id is" + socket.hashCode() + " 's Clientsocket " + stringNowTime() + "read finish");
                    sleep(1000);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                try {
                    reader.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        BIOProNotBR server = new BIOProNotBR();
        server.initBIOServer(8888);
    }
}
複製程式碼

The result is:

2019-01-02 17:59:03:713: serverSocket started
now time is: 2019-01-02 17:59:04:714
now time is: 2019-01-02 17:59:05:714
now time is: 2019-01-02 17:59:06:714
2019-01-02 17:59:06:932: id is 1810132623's Clientsocket connected
now time is: 2019-01-02 17:59:07:934
Not read data: 2019-01-02 17:59:07:935
now time is: 2019-01-02 17:59:08:934
Not read data: 2019-01-02 17:59:08:935
now time is: 2019-01-02 17:59:09:935
Not read data: 2019-01-02 17:59:09:936
Received id is 1810132623  2019-01-02 17:59:09: The 0th message: ccc // <1>
now time is: 2019-01-02 17:59:10:935
Not read data: 2019-01-02 17:59:10:981 // <2>
Received id is 1810132623  2019-01-02 17:59:11: The 1st message: bbb
now time is: 2019-01-02 17:59:11:935
Not read data: 2019-01-02 17:59:12:470
now time is: 2019-01-02 17:59:12:935
id is 1810132623's Clientsocket 2019-01-02 17:59:13:191 read finish
now time is: 2019-01-02 17:59:13:935
id is 1810132623's Clientsocket 2019-01-02 17:59:14:192 read finish

複製程式碼

Not read data part solve our Blocking problem in read() partially, it can allocate a thread to deal with their own business for each client. Based on this point, it almost solved Blocking problem. We can consider it as origin of NIO. Although it is a advantage for us to solve this problem, it also create a new problem when we face too many clients. It'll waste our server performance when switch in different threads. More over, we can use threadPool to solve this, but it will make BlockingQueue become larger and larger when the number of Clients increase. Now, NIO can help us solve this problem very elegantly. It won't allocate aThread for every client. It will only has one Thread at Server, then it will create a new Channel for each client.

Think on key points of Accept()

For accept(), we can see how it designed in Linux:

#include <sys/types.h>

#include <sys/socket.h>

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
複製程式碼

System will mainly invoke accept() based on Socket such as SOCK_STREAM and SOCK_SEQPACKET. It will extract the first connection request to create a new Socket and return the related File Descriptor. If new Socket isn't listened, the old Socket won't be affect by System. (PS: new Socket is ready to send() and receive().

Parameter:

sockfd is created by System based on socket() method, then it will bind() tp a local address(Generally it is a server socket). After that, it will be always listened.

addr is a pointer which points to the struct sockaddr, this is filled by client address. The return format will be determined by TCP or UDP. If addr is NULL, it will be invalid address. In this situation, addrlen also won't use, the value should be NULL.

For addrlen, The invoked function must initialize the value which was pointed by addr. The return value will contain the equivalent address(server address) real value.

(PS: addrlen is a local int variable, it was set as sizeof(struct sockaddr_in)

If there's no any awaiting connections in queue and sockets weren't marked as Non-Blocking. accept() will block the function until the connection was received. In another way, if socket is marked as Non-Blocking, there's no connections in queue, accept() will return error EAGAIN or EWOULDBLOCK.

(PS: In general, accept() is a blocking function. When socket invokes accept(), it will check the connection at receive_buf. If it has connections, it will copy data and delete received data packages,then create new Socket and connect with Client's address. Otherwise, it will be blocked and waiting.)

To get the notification from new connection in Socket, we can use select() or poll(). We are attempt to build a new connection, System will send a readable event, then invoke accept()to get socket for the connection. Another method is when new connection is received in socket, socket will send SIGIO.

When it succeeds, then return non-negative integer. This number is descriptor for receiving Socket. When error happens, return -1 and set global variable errno.

Therefore, we will new a Socket in java.net.ServerSocket#accept. It will help us get new Socket File Descriptor and set it in new Socket. It is obviously in java.net.PlainSocketImpl#socketAccept. If anyone have interest in this, just check source code.

相關文章