List of Java.NIO Stream Programming

Posted by proxydude on Mon, 01 Jul 2019 00:27:07 +0200

Simple differences between Java standard IO and Java NIO indicate:

  Java Standard IO Java NIO
API calls simple complex
Low-level implementation Stream-oriented, one-way channel Oriented, Release CPU, Memory Pressure
Results Synchronized blocking Synchronized non-blocking
Data Peep Blocking reads, either sufficient or not With a Buffer, you need to check if the data is sufficient to read
Number of threads processing data 1:1 (a thread processes a stream) 1:N (Selector, multiplexing, can manage multiple channels with one or several small threads)

Java NIO Knowledge System Diagram:

 

 

  • 1. What is NIO?

Java NIO (New IO) provides an API (starting with Java 1.4) that replaces the standard Java IO, with a package name of java.nio. *.

  • 2. What features does NIO provide?

Java NIO provides the feature of synchronous non-blocking IO, so that IO no longer depends on stream (stream), but uses channel (channel) to achieve non-blocking response, avoiding the overhead of thread context switching.

  • 3. Basic concepts of NIO

Java NIO consists of three core components: Buffer, Channel and Selector.

In addition, Charset (Character Set) provides API s that provide Unicode string encoding conversion to byte sequences and decoding.

  • 4. Buffer (Buffer)

Buffer, as its name implies, is actually a linear and finite array memory container.

Data can be read from channel to Buffer or written from Buffer to channel.

The implementations include ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, Float Buffer and DoubleBuffer.

(1) Basic attributes

Capacity: The number of elements contained. The capacity of the buffer cannot be negative and cannot be changed once the definition is defined.

limit: The first index that should not be read or written. Cache limits should not be negative and should not be greater than capacity.

position: The next index to read or write to. It should not be negative or exceed the limit.

mark: An index marking the current location for temporary storage. If a tag is defined, the tag will be discarded when the position or restriction is adjusted to a value less than the tag.

remaining: The number of elements (limit - position) between the current position and the restriction.

 

Labels, locations, limits and capacity values are as follows:

0 (= mark) = position (= limit) = capacity (= capacity)

The schematic diagram of position, limit and capacity in read-write mode:

capacity defines the size of memory blocks you can operate on, capacities of byte, long, char, etc.

In read mode, limit indicates how much data you can read at most. When switching read mode, limit is set to the current position in write mode. In this way, you can read all the data you wrote before.

In write mode, limit equals capacity, indicating that at most capacity data can be written.

(2) Basic usage of Buffer

Using Buffer to read and write data generally follows the following four steps:

1. Create and allocate buffer spaces of specified size

2. Write data to Buffer

3. Call flip() method

4. Read data from Buffer (if necessary, check for adequacy)

5. Call the clear() method or compact() method

Examples:

java.nio.ByteBuffer byteBuffer = ByteBuffer.allocate(5);
//Clear data, switch write mode, prepare to write data
byteBuffer.clear();
//byteBuffer.put((byte) 1);
//Switch Read Mode to Read Data
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
	byte b = byteBuffer.get();
	//Operational data
}

(3) Instance method

Byte buffers are either direct or indirect. If it is a direct byte buffer, the Java Virtual Opportunity does its best to perform native I/O operations directly on that buffer. That is, before (or after) each call to a native I/O operation of the underlying operating system, the virtual machine tries to avoid copying the contents of the buffer into or from the intermediate buffer. The allocateDirect method can be used to allocate capacity-sized continuous byte space at one time. Creating ByteBuffer objects with continuous space by allocateDirect method can improve efficiency to a certain extent, which can be greatly improved on some operating system platforms, while performance will be very poor on other operating system platforms. Use direct buffers with caution unless you know that there is a real performance improvement.

a. Indirect Buffers - Create with Allocation

java.nio.ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);

b. Direct Buffer - Create with AlloeDirect

java.nio.ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(10);

c. Static warp Method Creation

byte[] bytes = new byte[]{1, 2, 3, 4, 5};
java.nio.ByteBuffer byteBuffer1 = ByteBuffer.wrap(bytes);

(4) Reading data from Buffer

There are two ways to read data from Buffer:

a. Read and write data from Buffer to channel (channel)

int bytesWritten = inChannel.write(buf);

b. get method using Buffer

byte aByte = buf.get();

(5) Writing data to Buffer

There are two ways to write data to Buffer:

a. Read and write data from channel (channel) to Buffer

int bytesRead = inChannel.read(buf); //read into buffer.

b. put method using Buffer

buf.put(127);

(6) Read-write mode switching

Buffer has the following methods to switch between read and write modes:

clear: (position=0, limit=capacity, mark=-1) position is set to 0, limit is set to capacity, and mark is discarded.

flip: (limit=position, position=0, mark = 1) Limit is set to the current position, then position is set to 0, and mark is discarded.

rewind: (position=0, mark = 1) position is set to 0, limit remains unchanged, and mark is discarded.

compact: (copy unread data to the front end, position=remaining, limit=capacity, mark=-1) Copy the bytes between the current location of the buffer and the boundaries (if any) to the beginning of the buffer, then set the location of the buffer to remain, limit to capacity, and discard the mark.

 

Therefore, switch from read mode to write mode and use clear.

Switch from write mode to read mode and use flip.

Read-write mode is mixed and rewind is used.

(7) Marking and Resetting

Mark: The current location is set to mark=position

reset: The location is set to the position of the previous tag (position=mark)

(8) Shared Buffer

duplicate - Shares the underlying buffer, where content is visible to each other, and where, where, and where tags are independent

Slce - Shared Buffer Subsection (from where the share occurs), which is visible to each other and independent of location, restriction and markup

