Recently, I plan to deepen my knowledge of Java network programming (IO, NIO, Socket programming, Netty).

Java I/O streams

Java Network Programming

Java NIO: Buffers

Java NIO: Channels

Java NIO: selector

Java NIO requires an understanding of the three core concepts of buffers, channels, and selectors as a complement to Java I/O to improve the efficiency of mass data transfer.

As one of NIO’s three core concepts (buffer, Channel, selector), channels are used to efficiently transfer data between byte buffers and entities (files or sockets) on the other side of the Channel.

The general pattern of NIO programming is to fill the send byte buffer with data –> send it over the channel to the channel peer file or socket

Channel basis

The purpose of using a Channel is to transfer data. You need to open the Channel before using it and close it after using it

Open the channel

There are two types of I/ OS: File I/O and Stream I/O. The corresponding channels are File channels and socket channels (SocketChannel, ServerSocketChannel, DatagramChannel)

For socket channels, open using the static factory method

SocketChannel sc = SocketChannel.open();
ServerSocketChannel sc = ServerSocketChannel.open();
DatagramChannel sc = DatagramChannel.open();
Copy the code

File channels can only be obtained by calling getChannel() on a RandomAccessFile, FileInputStream, FileOutputStream object

FileInputStream in = new FileInputStream("/tmp/a.txt");
FileChannel fc = in.getChannel();
Copy the code

Use channels for data transfer

The next code first puts the data to be written into ByteBuffer, then opens the file channel and puts the data from the buffer into the file channel.

// Prepare the data and put it into the byte buffer
ByteBuffer bf = ByteBuffer.allocate(1024);
bf.put("i am cool".getBytes());
bf.flip();
// Open the file channel
FileOutputStream out = new FileOutputStream("/tmp/a.txt");
FileChannel fc = out.getChannel();
// Data transfer
fc.write(bf);
// Close the channel
fc.close();
Copy the code

Close the channel

Just as objects such as sockets and FileInputStream need to be closed after they are used, channels need to be closed after they are used. An open channel represents a particular connection to a particular I/O service and encapsulates the state of that connection. When the channel is closed, the connection is lost and nothing is connected.

Blocking & non-blocking mode

Channels can run in both blocking and non-blocking modes. A channel in non-blocking mode never sleeps and the requested operation either completes immediately or returns a result indicating that no operation was performed (see the description of Socket channels). Only stream-oriented channels can use non-blocking mode

File channel

File channels are used to access files by calling getChannel() on a RandomAccessFile, FileInputStream, FileOutputStream object. Calling the getChannel method returns a FileChannel object connected to the same file that has the same access rights as the File object.

File access

The purpose of using a file channel is still to read and write files. The read and write APIS of the channel are as follows:

public abstract int read(ByteBuffer dst) throws IOException;
public abstract int write(ByteBuffer src) throws IOException;
Copy the code

Here is a Demo reading the file

// Open the file channel
RandomAccessFile f = new RandomAccessFile("/tmp/a.txt"."r");
FileChannel fc = f.getChannel();
// Reads data from a channel until the end of the file
ByteBuffer bb = ByteBuffer.allocate(1024);
while(fc.read(bb) ! = -1) {; }// Flip (flip before reading)
bb.flip();
StringBuilder builder = new StringBuilder();
// Convert each byte to a character (ASCII encoding)
while (bb.hasRemaining()) {
builder.append((char) bb.get());
}
System.out.println(builder.toString());

Copy the code

The problem with the demo above is that we can only read bytes, which are then decoded by the application. This problem can be solved by wrapping Channels as readers and writers. Of course, we can also use the Java I/O stream mode Reader and Writer to manipulate characters directly

File channel location and file void

Position is the location of a normal file, and the value of position determines where in the file data will be read or written next

Reading beyond the end of the file returns -1 (file EOF)

Writing data to a position beyond the end of the file creates a hole in the file: for example, a file is 10 bytes long, but writing data to position=20 creates a void between 10 and 20

The force operation

Force The operation forces the channel to immediately apply all changes to disk files (in case the changes are lost due to system downtime)

public abstract void force(boolean metaData) throws IOException;
Copy the code

Memory file mapping

FileChannel provides a map() method that creates a virtual memory map between an open file and a special type of ByteBuffer(MappedByteBuffer).

