Java NIO

Purpose

Netty is a library that abstracts Java NIO, so Java NIO is often discussed. This post provides a brief explanation.

Features

NonBlocking I/O

  • I/O operations can be performed asynchronously without waiting
  • Multiple connections can be implemented in a non-blocking way (Selectors)

Channels

  • Bidirectional communication
    • Unlike InputStream and OutputStream, a single channel can perform both read and write
  • Direct Buffer support
    • Performance improvement by reducing buffer copy overhead through memory-mapped buffers
  • Read/write for socket communication is only supported with native memory - stack overflow (Java Champion’s answer)
    • Native memory is part of the JVM’s native area
  • If you use a buffer created in JVM heap memory for socket communication, there is overhead from copying to native memory
  • To reduce buffer copy overhead, direct buffers are introduced, which are created and written directly in native memory
  • Due to direct buffer optimization, there is a difference in the number of direct buffers between Webflux and MVC models - https://oss.navercorp.com/nspa/nspa-server/issues/3219#issuecomment-14853076
    • MVC: 200~300
    • Webflux: 75

Main Components

  • Selector
    • Handles events for multiple channels in a non-blocking way
  • ServerSocketChannel
    • Server socket channel. Supports bidirectional communication.
    • By implementing SelectableChannel, Selector can register to receive events for the channel.
  • SelectionKey
    • Token issued when registering a channel with a Selector. Represents the connection between selector and channel.

img1

Code Example

A server implementation that returns the current time when a client connects

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

/**
 * sample code: https://docs.oracle.com/en/java/javase/19/core/non-blocking-time-server-nio-example.html
 */
public class NBTimeServer { // NonBlockingTimeServer
	private static final int DEFAULT_TIME_PORT = 8900;

	public NBTimeServer() throws Exception {
		acceptConnections(this.DEFAULT_TIME_PORT);
	}

	public NBTimeServer(int port) throws Exception {
		acceptConnections(port);
	}

	private static void acceptConnections(int port) throws Exception {
		// Create selector
		Selector acceptSelector = SelectorProvider.provider().openSelector();

		// Create serverSocketChannel and bind port
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.configureBlocking(false);

		InetAddress localhost = InetAddress.getLocalHost();
		InetSocketAddress isa = new InetSocketAddress(localhost, port);
		serverSocketChannel.socket().bind(isa);

		// Register accept event of serverSocketChannel to selector.
		// Receive selectionKey as a token for registration
		SelectionKey acceptKey = serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT);

		int keysAdded = 0;

		// Blocking until an event occurs
		while ((keysAdded = acceptSelector.select()) > 0) {
			// Check occurred events
			Set<SelectionKey> readyKeys = acceptSelector.selectedKeys();
			Iterator<SelectionKey> i = readyKeys.iterator();

			// Process logic for each selectionKey with an event
			while (i.hasNext()) {
				SelectionKey sk = i.next();
				// Only one is registered, so if an event is received, it is the same as the selectionKey received at registration
				assert acceptKey == sk;

				// selectionKey: channel=sun.nio.ch.ServerSocketChannelImpl[/127.0.0.1:8900], selector=sun.nio.ch.KQueueSelectorImpl@57829d67, interestOps=16, readyOps=16
				System.out.println("selectionKey: " + sk);

				i.remove();// Remove selectKey to prevent reprocessing

				ServerSocketChannel nextReady = (ServerSocketChannel)sk.channel();
				Socket socket = nextReady.accept().socket();
				PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
				Date now = new Date();
				out.println(now);
				out.close();
			}
		}
	}

	public static void main(String[] args) {
		System.out.println("start");

		try {
			NBTimeServer nbt = new NBTimeServer();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Comparison with C Code

1. select vs poll vs epoll

  • fd seems to correspond to selectionKey
  • The object that waits for and processes events is abstracted as Selector