warp - wraps, referring to byte arrays as buffer arrays, and if the buffer contents change, the byte arrays change accordingly

as view:

Sharing part or all of the buffer, content is visible to each other, location, restriction and markup are independent of each other.

Remaining > 1 (divided by 2)

asCharBuffer

asShortBuffer
Remaining > 2 (divided by 4)

asIntBuffer

asFloatBuffer
Remaining > 3 (divided by 8)

asLongBuffer

asDoubleBuffer
 

As Read Only Buffer (Read Only)

 

Examples:

public static void main(String[] args) {
	java.nio.ByteBuffer byteBuffer1 = ByteBuffer.allocate(31);
	printBuffer(byteBuffer1, "byteBuffer1");

	/**
	 * byteBuffer1 Remaining > 1 (divided by 2)
	 */
	CharBuffer charBuffer = byteBuffer1.asCharBuffer();
	printBuffer(charBuffer, "charBuffer");
	charBuffer.put('a');
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));

	/**
	 * byteBuffer1 Remaining > 1 (divided by 2)
	 */
	ShortBuffer shortBuffer = byteBuffer1.asShortBuffer();
	printBuffer(shortBuffer, "shortBuffer");
	shortBuffer.put((short) 3);
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));

	/**
	 * byteBuffer1 Remaining > 2 (divided by 4)
	 */
	IntBuffer intBuffer = byteBuffer1.asIntBuffer();
	printBuffer(intBuffer, "intBuffer");
	intBuffer.put(4);
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));

	/**
	 * byteBuffer1 Remaining > 3 (divided by 8)
	 */
	LongBuffer longBuffer = byteBuffer1.asLongBuffer();
	printBuffer(longBuffer, "longBuffer");
	longBuffer.put(120);
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));

	/**
	 * byteBuffer1 Remaining > 2 (divided by 4)
	 */
	FloatBuffer floatBuffer = byteBuffer1.asFloatBuffer();
	printBuffer(floatBuffer, "floatBuffer");
	floatBuffer.put(9f);
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));

	/**
	 * byteBuffer1 Remaining > 3 (divided by 8)
	 */
	DoubleBuffer doubleBuffer = byteBuffer1.asDoubleBuffer();
	printBuffer(doubleBuffer, "doubleBuffer");
	doubleBuffer.put(1);
	if (byteBuffer1.hasArray())
		System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));
}

private static void printBuffer(Buffer buffer, String name) {
	System.out.println((name != null && !name.isEmpty() ? name + " " : "") + "position=" 
			+ buffer.position() + ",limit=" + buffer.limit()
			+ ",remaining=" + buffer.remaining() + ",capacity=" + buffer.capacity());
}

(9) ByteOrder

In the field of computer science, byte order refers to the order in which bytes of multi-byte data are stored. Typically, the storage mode of integers in memory and the transmission order of network transmission are described.

In different processors, the byte order of the machine may be inconsistent, so it is necessary to adjust the byte order when processing data across platforms.

public static void main(String[] args) {
	String string = "abcde";
	java.nio.ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);
	System.out.println(byteBuffer1.order());
	byteBuffer1.rewind();//Set the location to 0 and discard the tag
	byteBuffer1.order(ByteOrder.BIG_ENDIAN);
	byteBuffer1.asCharBuffer().put(string);
	System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));
	byteBuffer1.rewind();//Set the location to 0 and discard the tag
	byteBuffer1.order(ByteOrder.LITTLE_ENDIAN);
	byteBuffer1.asCharBuffer().put(string);
	System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));
	/**
	 * Invalid Usage 1: Change order only, do not refill data, storage will not change, only the next time it takes effect
	 */
	byteBuffer1.rewind();//Set the location to 0 and discard the tag
	byteBuffer1.order(ByteOrder.LITTLE_ENDIAN);
	System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));
	/**
	 * Invalid Usage 2. Fill in the data and change the order. Storage will not change until next time.
	 */
	byteBuffer1.rewind();//Set the location to 0 and discard the tag
	byteBuffer1.asCharBuffer().put(string);
	byteBuffer1.order(ByteOrder.LITTLE_ENDIAN);
	System.out.println("byteBuffer1 data=" + Arrays.toString(byteBuffer1.array()));
}

(10) Records of other pits

ByteBuffer's array() method is not treated like other methods, such as CharBuffer:

The array() of ByteBuffer returns all the capacity elements as follows:

	/**
	 * Convert byte arrays
	 *
	 * @param byteBuffer
	 * @return
	 */
	public static byte[] toBytes(ByteBuffer byteBuffer) {
		// Retrieve bytes between the position and limit // (see Putting Bytes into a ByteBuffer)
		byte[] bytes = new byte[byteBuffer.remaining()];
		// transfer bytes from this buffer into the given destination array
		byteBuffer.get(bytes, 0, bytes.length);
		return bytes;
	}

 

  • 5. Channel (Channel)

(1) Working Principle

In operating system knowledge, channel refers to a CPU-independent I/O controller that controls the exchange of information between peripheral I/O devices and memory. In the instruction system with channel mode, besides the machine instruction system for CPU programming, another set of channel instructions for channel specific purpose is set up. The channel program is programmed with channel instructions, and I/O devices are read or stored. When I/O operation is needed, CPU only needs to start the channel, and then can continue to execute its own program. Channel then executes the channel program, manages and implements I/O operation. When completed, channel can report to CPU.

In this way, the channel enables a higher degree of parallelism between CPU, memory and I/O operations.

(2) Channel vs. Stream