Since the MappedByteBuffer object returned by the Map method is a direct buffer, manipulating files using MappedByteBuffer is very efficient (especially in the case of large data transfers).

The use of MappedByteBuffer

Read files from MappedByteBuffer

FileInputStream in = new FileInputStream("/tmp/a.txt");
FileChannel fc = in.getChannel();
MappedByteBuffer mbb = fc.map(MapMode.READ_ONLY, 0, fc.size());
StringBuilder builder = new StringBuilder();
while (mbb.hasRemaining()) {
  builder.append((char) mbb.get());
}
System.out.println(builder.toString());
Copy the code

Three modes of MappedByteBuffer

  • READ_ONLY

  • READ_WRITE

  • PRIVATE

    Read – only and read – write modes are easy to understand, while PRIVATE writes to a temporary buffer and does not actually write to the file. (Copy ideas as you write)

The Socket channel

Socket channels can run in non-blocking mode and are optional, which enables network programming to manage hundreds of Socket connections using a single thread instead of creating a single thread for each Socket connection.

Socket channels are not responsible for protocol-related operations. Protocol-related operations are delegated to peer Socket objects (e.g., SocketChannel objects delegate to Socket objects).

Non-blocking mode

SocketChannel provides a non-blocking mode instead of the blocking mode of traditional Java sockets to build high-performance network applications

In non-blocking mode, almost all operations return immediately. For example, if the following SocketChannel runs in non-blocking mode, the connect operation will return immediately, if SUCCESS is true, the connection was successfully established, and if SUCCESS is false, the connection is still being established (TCP connections take some time).

 // Open the Socket channel
 SocketChannel ch = SocketChannel.open();
 // Non-blocking mode
 ch.configureBlocking(false);
 // Connect to the server
 boolean success = ch.connect(InetSocketAddress.createUnresolved("127.0.0.1".7001));
 // Train the connection state. If the connection has not been established, you can do some other work
 while(! ch.finishConnect()){//dosomething else
 }
 // Set up the connection
 //do something;
Copy the code

ServerSocketChannel

ServerSocketChannel is similar to ServerSocket except that it can run in non-blocking mode

The following is a simple example of building a server over ServerSocketChannel, which mainly represents the non-blocking mode and has a similar core idea to ServerSocket

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(7001));
while (true){
  SocketChannel sc = ssc.accept();
  if(sc ! =null){
    handle(sc);
  }else {
    Thread.sleep(1000); }}Copy the code

SocketChannel and DatagramChannel

SocketChannel Indicates the Socket that simulates TCP. DatagramChannel corresponds to DatagramSocket and simulates UDP

The usage of SeverSocketChannel is similar to SeverSocketChannel, just look at the API

Utility class

We mentioned in the style channel that the codec needs to be done by the application itself by manipulating only the byte buffer. If we want to manipulate characters directly on Channels, we need to use the Channels utility class. Channels provides the ability to convert Channels to streams and convert Channels to reader writers

// channel --> input/output streams
public static OutputStream newOutputStream(final WritableByteChannel ch);
public static InputStream newInputStream(final AsynchronousByteChannel ch);
// Input/output streams --> channels
public static ReadableByteChannel newChannel(final InputStream in);
public static WritableByteChannel newChannel(final OutputStream out);
// Channel --> reader writer
public static Reader newReader(ReadableByteChannel ch, String csName);
public static Writer newWriter(WritableByteChannel ch, String csName);
Copy the code

We can manipulate characters directly on the channel by converting the channel into a reader or writer.

	RandomAccessFile f = new RandomAccessFile("/tmp/a.txt"."r");
  FileChannel fc = f.getChannel();
  // Channel conversion to reader, UTF-8 encoding
  Reader reader = Channels.newReader(fc, "UTF-8");
  int i = 0, s = 0;
  char[] buff = new char[1024];
  while ((i = reader.read(buff, s, 1024- s)) ! = -1) {
    s += i;
  }
  for (i = 0; i < s; i++) {
    System.out.print(buff[i]);
  }
Copy the code

conclusion

Channels are mainly divided into file channel and socket channel.

For file operations: if large files use the channel’s file memory mapping feature (MappedByteBuffer) to improve transfer performance, otherwise I prefer the traditional I/O streaming mode (character API); For socket operations, use channels can run in non-blocking mode and are optional, making it easy to build high-performance network applications.