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 getImpl
will 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 IO
request 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.