Stream (Stream), is one-way, can not read and write in a stream mixed use, or read all the way until closed, or write all the way until closed. At the same time, the stream is directly dependent on CPU instructions, and I/O operations with memory, CPU instructions will always wait for I/O operations to complete.

Channel, on the other hand, is bidirectional. It can read and write in one channel by Buffer. It can cross-read and write data to the channel without shutting down immediately after the read and write operation. In addition, it can be directly docked in two channels. Channels do not rely on CPU instructions, there are dedicated channel instructions, receive CPU instructions, you can independently and memory to complete I/O operations, only after the completion of I/O operations to notify the CPU, during which the CPU does not have to wait.

(3) Classification of Channels

Java Channel (Channel) provides a variety of I/O entity connections, mainly covering three aspects: file, network (TCP, UDP), pipeline.

Overall implementation: File Channel, Server Socket Channel, Socket Channel, Datagram Channel, Pipe. Sink Channel, Pipe. Source Channel.

  file channel Network (socket) channel The Conduit
Realization FileChannel ServerSocketChannel,
SocketChannel,
DatagramChannel
Pipe.SinkChannel, 
Pipe.SourceChannel
Inherited abstract classes From AbstractInterruptible Channel, an interruptible channel. Originated from AbstractSelectable Channel, it provides registration, cancellation, closure, setting blocking mode, managing the current selection keyset, and supporting "multiplexing" function. Same left
Instance method Obtain file channels through FileInputStream, FileOutputStream, Random AccessFile By registering listeners with the Selector selector, a new SelectionKey object is returned that indicates that the channel has registered with the selector. Same left

 

  • 6. File Channel

(1) Open FileChannel (instantiation)

Before using FileChannel, you need to open it. But we can't open a FileChannel directly. We need to get FileChannel instances through FileInputStream, FileOutputStream, Random AccessFile.

java.nio.channels.FileChannel channel = null;
FileInputStream fis = null;
try {
	fis = new FileInputStream(path);
	channel = fis.getChannel();
	
} finally {
	if (channel != null)
		channel.close();
	if (fis != null)
		fis.close();
}

(2) Reading data from FileChannel and writing data to FileChannel

Read and write: Like other Channels, read and write are transmitted by Buffer. It is important to note that writing data to Channel (i.e. Buffer reading data to Channel) requires checking whether the data is sufficient.

position: Gets and sets the current location

Size: Gets the current size of this FileChannel file

force(boolean): Forces all file updates to this channel to be written to the storage device containing the file. Ensure that files are updated to storage devices in time, especially when writing data.

truncate: Intercept the file for this channel to a given size.  

public static void main(String[] args) throws IOException {
	final String path = "file.txt";
	write(path);//Writing Documents
	write2(path);//Read and write at specific locations
	read(path);//read file
	System.out.println();
	truncate(path);//File interception
	read(path);//read file
}
/**
 * FileChannel read file
 */
private static void read(String path) throws IOException {
	java.nio.channels.FileChannel channel = null;
	FileInputStream fis = null;
	try {
		fis = new FileInputStream(path);
		channel = fis.getChannel();
		ByteBuffer buffer1 = ByteBuffer.allocate(1024);
		// Read data from incoming channel to buffer
		buffer1.rewind();
		while (channel.read(buffer1) > 0) {
			//Read buffer
			buffer1.flip();
			Charset charset = Charset.defaultCharset();
			CharBuffer charBuffer = charset.decode(buffer1);
			System.out.print(charBuffer);
		}
	} finally {
		if (channel != null)
			channel.close();
		if (fis != null)
			fis.close();
	}
}
/**
 * FileChannel Writing Documents
 */
private static void write(String path) throws IOException {
	ByteBuffer buffer = ByteBuffer.wrap("Zhao Keman Humu, Wu Gou Shuangxueming. The silver saddle shines like a white horse, like a meteor.\n".getBytes());
	java.nio.channels.FileChannel channel = null;
	FileOutputStream fos = null;
	try {
		fos = new FileOutputStream(path);
		channel = fos.getChannel();
		//Force to brush out to memory
		channel.force(true);
		// Read data from buffer and write to channel
		buffer.rewind();
		channel.write(buffer);
	} finally {
		if (channel != null)
			channel.close();
		if (fos != null)
			fos.close();
	}
}
/**
 * FileChannel Read and write at specific locations
 */
private static void write2(String path) throws IOException {
	ByteBuffer buffer = ByteBuffer.wrap("Ten steps to kill one person, thousands of miles do not stay. When it's over, hide deep in your name.\n".getBytes());
	java.nio.channels.FileChannel channel = null;
	RandomAccessFile file = null;
	try {
		file = new RandomAccessFile(path, "rw");
		channel = file.getChannel();
		channel.position(channel.size()); //Locate at the end of the file  
		//Force to brush out to memory
		channel.force(true);
		// Read data from buffer and write to channel
		buffer.rewind();
		channel.write(buffer);
	} finally {
		if (channel != null)
			channel.close();
		if (file != null) {
			file.close();// Closing flow
		}
	}
}
/**
 * FileChannel File interception
 */
private static void truncate(String path) throws IOException {
	java.nio.channels.FileChannel channel = null;
	RandomAccessFile file = null;
	try {
		file = new RandomAccessFile(path, "rw");
		channel = file.getChannel();
		/** 
		 * 36 byte before intercepting files 
		 */
		channel.truncate(36);
	} finally {
		if (channel != null)
			channel.close();
		if (file != null) {
			file.close();// Closing flow
		}
	}
}

(3) exclusive locking

lock() and tryLock() - Get or attempt to obtain an exclusive lock on the file for this channel.

