Build Your Own Netty — Start from BIO

Netty Project

In this article, we will implement an Echo Server by the Java Blocking I/O library.

Echo Server with BIO

1. listen on port 8080
+-------------+
| |
| |
| |
+---------------+ | +-------+--------+
| | | | |
| Echo Client | +---->+ Echo Server |
| | | |
+-------+-------+ +-------+--------+
| |
| 2. connect |
+----------------------------------------->+
| |
| +----+
| | | 3. connected
| +<---+
| 4. send "hello" |
+----------------------------------------->+
| |
| 5. echo "hello" |
+<-----------------------------------------+
| |
| . |
| . |
| . |
| |
| 6. send "BYE" |
+----------------------------------------->+
| |
| 7. echo "BYE" |
+<-----------------------------------------+
| |
| +----+
| | | 8. close
| +<---+
|
|
|
|

The flow chart above shows how Echo Server and Echo Client work together, quite simple and straightforward, and there are two steps worth noting:

  1. In the step 1 (listen on port 8080), the Echo Server will block until there is connection coming in.
  2. In the step 8 (close), after closing the connection, the Echo Client will terminate while the Echo Server will go back to step 1, listening for another incoming connection.

So let’s see how to implement it with Java code.

public class EchoServer {
public void start() throws IOException {
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
while (true) {
final Socket socket = serverSocket.accept();
new SocketHandler(socket).run();
}
}
}
  1. To listen on a specified port for incoming connection, we will firstly need to create a java.net.ServerSocket, and bind it to the specified port.
    Note that this step only tells the operating system which network card and port our following operations will target to. Because there may be more than one
    network cards existed on your machine.
  2. Then we call java.net.ServerSocket.accept to wait for and accept the incoming connection. As stated before, this operation will block until there are connections coming in.
    And once there are connections accepted, this method will return the accepted connection, represented by java.net.Socket.
  3. new SocketHandler(socket) creates a handler that carefully processes the client connection, reading messages from the client and writing the echo messages are done in this class.
  4. In order to accept another connection after the previous connection is closed (in step 8), we put the accept method into the while (true) loop, preventing the Echo Server from exiting too.

That’s all! The code of the Echo Server is as easy as what it does.

Problems with BIO

If you check out the complete code of Echo Server and Echo Client, and run them respectively, you’ll find that they work well, is it perfect?
Of course not, as a server, it’s normal that there are many clients wanting to send requests and expecting to get response. Here comes the problems:

  1. The Echo Server must handle (new SocketHandler(socket).run()) and wait until it finishes before serving the next incoming connection.
  2. The Echo Server cannot do anything but wait if there is no connection (consider it’s midnight and nobody is requesting for services), it’s a waste of resources (CPU, Memory, etc.).

If you have multi-thread programming experience (likely), it is easy to come out a solution for the first problem, make the handler run in another thread:

public class EchoServer {
private final Executor executor = Executors.newCachedThreadPool();

public void start() throws IOException {
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
while (true) {
final Socket socket = serverSocket.accept();
executor.execute(new SocketHandler(socket));
}
}
}

Brilliant! executor.execute(new SocketHandler(socket)) make the handler run in another thread and the Echo Server goes back to wait and accept another connection.

There are other issues when tens of thousands of clients try to connect to your server:

  1. Tens of thousands of threads must be created because we use one thread per client to handle it, creating such amount of threads is memory-expensive and may run out of the memory.
  2. For the sake of justice, the OS will give opportunity for each thread to run, and switching thread context is another expensive operation, it may be dramatically more expensive than handling the client itself.
  3. The Echo Server still cannot do anything but wait if there is no connection coming in.

Summarize

In this article, we implemented an Echo Server by using the BIO (Blocking I/O) library, and discussed the shortages of BIO, one of which can be solved by leveraging the multi-thread framework, yet bringing other issues such as switching thread context. The problem that when waiting for incoming connections, the server thread is just blocked and cannot do anything is still not solved.

In the next article of this series, we will try to solve the problems above by using the NIO (New I/O or Non-blocking I/O) library, which is more powerful and used in Netty.

Apache SkyWalking Core Maintainer; Open-source enthusiast. GitHub@kezhenxu94