FileLock lock(long position, long size, boolean shared) and FileLock tryLock(long position, long size, boolean shared) obtain or attempt to obtain locks on a given area of the file for this channel. Shared means: whether to use shared locks or not, some operating systems that do not support shared locks will automatically change shared locks to exclusive locks. You can detect what type of lock you get by calling the isShared() method.

- The difference between shared locks and exclusive locks:

Shared locks: If a thread acquires a shared lock for a file, other threads can acquire a shared lock for the same file or for parts of the same file, but cannot acquire an exclusive lock.

Exclusive Lock: Only one read or one write (neither read nor write at the same time). Exclusive locks prevent other programs from acquiring any type of lock.

- The difference between lock() and tryLock():

lock() is blocking until the lock is available, or the thread that calls lock() interrupts, or the channel that calls lock() closes. Locking range can increase with the increase of file size. No parameter lock() is an exclusive lock by default; parameter lock(0L, Long.MAX_VALUE, true) is a shared lock.

tryLock() is non-blocking and returns null when the lock is not obtained.

- FileLock is thread-safe

- FileLock life cycle: Calling FileLock.release(), Channel.close(), or JVM closure

Be careful:

- In the same process, the file lock cannot be retrieved again until it is released. That is, before the release() method is called, only lock() or tryLock() can be called once.

- File locking is maintained by the entire Java virtual machine. However, they are not suitable for controlling access to files by multiple threads in the same virtual machine.  

- NonWritable ChannelException exception is reported when a read-only file or a read-only channel is locked in any way.

- NonReadable ChannelException exception is reported when an unreadable file or unreadable channel is locked in any way.

Examples:

Reading and reading overlap in the same process

Both read threads are blocking access to shared locks.

lock = channel.lock(0L, Long.MAX_VALUE, true);

Overlapping FileLock Exception

------------- Shared Locks for Different Processes - Read and Read

Both process read threads are blocking access to shared locks.

lock = channel.lock(0L, Long.MAX_VALUE, true);

 

According to the results, we can see that when the second process reads, the shared lock is acquired (18:46:03 acquired), and the shared lock of the first process is not released (18:46:05 released).

The shared lock allows simultaneous reading.

------------- Different Processes - Reading and Writing

FileInputStream read process: lock = channel.lock(0L, Long.MAX_VALUE, true); block access to shared locks

FileOutputStream Writing Process: lock = channel.lock(); Blocking to obtain exclusive locks

We try to start the read process and the write process one after another, and find that neither of them is abnormal. At the same time, we wait for another process to complete and release the lock before acquiring the file lock.

------------- Different Processes - Writing

Similarly, the characteristics of exclusive locks are verified.

 

  • 7. Network TCP Channel (Server Socket Channel, Socket Channel)

7.1. Basic usage of Server Socket Channel

(1) Open Server Socket Channel

Open ServerSocketChannel by calling the ServerSocketChannel.open() method

ServerSocketChannel channel= ServerSocketChannel.open();

(2) Close Server Socket Channel

Close ServerSocketChannel by calling ServerSocketChannel.close().

//Close Server Socket Channel
if (channel != null) {
	channel.close();
}

(3) Binding ServerSocket to a specific address (IP address and port number)

channel.bind(new InetSocketAddress("127.0.0.1", 9595));

(4) Monitor incoming connections

In blocking mode, the accept() method is blocked until a new connection, SocketChannel, arrives.

Call the accept() method in the while loop. For example:

while (true) {
	// Monitor incoming connections
	java.nio.channels.SocketChannel socketChannel = channel.accept();
	//do something with socketChannel...
}

(5) Non-blocking mode

Server Socket Channel can be set to non-blocking mode.

In non-blocking mode, the accept() method returns immediately, and null returns if no new connection has been entered.  

Therefore, it is necessary to check whether the returned SocketChannel is null.

// Set non-blocking mode so that no blocking occurs when read
channel.configureBlocking(false);

while (true) {
	// Monitor incoming connections
	java.nio.channels.SocketChannel socketChannel = channel.accept();
	if (socketChannel == null) {
		// System.out.println("No client connection");
		TimeUnit.SECONDS.sleep(1);
		continue;
	}
	//do something with socketChannel...
}

7.2. Basic usage of Socket Channel

Socket Channel is similar to Server Socket Channel in that it only needs to specify the server to connect to:

// Open Socket Channel
SocketChannel channel = SocketChannel.open();
// Set non-blocking mode so that no blocking occurs when read
channel.configureBlocking(false);
// tcp connection network
channel.connect(new InetSocketAddress("127.0.0.1", 9595));
if (channel.finishConnect()) {// Successful connection to server
	//do something 
}

7.3. Demo for a Simple Simulated TCP Interface

In order to facilitate learning and communication, only the basic api is used, and Selector is not used for the time being.

Server side:

package io.flysium.nio.c2_channel.socket;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.util.concurrent.TimeUnit;

/**
 * ServerSocketChannel Example
 *
 * @author Sven Augustus
 */
public class ServerSocketChannelTest {

	public static void main(String[] args) throws IOException, 
InterruptedException, ClassNotFoundException {
		java.nio.channels.ServerSocketChannel channel = null;
		try {
			// Open Server Socket Channel
			channel = ServerSocketChannel.open();
			// Set non-blocking mode so that no blocking occurs when read
			channel.configureBlocking(false);
			// Bind ServerSocket to a specific address (IP address and port number)
			channel.bind(new InetSocketAddress("127.0.0.1", 9595));

			while (true) {
				// Monitor incoming connections
				java.nio.channels.SocketChannel socketChannel = channel.accept();
				if (socketChannel == null) {
					// System.out.println("No client connection");
					TimeUnit.SECONDS.sleep(1);
					continue;
				}
				System.out.println("Read:");
				// Read the data sent by the client
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				buffer.clear();
				socketChannel.read(buffer);
				buffer.flip();// Read buffer
				Object object = ByteBufferUtils.readObject(buffer);
				System.out.println(object);
				// Write data to client
				String serializable = "Hello, Client" + socketChannel.getRemoteAddress();
				System.out.println("Ready to write:" + serializable);
				ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
				socketChannel.write(byteBuffer);
			}
		} finally {
			//Close Server Socket Channel
			if (channel != null) {
				channel.close();
			}
		}
	}


}

Client:

package io.flysium.nio.c2_channel.socket;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * SocketChannel Example
 *
 * @author Sven Augustus
 */
@SuppressWarnings("unused")
public class SocketChannelTest {

	public static void main(String[] args) throws IOException, 
InterruptedException, ClassNotFoundException {
		java.nio.channels.SocketChannel channel = null;
		try {
			// Open Socket Channel
			channel = SocketChannel.open();
			// Set non-blocking mode so that no blocking occurs when read
			channel.configureBlocking(false);
			// tcp connection network
			channel.connect(new InetSocketAddress("127.0.0.1", 9595));
			if (channel.finishConnect()) {// Successful connection to server
				/**
				 * Write data to the server
				 */
				String serializable = "Hello! ServerSocketChannel. ";
				System.out.println("Ready to write:" + serializable);
				ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
				channel.write(byteBuffer);
				System.out.println("Read:");
				// Read the data sent by the server
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				buffer.clear();
				int numBytesRead = -1;
				while ((numBytesRead = channel.read(buffer)) != -1) {
					if (numBytesRead == 0) {// If there is no data, wait a little.
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						continue;
					}
					buffer.flip();// Read buffer
					Object object = ByteBufferUtils.readObject(buffer);
					System.out.println(object);
        			buffer.clear(); // Reset and empty
				}
			} else {
				System.out.println("Connection failure, server denial of service");
				return;
			}
		} finally {
			// Close Socket Channel
			if (channel != null) {
				channel.close();
			}
		}
	}

}

 

  • 8. Network UDP Channel

(1) Differences between TCP and UDP

Reference can be made to the following articles: http://www.cnblogs.com/visily/archive/2013/03/15/2961190.html

We can simply understand and summarize:

Agreement Be based on Data schema Resource requirements Data Correctness Data sequentiality Applicable scenarios
TCP Connect Flow or passage More ensure ensure Scenarios of Actuarial Computing
UDP Connectionless Data Paper less Unguaranteed, possible packet loss (of course, the Intranet environment is almost non-existent) No guarantee Communication within Service System

(2) Open Datagram Channel

Open DatagramChannel by calling the DatagramChannel.open() method

DatagramChannel channel= DatagramChannel.open();

Note that the open () method of DatagramChannel only opens the access channel, but it is not connected at this time. Although Datagram Channel does not need to establish a remote connection, it can still detect whether the current channel declares a remote connection address through isConnect ().

(3) Close Datagram Channel

//Close Datagram Channel
if (channel!= null) {
	channel.close();
}

(4) Receiving data

The receive() method receives data from the Datagram Channel and returns a SocketAddress object to indicate the data source. In blocking mode, receive() will block until a packet arrives, while in non-blocking mode, null will be returned if there are no acceptable packets. If the size of the data in the package exceeds the buffer capacity, the extra data will be quietly discarded.

ByteBuffer byteBuffer = ByteBuffer.allocate(size);
byteBuffer.clear();
SocketAddress address = channel.receive(byteBuffer);//receive data

Non-blocking mode:

while (true) {
	ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
	byteBuffer.clear();
	SocketAddress socketAddress = channel.receive(byteBuffer);
	if (socketAddress == null) {
		// System.out.println("No client connection");
		TimeUnit.MILLISECONDS.sleep(1);
		continue;
	}
	//do something with DatagramChannel...
}

(5) Sending data

Send data from Datagram Channel to the address described by the specified SocketAddress object through the send() method. In blocking mode, the calling thread is blocked until the data packet is added to the transmission queue. In non-blocking mode, if the sending content is empty, 0 is returned, otherwise the number of bytes sent is returned.

Note that the non-zero value returned by the send() method does not mean that the datagram has arrived at its destination, but only that the datagram has been successfully added to the transmission queue at the local network layer.

ByteBuffer byteBuffer = ByteBuffer.wrap(new String("i 'm client").getBytes());
int bytesSent = channel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 9898));

Non-blocking mode:

Serializable serializable = "Hello! DatagramChannel. ";
ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
// To send data, the following is a simple simulation of non-blocking mode retransmit mechanism three times
final int TIMES = 3;
int bytesSent  = 0;
int sendTime = 1;
while (bytesSent  == 0 && sendTime <= TIMES) {
	bytesSent  = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 9898));
	sendTime++;
}

(6) Connect to a specific address

Datagram Channel can be "connected" to a specific address in the network. Because UDP is connectionless, connecting to a specific address does not create a real connection like a TCP channel. Instead, the Datagram Channel is locked so that it can only receive and receive data from a specific address. When connected, you can also use read() and write() methods, just like using traditional channels. It's just that there's no guarantee of data transmission.

int bytesRead = channel.read(buf);
int bytesWritten = channel.write(but);

Calling read() or write() methods when the channel is not connected will result in a NotYetConnectedException exception.

(7) Demo, a simple analog UDP interface

In order to facilitate learning and communication, only NIO basic api is used here, but Selector is not used for the time being.

Receiver:

package io.flysium.nio.c2_channel.socket;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.concurrent.TimeUnit;

/**
 * Network UDP Channel Testing--As a Server
 *
 * @author Sven Augustus
 */
public class DatagramChannelServerTest {

	public static void main(String[] args) throws IOException, 
ClassNotFoundException, InterruptedException {
		DatagramChannel channel = null;
		try {
			//Open Datagram Channel
			channel = DatagramChannel.open();
			//Non-blocking mode
			channel.configureBlocking(false);
			//Bind UDP to a specific address (IP address and port number) as a server listening port
			channel.bind(new InetSocketAddress("127.0.0.1", 9898));

			ByteBuffer buffer = ByteBuffer.allocate(1024);
			while (true) {
				buffer.clear();
				SocketAddress socketAddress = channel.receive(buffer);
				if (socketAddress == null) {
					// System.out.println("No client connection");
					TimeUnit.MILLISECONDS.sleep(1);
					continue;
				}
				System.out.println("Read:"+ socketAddress);
				buffer.flip();//Switching Read Mode
				Serializable object = ByteBufferUtils.readObject(buffer);
				System.out.println(object);
				// Write data to client
				String serializable = "Hello, Client" + socketAddress.toString();
				System.out.println("Ready to write:" + serializable);
				ByteBuffer byteBuffer = 
                                           ByteBufferUtils.writeObject(serializable);
				channel.send(byteBuffer,socketAddress);
			}
		} finally {
			//Close Datagram Channel
			if (channel != null) {
				channel.close();
			}
		}

	}

}

Sender:

package io.flysium.nio.c2_channel.socket;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.concurrent.TimeUnit;

/**
 * Network UDP Channel Test - Send Data as Client
 *
 * @author Sven Augustus
 */
public class DatagramChannelClientTest {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		java.nio.channels.DatagramChannel channel = null;
		try {
			//Open Datagram Channel
			channel = DatagramChannel.open();
			//Non-blocking mode
			channel.configureBlocking(false);
			// Sending data does not need to provide a destination address and the source address at the time of receiving is known (similar to Socket Channel).
			// Then you can use regular read() and write() methods at this point.
			channel.connect(new InetSocketAddress("127.0.0.1", 9898));

			Serializable serializable = "Hello! DatagramChannel. ";
			System.out.println("Ready to write:" + serializable);
			ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
			// To send data, the following is a simple simulation of non-blocking mode retransmit mechanism three times
			final int TIMES = 3;
			int bytesSent = 0;
			int sendTime = 1;
			while (bytesSent == 0 && sendTime <= TIMES) {
//bytesSent = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 9898));
				bytesSent = channel.write(byteBuffer);
				sendTime++;
			}
			if (bytesSent > 0) {
				System.out.println("Send successfully.");
			} else {
				System.out.println("Send failed.");
			}
			byteBuffer.clear();

			// Read the data sent by the server
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			buffer.clear();
			int numBytesRead = -1;
			while ((numBytesRead = channel.read(buffer)) != -1) {
				if (numBytesRead == 0) {// If there is no data, wait a little.
					try {
						TimeUnit.MILLISECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					continue;
				}
				buffer.flip();// Read buffer
				Object object = ByteBufferUtils.readObject(buffer);
				System.out.println(object);
				buffer.clear(); // Reset and empty
			}
		} finally {
			//Close Datagram Channel
			if (channel != null) {
				channel.close();
			}
		}

	}

}

 

  • 9. Pipe (Pipe. Sink Channel, Pipe. Source Channel)

Java NIO pipeline is a one-way data connection between two threads. Pipe has a source channel and a sink channel. The data is written to the sink channel and read from the source channel.

The following are illustrations of Pipe's principles:

(1) Pipeline creation

Open the pipe through the Pipe.open() method. For example:

Pipe pipe = Pipe.open();

(2) Writing data to pipes

To write data to the pipeline, you need to access the sink channel. Like this:

Pipe.SinkChannel sinkChannel = pipe.sink();

Then you can call the write method.

(3) Reading data from pipes

From reading the data from the pipeline, you need to access the source channel, like this:

Pipe.SourceChannel sourceChannel = pipe.source();

You can then call the read method.

(4) Simple examples

package io.flysium.nio.c2_channel.pipe;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

/**
 * Pipeline test
 * @author Sven Augustus
 */
public class PipeTest {

	static class Input implements Runnable {

		private final Pipe pipe;

		public Input(Pipe pipe) {
			this.pipe = pipe;
		}

		@Override
		public void run() {
			try {
				Pipe.SourceChannel sourceChannel = pipe.source();

				System.out.println("Read the pipeline.");
				ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
				int bytesRead = sourceChannel.read(byteBuffer);
				byteBuffer.flip();//Switching Read Mode
				Serializable serializable = 
                                           ByteBufferUtils.readObject(byteBuffer);
				System.out.println("Pipeline reading results:" + serializable);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	static class Output implements Runnable {
		private final Pipe pipe;

		public Output(Pipe pipe) {
			this.pipe = pipe;
		}

		@Override
		public void run() {
			try {
				Pipe.SinkChannel sinkChannel = pipe.sink();

				System.out.println("The pipeline writes out the preparation.");
				Serializable object = "Hello. Pipe. ";
				ByteBuffer byteBuffer = ByteBufferUtils.writeObject(object);
				while (byteBuffer.hasRemaining()) {
					sinkChannel.write(byteBuffer);
				}
				System.out.println("Pipeline Write Complete:"+object);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws IOException {
		Pipe pipe = Pipe.open();
		new Thread(new Input(pipe)).start();
		new Thread(new Output(pipe)).start();
	}

}

  • 10. Selector (selector)

(1) Selector pattern

The Selector object is essentially an observer who monitors registered channels and their events, and reports this information when an event occurs in a channel after the select ion mechanism is applied.

This is a network event-driven model. It is divided into three parts:

Register events: Channels register events of interest to Selector.

Select mechanism: Actively apply the select mechanism and return a set of SelectionKey (keys) when an event occurs.

Event Processing: Get event set, ready IO set, registered channel and other information from Selection Key (key) for I/O operation.

(2) Create Selector

Selector selector = Selector.open();

(3) Register Channel Interest Events with Selector

In order to use Channel and Selector together, Channel must be registered with Selector. As follows:

When used with Selector, Channel must be in non-blocking mode.

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

The second parameter of register is the "interest set", which means the set of events that the channel is interested in, and also specifies the Selector to monitor what happens on the channel.

Incidents fall into four main categories:

SelectionKey.OP_CONNECT   The connection is ready and channel successfully connects to another server.
SelectionKey.OP_ACCEPT   Receive ready, server channel successfully accepts a connection.
SelectionKey.OP_READ  Read it. Data is readable in the channel.
SelectionKey.OP_WRITE Write ready, channel channel waiting to write data.

If you are interested in more than one event, you can use the displacement operation:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

(4) Selecting channels through Selector

Using the select mechanism, you can return channels that are ready for events of interest, such as connections, acceptances, reads or writes.

The following is the select() method:

  • int select() is blocked until at least one channel registration event occurs.
  • int select(long timeout) is blocked until at least one channel registration event occurs, or timeout milliseconds have been timed out.
  • int selectNow() will not block, regardless of whether the channel is ready or not, it will return immediately.

The return value is the number of channels in place.

 

Once the select() method is invoked and the return value indicates that one or more channels are ready, the ready channel in the selected key set can be accessed by calling the selector's selectedKeys() method. As follows:

Set selectedKeys = selector.selectedKeys();

Each element, SelectionKey (key), contains

  • The set of interest, the next set of events of interest, determines the type of operation readiness information that will be tested the next time a selector selection method is invoked. The set of interests is initialized with the given value when the key is created; it can then be changed through the interestOps(int) method.
  • Ready collections are collections of events whose channels are ready.
  • Channel is a registered channel instance.
  • Selector object
  • Additional objects (optional) are those that are added at the time of registration.

You can traverse this selected set of keys to access the ready channel. As follows:

Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    keyIterator.remove();
	if(key.isAcceptable()) {
        // Receive ready, server channel successfully accepts a connection.
		//  a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        //The connection is ready and channel successfully connects to another server.
		// a connection was established with a remote server.
    } else if (key.isReadable()) {
        //Read it. Data is readable in the channel.
		// a channel is ready for reading
    } else if (key.isWritable()) {
        //Write ready, channel channel waiting to write data.
		// a channel is ready for writing
    }
}

The keyIterator.remove() call for each iteration. Selector does not remove the SelectionKey instance itself from the selected key set. The channel must be removed by itself when it has been processed. The next time the channel becomes ready, the Selector puts it in the selected key set again.

(5) Examples

Now let's simply simulate the client sending interface requests to the server in a circular way. The request parameters are integers. The server calculates well (actually multiplied by 2) and returns them to the client.

Start several client tests.

Server side:

package io.flysium.nio.c3_selector;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * ServerSocketChannel Example, using the Selector pattern
 *
 * @author Sven Augustus
 */
public class ServerSocketChannelTest2 {

	public static void main(String[] args) throws IOException, 
	InterruptedException, ClassNotFoundException {
		ServerSocketChannel channel = null;
		Selector selector = null;
		try {
			// Open Server Socket Channel
			channel = ServerSocketChannel.open();
			// Set non-blocking mode so that no blocking occurs when read
			channel.configureBlocking(false);
			// Bind ServerSocket to a specific address (IP address and port number)
			channel.bind(new InetSocketAddress("127.0.0.1", 9595));

			// Create Selector selector
			selector = Selector.open();
			// Register events to listen for client connection requests
			channel.register(selector, SelectionKey.OP_ACCEPT);

			final int timeout = 1000;//timeout milliseconds
			while (true) {
				if (selector.select(timeout) == 0) {//Whether or not an event occurs, selector is awakened every other time out
					continue;
				}
				Set selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
				while (keyIterator.hasNext()) {
					SelectionKey key = keyIterator.next();
					keyIterator.remove();
					if (key.isAcceptable()) {// Receive ready, server channel successfully accepts a connection.
						SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
						socketChannel.configureBlocking(false);// Setting up non-blocking mode
						// Register the read operation for the next read operation
						socketChannel.register(key.selector(), SelectionKey.OP_READ);
					} else if (key.isConnectable()) {//The connection is ready and channel successfully connects to another server.

					} else if (key.isReadable()) {//Read it. Data is readable in the channel.
						SocketChannel socketChannel = (SocketChannel) key.channel();
						//System.out.println("Ready to Read:");
						// Read the data sent by the client
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						buffer.clear();
						int readBytes = socketChannel.read(buffer);
						if (readBytes >= 0) {// Non-blocking, read available bytes of buffer immediately
							buffer.flip();// Read buffer
							Object object = ByteBufferUtils.readObject(buffer);
							//System.out.println(object);
							//Additional parameters
							key.attach(object);
							// Switch write operations for the next write operation
							key.interestOps(SelectionKey.OP_WRITE);
						} else if (readBytes < 0) { //Client Connection Closed, Release Resources
							System.out.println("Client" + socketChannel.socket().getInetAddress() 
							+ "port" + socketChannel.socket().getPort() + "To break off...");
							socketChannel.close();
						}
					} else if (key.isValid() && key.isWritable()) {//Write ready, channel channel waiting to write data.
						SocketChannel socketChannel = (SocketChannel) key.channel();
						// Calculation
						Integer integer = Integer.parseInt(String.valueOf(key.attachment()));
						String serializable = String.valueOf(integer * 2);
						// Write data to client
						ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
						socketChannel.write(byteBuffer);
						System.out.println("Client Server:" + integer + ",Response:" + serializable);
						// Switch the read operation to make the next interface request, the next read operation
						key.interestOps(SelectionKey.OP_READ);
					}
				}
			}
		} finally {
			//Close Server Socket Channel
			if (channel != null) {
				channel.close();
			}
			if (selector != null) {
				selector.close();
			}
		}
	}


}

Client:

package io.flysium.nio.c3_selector;

import io.flysium.nio.ByteBufferUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * SocketChannel Example, using the Selector pattern
 *
 * @author Sven Augustus
 */
@SuppressWarnings("unused")
public class SocketChannelTest2 {

	public static void main(String[] args) throws IOException,
	InterruptedException, ClassNotFoundException {
		new Thread(new ClientRunnable("A")).start();
		new Thread(new ClientRunnable("B")).start();
		new Thread(new ClientRunnable("C")).start();
		new Thread(new ClientRunnable("D")).start();
	}

	private static class ClientRunnable implements Runnable {

		private final String name;

		private ClientRunnable(String name) {
			this.name = name;
		}

		@Override
		public void run() {
			SocketChannel channel = null;
			Selector selector = null;
			try {
				// Open Socket Channel
				channel = SocketChannel.open();
				// Set non-blocking mode so that no blocking occurs when read
				channel.configureBlocking(false);
				// tcp connection network
				channel.connect(new InetSocketAddress("127.0.0.1", 9595));

				// Create Selector selector
				selector = Selector.open();
				// Register events, listen for read/write operations
				channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

				final int timeout = 1000;//timeout milliseconds
				if (channel.finishConnect()) {// Successful connection to server
					while (true) {
						if (selector.select(timeout) == 0) {
							continue;
						}
						Set selectedKeys = selector.selectedKeys();
						Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
						while (keyIterator.hasNext()) {
							SelectionKey key = keyIterator.next();
							keyIterator.remove();
							if (key.isValid() && key.isWritable()) {//Write ready, channel channel waiting to write data.
								TimeUnit.SECONDS.sleep(3);
								SocketChannel socketChannel = (SocketChannel) key.channel();
								// Write data to the server
								String serializable = String.valueOf(new Random().nextInt(1000));
								//System.out.println("Ready to write:" + serializable);
								ByteBuffer byteBuffer = ByteBufferUtils.writeObject(serializable);
								socketChannel.write(byteBuffer);
								//Additional parameters
								key.attach(serializable);
								// Switch the read operation to make the next interface request, the next read operation
								key.interestOps(SelectionKey.OP_READ);
							} else if (key.isReadable()) {//Read it. Data is readable in the channel.
								SocketChannel socketChannel = (SocketChannel) key.channel();
								//System.out.println("Ready to Read:");
								// Read the data sent by the server
								ByteBuffer buffer = ByteBuffer.allocate(1024);
								buffer.clear();
								int readBytes = socketChannel.read(buffer);
								if (readBytes >= 0) {// Non-blocking, read available bytes of buffer immediately
									buffer.flip();// Read buffer
									Object object = ByteBufferUtils.readObject(buffer);
									//System.out.println(object);
									buffer.clear(); // Reset and empty
									Integer integer = Integer.parseInt(String.valueOf(key.attachment()));
									System.out.println("thread-" + name 
+ ",Request server:" + integer + ",Response:" + object);
									// Switch the write operation for the next write operation, the interface request
									key.interestOps(SelectionKey.OP_WRITE);
								} else if (readBytes < 0) { //Client Connection Closed, Release Resources
									System.out.println("Server disconnection...");
								}
							}
						}
					}
				} else {
					System.out.println("Connection failure, server denial of service");
					return;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ClosedChannelException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} finally {
				// Close Socket Channel
				if (channel != null) {
					try {
						channel.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

}

  • 11. Divergence/Convergence

Scattered reading from Channel refers to the writing of read data into multiple buffers during the read operation. Therefore, Channel will "scatter" the data read from Channel into multiple Buffers.

Gathering to Channel means writing data from multiple buffers to the same Channel during a write operation. Therefore, Channel sends data from multiple Buffers "Gathering" to Channel.

scatter / gather is often used in situations where the transmitted data needs to be processed separately, such as transmitting a message consisting of a header and a message body. You may disperse the message body and the header into different buffer s so that you can easily handle the header and the message body.

(1) Dispersion

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

Buffers are first inserted into the array, and then the array is used as an input parameter for channel.read(). The read() method writes the data read from the channel to the buffer in the order in which the buffer is in the array. When a buffer is full, the channel writes to another buffer. Decentralization is not suitable for dynamic message processing.

(2) Aggregation

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

The buffers array is the input of the write() method. The write() method writes the data to the channel in the order in which the buffers are in the array. Note that only the data between position and limit will be written. Therefore, if a buffer has a capacity of 128 bytes, but contains only 100 bytes of data, then the 100 bytes of data will be written into the channel. Aggregation is suitable for dynamic message processing.

 

More Demo: https://git.oschina.net/svenaugustus/MyJavaIOLab

This article is only for NIO knowledge summary. For other IO summary sister articles (IOs), see:
+ List of Java Standard I/O Stream Programming: https://my.oschina.net/langxSpirit/blog/830620

Topics: Java socket network less