Chapter 1 Netty introduction and application scenarios
1.2 Netty
-
Netty is an open source Java framework provided by JBOSS and is now a standalone project on Github.
-
Netty is an asynchronous, event-driven network application framework for rapid development of high performance and high reliability network IO programs
-
Netty applies to TCP applications with high concurrency for Clients or applications with large amounts of continuous data transfer in peer-to-peer scenarios.
-
Netty is essentially a NIO framework applicable to various application scenarios related to server communication
1.3 Netty Application Scenarios
1.3.1 Internet Industry
-
Internet industry: In distributed systems, remote service invocation is required between nodes, and high-performance RPC frameworks are essential. Netty, as an asynchronous high-performance communication framework, is often used by these RPC frameworks as a basic communication component.
-
Typical applications are as follows: Ali distributed service framework Dubbo RPC framework uses Dubbo protocol for inter-node communication. Dubbo protocol uses Netty as the basic communication component by default, which is used to realize internal communication between process nodes
1.3.2 Game industry
-
Whether it is mobile game server or large online game, Java language has been more and more widely used
-
As a high-performance basic communication component, Netty provides TCP/UDP and HTTP protocol stacks, facilitating customization and development of private protocol stacks, and logging accounts to the server
-
Map servers can easily communicate with each other through Netty
1.3.3 Big data field
-
The RPC framework of Avro, a classic Hadoop high-performance communication and serialization component, uses Netty by default for cross-boundary point communication
-
Its Netty Service is implemented based on Netty framework secondary encapsulation.
1.3.4 Other Open Source Projects Use Netty
Address: netty. IO/wiki/relate…
Chapter 2 Java BIO programming
2.1 I/O model
-
The I/O model is simple to understand: is what kind of channel to send and receive data, largely determines the performance of program communication
-
Java supports three network programming models /IO modes: BIO, NIO, and AIO
-
Java BIO: Synchronous and block (traditional blocking). The server implements a connection one thread at a time. When a client makes a connection request, the server needs to start a thread to process it
- Java NIO: Synchronous non-blocking, server implementation mode is a thread processing multiple requests (connections), that is, the client sent connection requests are registered to the multiplexer, multiplexer polling connection to have I/O requests to process
- Java AIO (NIO. 2) : Asynchronous non-blocking, AIO introduces the concept of asynchronous channel, using Proactor mode, simplify the program writing, effective request to start the thread, its characteristics is completed by the operating system before informing the server program to start the thread to deal with, generally applicable to the number of connections and connection time is long applications
2.2 BIO, NIO, and AIO application scenarios
-
The BIO approach is suitable for a small and fixed number of connections. It requires high server resources, is limited to applications, and was the only choice before JDK1.4, but the program is simple and easy to understand.
-
NIO is suitable for architectures with a large number of connections and relatively short connections (light operation), such as chat servers, bullet screen systems, and communication between servers. Programming is more complex and is supported in JDK1.4.
-
AIO mode is used in the architecture with a large number of connections and long connections (heavy operation), such as photo album server. It fully calls OS to participate in concurrent operations, and the programming is complicated, which is supported by JDK7.
2.3 Java BIO introduction
-
Java BIO is traditional Java IO programming, with its related classes and interfaces in java.io
-
BIO (blocking I/O) : Synchronous blocking, the server implementation mode is one connection one thread, that is, when the client has a connection request, the server needs to start a thread for processing, if the connection does not do anything will cause unnecessary thread overhead, can be improved through the thread pool mechanism (actual multiple clients to connect to the server).
-
The BIO approach is suitable for a small and fixed number of connections. It requires high server resources, is limited to applications, and was the only choice prior to JDK1.4
2.4 Java BIO Working Mechanism
** Comb through the BIO programming process **
-
Start a ServerSocket on the server side
-
The client starts the Socket to communicate with the server. By default, the server needs to create a thread for each client to communicate with it
-
When a client sends a request, it first consults the server to see if there is a thread response, and if not, it waits or is rejected
-
If there is a response, the client thread waits for the request to end and then resumes execution
2.5 Java BIO Application Example
-
Use BIO model to write a server, listen to port 6666, when there is a client connection, start a thread to communicate with it.
-
The thread pool mechanism is required to improve the ability to connect multiple clients.
-
The server can receive data sent by the client (in Telnet mode).
public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newCachedThreadPool();
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("Server started");
while (true) {
System.out.println("Waiting for connection...");
// Receive a server connection from the server
final Socket socket = serverSocket.accept();
System.out.println("Connected to a client.");
executorService.execute(new Runnable() {
public void run(a) { handler(socket); }}); }}Copy the code
Obtain data based on the received socket
// Get data from socket
public static void handler(Socket socket) {
try {
// system.out.println (" Thread ID="+ thread.currentThread ().getid ()+" + thread.currentThread ().getName());
//1. Define a byte array to accept data
byte[] bytes = new byte[1024];
//2. Socket input stream object
InputStream inputStream = socket.getInputStream();
//3. Read the data sent from the client in a loop, where the inputSteam object is read in a loop
while (true){
System.out.println("Thread ID ="+Thread.currentThread().getId()+"Thread name ="+Thread.currentThread().getName());
//4. Use an array object each time to receive data from the input stream
System.out.println("read...");
// The caveat here is that the read method blocks
int read = inputStream.read(bytes);
//5. If the length of the data read is not unique, convert the bytes array object to a string object
if(read! = -1){
String message = new String(bytes,0,read);
System.out.println("Message sent by client:"+message);
}else {
break; }}}catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("Close the connection to client");
try {
socket.close();
} catch(IOException e) { e.printStackTrace(); }}}Copy the code
Client demo:
# Enter session mode CTRL +]Copy the code
There is a connection request on the server:
Client sends data:
The server receives data:
2.6 Java BIO Problem Analysis
-
Each request needs to create a separate thread, with the corresponding client for data Read, business processing, data Write
-
When the number of concurrent connections is large, a large number of threads need to be created to process connections, occupying large system resources.
-
After a connection is established, if the current thread has no data to Read temporarily, the thread blocks on the Read operation, resulting in a waste of thread resources
Chapter 3 Java NIO programming
3.1 Introduction to Java NIO
-
Java NIO stands for Java non-blocking IO, a new API provided by the JDK. Starting with JDK1.4, Java offers a New set of improved input/output features, collectively known as NIO(New IO), that are synchronous and non-blocking
-
Nio-related classes are placed in the java.nio package and subpackages, and many of the classes in the original java.io package are overwritten.
-
NIO has three core parts: channels, buffers, and selectors.
-
NIO is buffer-oriented, or block-oriented programming. Data is read into a buffer that it processes later and can be moved back and forth in the buffer as needed, which adds flexibility to the process and provides a highly scalable network that is non-blocking
-
Java NIO non-blocking mode, make a thread from one channel to send requests or read the data, but it can only get the currently available data, if there is no data are available, they will not get everything, rather than keep thread block, so until the data can be read before, the thread can be to continue to do other things. Non-blocking writes also work the same way. A thread requests to write some data to a channel, but does not have to wait for it to write completely. The thread can do other things in the meantime.
-
NIO can handle multiple operations on a single thread. Assuming 10,000 requests come in, 50 or 100 threads can be allocated to handle them, depending on the situation. Unlike the previous blocking IO, you had to allocate 10,000.
-
HTTP2.0 uses multiplexing techniques to process multiple requests concurrently for the same connection, and the number of concurrent requests is orders of magnitude larger than HTTP1.1
8) Simple case description
import java.nio.IntBuffer;
public class BasicBuffer {
public static void main(String[] args) {
// Declare an IntBuffer to store data of type 5
IntBuffer intBuffer = IntBuffer.allocate(5);
// Write mode is used by default. Put is used to write data
for(int i=0; i<intBuffer.capacity(); i++){ intBuffer.put(i*2);
}
// Key code, used to reverse read mode, write mode
intBuffer.flip();
while (intBuffer.hasRemaining()){
int i = intBuffer.get();
System.out.println("The result in buffer is:+i); }}}Copy the code
3.2 Comparison of NIO and BIO
-
BIO processes data as a stream, while NIO processes data as a block, and block I/O is much more efficient than stream I/O
-
BIO is blocking, NIO is non-blocking
-
BIO operates based on byte streams and character streams, while NIO operates based on channels and buffers, where data is always read from a Channel into a Buffer or written from a Buffer into a Channel. A Selector listens for events on multiple channels (such as connection requests, data arrivals, etc.), so multiple client channels can be listened for using a single thread
3.3 Schematic diagram of three core principles of NIO
The relationship between Selector, Channel, and Buffer
-
Each channel has a Buffer
-
Selector for one thread, one thread for multiple channels.
-
This diagram reflects that there are three channels registered to the selector
-
Which channel the program switches to is determined by events, and events are an important concept
-
Selector switches between channels based on different events
-
A Buffer is a block of memory with an array at the bottom
-
The data is read and written through the Buffer, and this and the BIO, which is either an input stream or an output stream, can’t be two-way, However, NIO’s Buffer can be read or written. The flip method is required to switch the channel to be bidirectional, which can return to the situation of the underlying operating system, such as Linux, where the channel is bidirectional
3.4 the buffer
Basic introduction
Buffer: A Buffer is essentially aA block of memory that can read and write data
Can be interpreted as aContainer objects (including arrays)
The buffer object provides a set of methods that make it easier to use chunks of memory. The buffer object has built-in mechanisms for tracking and logging changes in the state of the buffer. Channel Provides a Channel for reading data from a file or network, but the data read or written must be read through Buffer, as shown in the figure
Buffer class and its subclasses
- In NIO, Buffer is a top-level parent class. It is an abstract class with a hierarchical diagram of the class:
- The Buffer class defines four attributes that all buffers have to provide information about the data elements they contain:
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
Copy the code
attribute | describe |
---|---|
Capacity | Capacity, that is, the maximum amount of data that can be held; Is set when the buffer is created and cannot be changed |
Limit | Represents the current end of the buffer. You cannot read or write to positions where the buffer exceeds its limit. And the limit can be modified |
Position | Position, the index of the next element to be read or written, which changes each time buffer data is read or written in preparation for the next read or write |
3. List of buffer methods
# JDK 1.4, Public Final Int Capacity () // Returns the capacity of the Buffer public Final Int Position () // Returns the position of the Buffer public Final Buffer Position (int Public Final Buffer Limit (int newLimit) public Final Buffer Limit (int newLimit) // Set the Buffer limit public Final Buffer clear() // Clear this Buffer, that is, each mark is restored to the initial state, Public Final Boolean hasRemaining() public Final Boolean hasRemaining() tells us whether there is any data between the current position and the limit Boolean isReadOnly() // tells whether the buffer is read-only //jdk1.6 introduced apis public Abstract Boolean hasArray() // tells whether the buffer has an accessible underlying implementation array public Abstract Object array() // Returns an array of the underlying implementations of this bufferCopy the code
ByteBuffer
As you can see from the previous section, there is a Buffer type for all basic data types in Java (except Boolean). The most commonly used is of course the ByteBuffer class (binary data), whose main methods are as follows
Public static ByteBuffer allocateDirect(int capacity) public static ByteBuffer allocate(int capacity) Public abstract byte get() public abstract byte get() Public abstract byte get(int index) public abstract ByteBuffer put(byte B) public abstract ByteBuffer put(byte B) Position automatically +1 public abstract ByteBuffer put(int index, byte B) // Put in absolute positionCopy the code
3.5 Channel (Channel)
Basic introduction
- NIO channels are similar to streams, but with some differences as follows:
- Channels can read and write simultaneously, while streams can only read or write
- Channels can read and write data asynchronously
- A channel can either read data from the buffer or write data to the buffer:
-
A STREAM in BIO is unidirectional. For example, a FileInputStream can only read data, while a Channel in NIO is bidirectional and can be read or written.
-
Public interface Channel extends Closeable{}
-
Common Channel classes include:
- FileChannel
- DatagramChannel
- ServerSocketChannel
- SocketChannel
+ ServerSocketChanne = ServerSocket, SocketChannel = Socket
-
FileChannel is used for file data reading and writing, DatagramChannel is used for UDP data reading and writing, and ServerSocketChannel and SocketChannel are used for TCP data reading and writing.
-
Basic architecture
FileChannel class
FileChannel is used to perform I/O operations on local files
public int read(ByteBuffer dst), reads data from the channel and puts it into a bufferpublic int write(ByteBuffer src), writes the buffer's data to the channelpublic long transferFrom(ReadableByteChannel src, long position, long count)To copy data from the destination channel to the current channelpublic long transferTo(long position, long count, WritableByteChannel target)To copy data from the current channel to the destination channelCopy the code
FileChannel basic case
3.5.1 Writing Data to a Local File
Example: Write “Hello, how are you” to file01.txt using the ByteBuffer and FileChannel learned earlier
public class NIOFileChannel {
public static void main(String[] args) throws IOException {
String message="Hello, how are you";
// Create an output stream
FileOutputStream fileOutputStream = new FileOutputStream("d:\\a.txt");
// Get a channel through the output stream
FileChannel channel = fileOutputStream.getChannel();
// Create a buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// Put the message into the buffer
byteBuffer.put(message.getBytes());
/** * Before flip: * * position=15 * limit=1024 * capacity=1024 */
// Now to read the data in byteBuffer, a read/write switch is required
byteBuffer.flip();
/** * After flip * * position=0 * limit=15 * capacity=1024 */
// Write data to the channelchannel.write(byteBuffer); fileOutputStream.close(); }}Copy the code
3.5.2 Reading Data from local Files
Example: Write text from file01. TXT to the screen using ByteBuffer and FileChannel
public class NIOFileChannel02 {
public static void main(String[] args) throws IOException {
// Create an input stream for the file
File file = new File("d:\\a.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// Get the corresponding Channel through inputStream
FileChannel fileChannel = fileInputStream.getChannel();
// Create a buffer
ByteBuffer byteBuffer=ByteBuffer.allocate((int) file.length());
// The file channel reads data into the buffer
fileChannel.read(byteBuffer);
// Read the buffer array directly, the underlying hb byte array
//final byte[] hb; // Non-null only for heap buffers
byte[] array = byteBuffer.array();
String message = newString(array); System.out.println(message); }}Copy the code
3.5.3 File Copy
A Buffer is used to read and write files, and a FileChannel and the read and write methods are used to copy files
public class NIOFileChannel03 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true){
byteBuffer.clear();
Position =0 * limit=512 * capacity=512 */
int read = fileChannel01.read(byteBuffer);
Position =31 * limit=512 * capacity=512 */
if(read==-1) {/ / read
break;
}
byteBuffer.flip();
** position=0 * limit=31 * capacity=512 */
fileChannel02.write(byteBuffer);
Position =31 * limit=31 * capacity=512 */} fileInputStream.close(); fileOutputStream.close(); }}Copy the code
3.5.4 Copying a File transferFrom method
In the last example, ByteBuffer was used to copy files. Now, the transferFrom method of Channel is used to copy files directly
public class NIOFileChannel04 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("E:\\aa.docx");
FileChannel sourceChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("E:\\aa1.doc");
FileChannel destChannel = fileOutputStream.getChannel();
destChannel.transferFrom(sourceChannel,0,sourceChannel.size()); fileInputStream.close(); sourceChannel.close(); fileOutputStream.close(); destChannel.close(); }}Copy the code
3.6 Precautions and Details
3.6.1 bytebuffer abnormal
ByteBuffer supports typed PUT and GET. If put puts the data type, GET should use the corresponding data type to retrieve it. If not, a BufferUnderflowException may occur
public class NIOByteBufferPutGet {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(64);
// Put data in typed mode
buffer.putInt(10);
buffer.putLong(10L);
buffer.putChar('you');
buffer.putShort((short) 11);
/* limit = position; position = 0; mark = -1; * /
buffer.flip();
//System.out.println(buffer.getLong()); //BufferUnderflowExceptionSystem.out.println(buffer.getInt()); System.out.println(buffer.getLong()); System.out.println(buffer.getChar()); System.out.println(buffer.getShort()); }}Copy the code
3.6.2 readOnlyBuffer
The asReadOnlyBuffer method is used to get a read-only buffer that can only be used to read data
public class ReadOnlyBuffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(64);
for (int i = 0; i < 64; i++) {
buffer.put((byte)i);
}
buffer.flip();
/ / a read-only buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
while(readOnlyBuffer.hasRemaining()){ System.out.println(readOnlyBuffer.get()); }}}Copy the code
3.6.3 Modify the Out-of-heap Memory
NIO also provides MappedByteBuffer, which allows files to be modified directly in memory (memory outside the heap). The operating system does not need to copy the file once, but how to synchronize to the file is done by NIO
public class MappedByteBufferTest {
public static void main(String[] args) throws IOException {
// Since RandomAccessFile can be accessed anywhere in the file, we can use the RandomAccessFile class if we want to access only part of the file
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt"."rw");
FileChannel channel = randomAccessFile.getChannel();
/*** Parameter 1: filechannel.mapmode. READ_WRITE Used read/write mode * Parameter 2:0: start position that can be directly modified * Parameter 3:5: Is the size mapped to memory (not the index location), i.e. how many bytes of 1.txt are mapped to memory * the range can be directly modified is 0-5 * the actual type DirectByteBuffer */
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0.5);
// Map the data in the channel directly to the memory, modify the data in the memory, reduce a memory copy,
// Just do the map once
mappedByteBuffer.put(0, (byte)'H');
mappedByteBuffer.put(1, (byte)'9');
randomAccessFile.close();
System.out.println("Modified successfully ~"); }}Copy the code
3.6.4 Dispersion & polymerization
In actual development, when a channel is established and data is transmitted, a buffer may not be enough. In this case, we can use the buffer array for data transmission. In this case, there will be read dispersion and write aggregation
- Scattering: When writing data to buffer, use buffer array, write sequentially [scatter]
- 3. When reading data from a buffer, use an array of buffers.
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(8000);
// Bind the port to the socket and start it
serverSocketChannel.bind(inetSocketAddress);
// Create a ByteBuffer array to use for data transfer
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0]= ByteBuffer.allocate(5);
byteBuffers[1]= ByteBuffer.allocate(3);
// Waiting for the client to connect, this will block
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength=8;
while (true) {int byteRead=0;
while (byteRead<messageLength){
// The number of reads
// When reading, pass in a buffer array
long l = socketChannel.read(byteBuffers);
byteRead+=l; // Total number of bytes read
System.out.println("ByteRead="+byteRead);
Arrays .asList(byteBuffers)
.stream()
.map(buffer->"position="+buffer.position()+",limit="+buffer.limit())
.forEach(System.out::println);
}
// Flip all buffers
Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.flip());
// Display the data to the client
long byteWrite=0;
while (byteWrite<messageLength){
long l = socketChannel.write(byteBuffers);
byteWrite+=l;
}
// Clear all buffers
Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.clear());
System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWrite + ", messagelength"+ messageLength); }}}Copy the code
The client
The service side
3.7 Selector
3.7.1 Basic Introduction
-
Java NIO, with non-blocking IO. You can have one thread, handle multiple client connections, and use a Selector.
-
The Selector can detect if an event has occurred on multiple registered channels (note that multiple channels can be registered to the same Selector as an event), and if an event has occurred, it retrieves the event and handles each event accordingly. This makes it possible to manage multiple channels with a single thread, that is, to manage multiple connections and requests.
-
Reading and writing occurs only when a connection/channel actually has a read or write event, greatly reducing the overhead and eliminating the need to create a thread for each connection and maintain multiple threads
-
Avoids the overhead of context switching between multiple threads
3.7.2 Schematic diagram and description of features
-
Netty’s IO thread NioEventLoop aggregates selectors (also called multiplexers) and can process hundreds or thousands of client connections concurrently.
-
When a thread reads or writes data from a client Socket channel, it can perform other tasks if no data is available.
-
Threads typically spend idle time of non-blocking IO performing IO operations on other channels, so a single thread can manage multiple input and output channels.
-
Since both read and write operations are non-blocking, this can greatly improve the efficiency of THE I/O thread and avoid thread suspension due to frequent I/O blocking.
-
A single I/O thread can concurrently process N client connections and read/write operations, which fundamentally solves the traditional synchronous blocking I/O connection-thread model, and greatly improves the performance, flexibility and reliability of the architecture.
3.7.3 Selector Related methods of a class
The Selector class is an abstract class with the following methods:
3.8 NIO principle analysis diagram
NIO non-blocking network programming related (Selector, SelectionKey, ServerScoketChannel Socket Channel) relationship comb diagram1) When the client connects, it gets the socket channel through the server socket channel
2) The selector listens to the selection method and returns the number of channels with events.
3) Register socketchannels with a Selector (Selector sel, int OPS). Multiple Socketchannels can be registered with a Selector
4) After registration returns a SelectionKey, which is associated with that Selector.
5) Further obtain each SelectionKey (events occur)
Select SocketChannel () from SelectionKey
7) Business processing can be completed through the obtained channel
3.9 NIO Quick Start
Write a NIO starter case to implement simple data communication between server and client (non-blocking)
The service side
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws IOException {
//1. Create ServerSocketChannel, which is the server channel
SocketChannel is generated for each client
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2. Create the most important Selector object to manage various connection requests
Selector selector = Selector.open();
// Bind port 6666 to listen on the server
serverSocketChannel.bind(new InetSocketAddress(6666));
// Set to non-blocking mode
serverSocketChannel.configureBlocking(false);
//3. Register with selector, which is used by the select
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {The select method is a blocking method and must have at least one chanel selected to return
if(selector.select(1000) = =0){
System.out.println("Server waiting one second, no connection");
continue;
}
// If >0 is returned, the associated selectionKey collection is retrieved
// 1. If >0 is returned, the concerned event has been obtained
// 2.selector. SelectedKeys () returns a collection of concerned events
// Get the channel backwards through selectionKeys
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
// The selected key is traversed, through which the channel can be obtained in reverse
while (keyIterator.hasNext()){
SelectionKey key = keyIterator.next();
// Handle the events on the channel corresponding to the key
// If it is OP_Accept, a new connection client arrives
if(key.isAcceptable()){
// Generate a socketChannel for the client
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("Client connected successfully, generated a socketChannel"+socketChannel.hashCode());
// Because our ServerSocketChannel is non-blocking, we generate a new socketChannel
// Also set to non-blocking
socketChannel.configureBlocking(false);
// After a new channel is generated, it is registered with the selector so that the selector can listen for events
// Listen for read events
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
// Get the channel back by key
SocketChannel socketChannel = (SocketChannel)key.channel();
// Get the buffer back by key
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
// Read the contents of the channel into the buffer
socketChannel.read(byteBuffer);
System.out.println("From client :"+new String(byteBuffer.array()));
}
// When we finish processing a channel, we remove the current channel to prevent repeated operationskeyIterator.remove(); }}}}Copy the code
The client
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1".6666);
if(! socketChannel.connect(inetSocketAddress)){while(! socketChannel.finishConnect()){ System.out.println("Because the connection takes time, the client doesn't block and can do other work..."); }}// The connection is successful and data is sent
String str="Hello, how are you"; ByteBuffer buffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(buffer); System.in.read(); }}Copy the code
3.10 SelectionKey
- SelectionKey, which represents the registration relationship between Selector and network channel. There are four types:
Int OP_ACCEPT: new network connections can be accepted. The value is 16
Int OP_CONNECT: indicates that a connection has been established. The value is 8
Int OP_READ: indicates a read operation. The value is 1
Int OP_WRITE: indicates the write operation. The value is 4
Source code:
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
Copy the code
3.11 ServerSocketChannel
-
ServerSocketChannel Listens for new client Socket connections on the server. Its main responsibility is to generate a socketChannel for each new client
-
Relevant methods
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel { Public static ServerSocketChannel Open () public Final ServerSocketChannel Bind (SocketAddress local) // Set blocking or non-blocking mode public abstract SelectableChannel configureBlocking(Boolean block) // Accept a connection, Return the channel object to represent the connection // If the channel is in non-blocking mode, this method will return immediately when no connection is pending. Public abstract SocketChannel Accept () registers a selector and sets the listener to listen for events. Public final SelectionKey register(Selector SEL, int ops)}Copy the code
3.12 a SocketChannel
- SocketChannel, network IO channel,
Specifically responsible for reading and writing operations
. NIO writes buffer data to the channel, or reads data from the channel to the buffer. The main job is to exchange data
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {// Get a SocketChannel. Public static SocketChannel Open () public final SelectableChannel ConfigureBlocking (Boolean block) Public Abstract Boolean connect(SocketAddress remote) // If the above method fails to connect, Public abstract Boolean finishConnect()}Copy the code
3.13 NIO builds group chat system
-
- Write a NIO group chat system to achieve simple data communication between the server and the client (non-blocking)
-
- Achieve multi-crowd chat
-
- Server side: can monitor the user online, offline, and achieve message forwarding function
-
-
Client: the channel can send messages to all other users without blocking, and can accept messages sent by other users (forwarded by the server).
-
Server code
package com.atguigu.groupchat; /** * 1) Write a NIO group chat system, to achieve simple data communication between the server and the client (non-blocking) * 2) to achieve multi-crowd chat * 3) server: can monitor the user online, offline, and achieve message forwarding function * 4) client: */ import java.io.IOException; */ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; * <p> * 1. Open a server to listen on the port * 2. Once the client has connected, a channel is created to listen * 3. A notification of going online is displayed on the server * 4. */ public class GroupChatServer {// Define the attribute, Define the most important components on the server. // Selector private ServerSocketChannel listenChannel; Private static final int PORT = 5555; Public GroupChatServer() {// In the constructor, construct the message try {// The most important steps to create a server object: // Step 1: both the server and the client must create a selector = select.open (); ListenChannel = ServerSocketChannel.open(); listenChannel = ServerSocketChannel.open(); // Step 3: Socket ():Retrieves a server socket associated with this channel. // Socket corresponding to the server channel, Listenchannel.socket ().bind(new InetSocketAddress(PORT)); / / must be set to a non-blocking mode, otherwise it will report abnormal listenChannel. ConfigureBlocking (false); Listenchannel. register(selector, selectionkey. OP_ACCEPT); } catch (IOException e) { e.printStackTrace(); Public void Listen () {public void listen() {public void listen() {public void listen() { Make an infinite loop while (true) {// it blocks until an event generates int count = selector. Select (); If (count > 0) {// In the selector object, Iterator<SelectionKey> Iterator = selection.selectedKeys ().iterator(); While (iterator.hasnext ()) {SelectionKey SelectionKey = iterator.next(); / / if the current is connected events, to a new channel for the client object if (selectionKey. IsAcceptable ()) {/ / here will accept for client connection events, a new socket channel / / not blocked here, SocketChannel = listenChannel.accept(); SocketChannel = listenChannel.accept(); / / set the current channel object to a non-blocking socketChannel. ConfigureBlocking (false); Socketchannel. register(selector, selectionkey.op_read); / / online tip: System. Out. Println (socketChannel. GetRemoteAddress () + "has been launched to"); If (selectionKey.isreadable ()) {readData(selectionKey); } // Remove the current key iterator.remove(); } } } } catch (IOException e) { e.printStackTrace(); Private void readData(selectionKey selectionKey) {// Select * from selectionKey; SocketChannel = null; SocketChannel = null; Channel = (SocketChannel) selectionkey.channel (); ByteBuffer = ByteBuffer. Allocate (1024); int readCount = channel.read(byteBuffer); If (readCount>0){String MSG = new String(bytebuffer.array ()); System.out.println("from "+ channel.getremoteAddress ()+" say: "+ MSG); SendMsgToOtherClient (MSG,channel); } } catch (IOException e) { e.printStackTrace(); Try {//?? Question: why is it possible to obtain the read event when the current client is offline? * java.io.IOException: * java.io.IOException: * java.io. The remote host forced an existing connection down. * */ system.out.println (channel.getremoteAddress ()+" offline ~"); Selectionkey.cancel (); selectionkey.cancel (); // Close the channel channel.close(); } catch (IOException ex) { ex.printStackTrace(); Private void sendMsgToOtherClient(String MSG, private void sendMsgToOtherClient(String MSG, private void sendMsgToOtherClient)) SocketChannel channel) throws IOException {system.out. println(" In the message forwarded by the server... ); // Get the SocketChannel registered to the selector, and exclude Self for (SelectionKey selectedKey: Selectedkey.channel (); selectedKey.channel(); If (targetChannel instanceof SocketChannel && targetChannel! DestChannel = (SocketChannel) targetChannel; ByteBuffer = bytebuffer.wrap (msg.getBytes()); Destchannel. write(byteBuffer); destchannel. write(byteBuffer); } } } public static void main(String[] args) { GroupChatServer groupChatServer = new GroupChatServer(); groupChatServer.listen(); }}Copy the code
Client code
package com.atguigu.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
public class GroupChatClient {
private final String HOST="127.0.0.1";
private final Integer PORT=5555;
private Selector selector;
private SocketChannel socketChannel;
private String userName;
// constructor
public GroupChatClient(a) throws IOException {
The first thing to do, whether it's a server or a client, is to create a Selector object
selector = Selector.open();
// Connect to the server
socketChannel= SocketChannel.open(new InetSocketAddress(HOST,PORT));
// Set the socket channel to non-blocking
socketChannel.configureBlocking(false);
// Register the current channel
socketChannel.register(selector, SelectionKey.OP_READ);
// Obtain the user information of the client
userName = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(userName+" is ok");
}
// The method of sending the message
public void send(String msg){
msg=userName+"Say:"+msg;
try {
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(byteBuffer);
} catch(IOException e) { e.printStackTrace(); }}// Read the message from the server
public void read(a){
try {
// Get a readable channel
int selectCount = selector.select();
if(selectCount>0) {// Get all selectionkeys for the current selection
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
// If the current key type is readable
if(selectionKey.isReadable()){
// Get the corresponding channel
SocketChannel channel = (SocketChannel) selectionKey.channel();
// Build a buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// Read data from the channel into buffer
channel.read(byteBuffer);
// Display data
System.out.println(newString(byteBuffer.array())); } iterator.remove(); }}}catch(IOException e) { e.printStackTrace(); }}public static void main(String[] args) throws IOException {
// Create a client
GroupChatClient groupChatClient = new GroupChatClient();
// Create a thread to read data from the server
new Thread(() -> {
while (true){
groupChatClient.read();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// Create an input stream
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){ String line = scanner.nextLine(); groupChatClient.send(line); }}}Copy the code
Start the server and two clients
One of the clients does the input
Server display
Another client display
3.14 NIO and zero copy
Zero copy is the key to network programming, and many performance optimizations are required.
Let’s start by looking at how the IO model evolves step by step. Okay
3.14.1 Traditional I/O model
DMA: Direct memory access
In the traditional IO model, there are 3 context switches and 2 Cpu copies
The diagram flow is as follows:
- From disk, copy to kernel buffer using direct memory
- Memory buffers are copied into user-mode buffers using the CPU
- After the service is processed, it is copied to the socket buffer using the CPU
- Finally, Copy to the protocol stack using DMA Copy
3.14.2 mmap optimization
Mmap maps files to the kernel buffer through memory mapping, and the user space can share data in the kernel space. This reduces the number of copies from kernel space to user space during network transfers
3.14.3 sendFile optimization
The sendFile function is provided in Linux version 2.1. The basic mechanism is as follows: data goes directly from the kernel Buffer to the Socket Buffer without going through the user state at all. At the same time, it has nothing to do with the user state, thus eliminating a context switch
3.14.4 sendFile was optimized again
In Linux 2.4, some changes were made to avoid copying from the kernel buffer to the Socket buffer, and instead copy directly to the protocol stack, again reducing data copying
The CPU copied the kernel buffer -> socket buffer. However, the information copied was very small, such as lenght and offset, so the consumption was low and could be ignored
3.14.5 Understanding zero copy again
-
When we say zero copy, we’re talking about it from an operating system perspective. No data is duplicated between kernel buffers (only the kernel buffer has one copy of data).
-
Not only does zero copy result in less data replication, it also brings other performance benefits, such as fewer context switches, fewer CPU cache pseudo-shares, and cpuless checksum computation.
3.14.6 Difference between Mmap and sendFile
-
Mmap is suitable for reading and writing small amounts of data, while sendFile is suitable for transferring large files.
-
Mmap requires 4 context switches and 3 data copies; SendFile requires 3 context switches and at least 2 data copies.
-
SendFile can use DMA to reduce CPU copying, mmap cannot (must be copied from kernel to Socket buffer)
3.14.7 NIO Zero Copy Case
Server code
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NIOServer {
public static void main(String[] args) throws IOException {
// Create a port to listen on
InetSocketAddress inetSocketAddress = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(inetSocketAddress);
// Create a buffer to receive
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true) {
// Receive requests from clients
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while(readCount ! = -1) {
try
{
readCount = socketChannel.read(byteBuffer);
}catch (Exception ex){
break;
}
// Restore the buffer to its default statebyteBuffer.rewind(); }}}}Copy the code
Client code
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1".7001));
String fileName="FSCapture90.zip";
FileChannel channel = new FileInputStream(fileName).getChannel();
long startTime = System.currentTimeMillis();
// Under Linux a transferTo method can do the transfer
// Under Windows, a call to transferTo can only send 8m, so the file needs to be transferred in segments, and mainly
long transferCount= channel.transferTo(0, channel.size(), socketChannel);
long endTime = System.currentTimeMillis()-startTime;
System.out.println("Total number of bytes sent =" + transferCount + "When consumption."+ (System.currentTimeMillis() - startTime)); channel.close(); }}Copy the code
Compared with traditional IO, this method is more efficient
Chapter 4 Overview of Netty
4.1 Problems existing in native NIO
-
NIO class library and API complex, troublesome to use: need to master Selector, ServerSocketChannel, SocketChannel, ByteBuffer and so on.
-
Additional skills are required: Familiarity with Java multithreaded programming, as NIO programming involves the Reactor pattern, and you must be familiar with multithreaded and network programming to write high quality NIO programs.
-
The workload and difficulty of development are very large: for example, the client is faced with disconnection and reconnection, network intermittent disconnection, half-packet read and write, failure cache, network congestion and abnormal flow processing, etc.
-
JDK NIO bugs: Such as the infamous Epoll Bug, which causes Selector polling to be empty, eventually causing the CPU to be 100%. Until JDK 1.7, the problem persisted and was not fundamentally resolved.
4.2 Description of the Netty official website
Liverpoolfc.tv: netty. IO /
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients
4.3 Netty Advantages
Netty encapsulates the NIO API of the JDK to solve the above problems.
-
Elegant design: uniform API blocking and non-blocking sockets for all transport types; Clear separation of concerns based on flexible and extensible event models; Highly customizable threading model – single thread, one or more thread pools.
-
Easy to use: well-documented Javadoc, user guides, and examples; Without other dependencies, JDK 5 (Netty 3.x) or 6 (Netty 4.x) will suffice.
-
High performance, higher throughput: lower latency; Reducing resource consumption; Minimize unnecessary memory replication.
-
Security: Full SSL/TLS and StartTLS support.
-
Active community, constant updates: active community, short version iteration cycle, bugs found can be fixed in time, and more new features will be added
4.4 Netty Version Description
-
The netty version includes Netty3. x, netty4.x, and netty5.x
-
Because Netty5 has a major bug, it has been abandoned by the official website. Currently, we recommend using the stable version of Netty4.x
-
X netty4.0.x and netty4.1.x are available for download on the official website
-
Netty download: bintray.com/netty/downl…
Chapter 5 Netty high-performance architecture design
5.1 Basic introduction to the threading model
-
Different thread mode, has a great impact on the performance of the program, in order to understand the Netty thread mode, we will systematically explain each thread mode, and finally look at the Netty thread model has what advantages.
-
The existing threading models are: traditional blocking I/O service Model Reactor schema
-
According to the number of reactors and the number of threads in the resource pool, there are three typical implementations of single Reactor single thread. Single Reactor multithreading; Reactor is multithreaded
-
Netty threading model (Netty mainly made some improvements based on the master/slave Reactor multithreading model, which has multiple reactors)
5.2 Traditional blocking I/O service model
5.2.1 Working principle diagram
5.2.2 Model features
-
Use blocking IO mode to get input data
-
Each connection requires a separate thread for data input, business processing, and data return
5.2.3 Problem analysis
1) When the number of concurrent requests is large, a large number of threads will be created, occupying a large amount of system resources
After the connection is created, if the current thread has no data to read temporarily, the thread will block in the read operation, resulting in a waste of thread resources
5.3 Reactor model
5.3.1 Solutions for the two disadvantages of tradition
-
Based on the I/O multiplexing model: multiple connections share a blocking object, and applications only need to wait on one blocking object, instead of blocking for all connections. When a connection has new data to process, the operating system notifies the application, and the thread returns from the blocked state to process the Reactor. 2. Dispatcher mode 3. Notifier Mode
-
Thread pool-based reuse of thread resources: Instead of creating threads for each connection and assigning business processing tasks to threads after the connection is completed, one thread can process business for multiple connections
5.3.2 Basic design idea of Reactor model
Reuse and thread pool are the basic design ideas of Reactor model
As shown in figure
-
Reactor pattern, a pattern that passes one or more inputs simultaneously to the service processor (event-driven)
-
The Reactor pattern is also called the Dispatcher pattern because a server-side program processes incoming requests and dispatches them synchronously to the appropriate processing thread
-
The Reactor model uses IO to reuse listening events and distribute them to a thread (process) after receiving the events, which is the key to high concurrency processing of network servers
5.3.3 Core components in the Reactor model:
-
Reactor: THE Reactor runs in a separate thread that listens for and distributes events to the appropriate handlers to react to IO events. It is like a corporate telephone operator, which takes calls from customers and redirects the line to the appropriate contact;
-
Handlers: The actual event to which the I/O event is executed by the program, similar to a staff member in the company with whom the customer wants to talk. Reactor responds to I/O events by scheduling appropriate handlers that perform non-blocking actions.
5.3.4Reactor Model Classification:
There are three typical implementations, depending on the number of reactors and the number of resource pool threads that are processed
-
Single Reactor Single thread
-
Single Reactor multithreading
-
Reactor is multithreaded
5.4 Single Reactor Single Thread
Schematic diagram
5.4.1 Solution Description
-
Select is a standard network programming API introduced in the I/O multiplexing model earlier that enables applications to listen for multiple connection requests through a blocking object
-
The Reactor object monitors client request events through Select and distributes events through Dispatch
-
In the case of a connection request event, the Acceptor processes the connection request through Accept, and then creates a Handler object to handle subsequent business processing after the connection is completed
-
If the connection is not established, the Reactor dispatches the Handler that calls the connection in response
-
The Handler completes the complete business process of Read→ business process →Send
Combining examples: the server side with a thread through multiplexing all IO operations (including connection, read, write, etc.), simple coding, clear, but if the number of client connections, will not be able to support, the previous NIO case belongs to this model.
5.4.2 Analysis of advantages and disadvantages of the scheme
-
Advantages: simple model, no multithreading, process communication, competition, all in a thread to complete
-
Disadvantages: Performance issues, only one thread, can not fully play the performance of multi-core CPU. When a Handler processes services on a connection, the entire process cannot process other connection events, resulting in performance bottlenecks
-
Disadvantages: Reliability problems, unexpected thread termination, or into an infinite loop, the entire system communication module is unavailable, can not receive and process external messages, resulting in node failure
-
Usage scenario: The number of clients is limited and business processing is very fast, such as the time complexity O(1) of Redis business processing
5.5 Single-reactor Multithreading
5.5.1 schematic diagram
5.5.2 Summary of the figure above
-
The Reactor object monitors client request events through SELECT. Upon receiving events, the Reactor object distributes them through Dispatch
-
If a connection request is established, the right Acceptor processes the connection request with Accept, and then creates a Handler object to handle the various events after the connection is completed
-
If it is not a connection request, it is handled by the reactor distribution call handler corresponding to the connection
-
Handler is only responsible for responding to events, not specific business processing. After reading data through read, it is distributed to a thread in the following worker thread pool to process the business
-
The worker thread pool allocates independent threads to complete the real business and returns the results to the handler
-
After receiving the response, the Handler sends the result to the client
5.5.3 Advantages and disadvantages of the scheme:
-
Advantages: It makes full use of the processing power of the multi-core CPU
-
Disadvantages: Multithreaded data sharing and access is complex, reactor processes all the event monitoring and response, running in a single thread, prone to performance bottlenecks in high concurrency scenarios.
5.6 Principal/Slave Reactor Multithreading
5.6.1 Working principle diagram
In the single-reactor multithreading model, Reactor runs in a single thread, which is easy to become a performance bottleneck in high concurrency scenarios. Therefore, THE Reactor can be run in multithreading
5.6.2 Scheme description in the figure above
-
Reactor Main Thread The MainReactor listens for connection events through select and processes connection events through acceptors
-
When an Acceptor processes a connection event, the MainReactor assigns the connection to the SubReactor
-
The Subreactor adds connections to the connection queue for listening and creates handlers for various events
-
When a new event occurs, the Subreactor calls the corresponding handler
-
Handler reads the data via read and distributes it to subsequent worker threads for processing
-
The worker thread pool allocates separate worker threads for business processing and returns results
5.6.4 Advantages and disadvantages of the solution
-
Advantages: The responsibilities of the parent thread and child thread are simple and clear. The parent thread only needs to receive new connections, and the child thread completes the subsequent service processing.
-
Advantages: Simple data interaction between parent thread and child thread, Reactor main thread only needs to pass new connection to child thread, child thread does not need to return data.
-
Disadvantages: High programming complexity
-
Combined examples: This model is widely used in many projects, including support for the Nginx Master-slave Reactor, Memcached master-slave, and Netty master-slave models
5.7 Reactor Model summary
5.7.13 modes are understood by life cases
-
Single Reactor single thread, receptionist and waitress are the same person, the whole process for customer service
-
Single Reactor multithreading, 1 receptionist, multiple waiters, the receptionist is only responsible for reception
-
Reactor multithreaded, multiple receptionists, multiple servers
5.7.2 The Reactor model has the following advantages
-
Fast response and not blocked by a single synchronization time, although the Reactor itself is still synchronous
-
It minimizes complex multithreading and synchronization issues and avoids multithreading/process switching overhead
-
It has good scalability and can make full use of CPU resources by increasing the number of Reactor instances
-
The Reactor model itself is independent of the specific event processing logic and has high reusability
5.8 Netty model
5.8.1 Schematic diagram of Working Principle 1- Simple version
Netty mainly improves the multithreaded model of Master/slave Reactors (see figure) to some extent. There are multiple Reactors in the multithreaded model of master/slave Reactors
5.8.2 Description of the figure above
-
The client initiates a connection request to the server
-
The BossGroup thread maintains the Selector, cares only about the Accecpt event, and cares nothing else. When it receives the Accept event, it gets the corresponding SocketChannel, generating a SocketChannel object
-
SocketChannel is encapsulated as a NIOScoketChannel for later use
-
Register the NIOScoketChannel channel with the Worker thread (event loop) and maintain it
-
When the Worker thread listens to the selector channel for an event that it is interested in, it processes it (by handler), noting that the handler has been added to the channel
5.8.3 Schematic diagram of working principle 2- Advanced version
-
Abstract handles two types of thread pools, BossGroup and WorkGroup
-
Each Group has a NIOEventLoop that is constantly listening for threads registered with the selector
-
BossGroup receives the thread and dispatches it to the WorkGroup
5.8.4 Schematic diagram of Working Principle 3- Final version
5.8.5 Summary of the illustration above
-
Netty abstracts two groups of thread pools BossGroup for receiving client connections and WorkerGroup for reading and writing network connections
-
Both BossGroup and WorkerGroup types are NioEventLoopGroup
-
A NioEventLoopGroup is equivalent to a group of event loops containing multiple event loops, each of which is a NioEventLoop
-
A NioEventLoop represents a thread that executes processing tasks in a continuous loop. Each NioEventLoop has a selector that listens for network traffic from the socket tied to it
-
A NioEventLoopGroup can have multiple threads, that is, it can have multiple NioEventLoops
-
Each Boss NioEventLoop executes three steps
- Poll the Accept event
- Process the Accept event, establish a connection with the client, generate a NioScocketChannel, and register it with a selector on a worker NIOEventLoop
- Tasks that process the task queue, namely runAllTasks
- Each Worker NIOEventLoop executes a step
- Poll read, write events
- Handle I/O events, that is, read and write events, in the corresponding NioScocketChannel processing
- Tasks that process the task queue, namely runAllTasks
- Pipeline is used for each Worker NIOEventLoop to process business. The pipeline contains channels, that is, the corresponding channels can be obtained through the pipeline, and many processors are maintained in the pipeline
5.8.6Netty QuickStart Instance -TCP Service
-
The Netty server listens on port 6668, and the client can send a message to the server “Hello, server ~”
-
The server can reply to the client with a message “Hello, client ~”
NettyServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// Create BossGroup and WorkerGroup
/ / that
Create two thread groups: bossGroup and workerGroup
// 2. BossGroup only handles connection requests. Real and client business processing is handed over to workerGroup
// 3. Both are infinite loops
// 4. Number of nioeventloops in bossGroup and workerGroup
// The number of actual CPU cores is 2 by default
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try
{
// Set the parameters when creating the server
// A bootstrap class is used to set parameters
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup) // There are two workgroups on the Netty server
.channel(NioServerSocketChannel.class) // The current channel uses NioServerSocketChanel as the server implementation
.option(ChannelOption.SO_BACKLOG,128)// Set the number of connections to the thread queue
.childOption(ChannelOption.SO_KEEPALIVE,true) // Set the connection to remain active
.childHandler(new ChannelInitializer<SocketChannel>() { // Create a channel initialization object
// Set the handler to pipeline
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(newNettyServerHandler()); }}); System.out.println("Server is ready...");
// Bind a port and synchronize, generating a ChannelFuture object to start the server
ChannelFuture cf = bootstrap.bind(6668).sync();
// Listen for closed channels
cf.channel().closeFuture().sync();
}finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); }}}Copy the code
NettyServerHandler
/** * Define a channel inbound adapter */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// Reads the message sent from the client and calls this method as soon as there is a message
ChannelHandlerContext The ChannelHandlerContext object, which contains pipes, addresses, and channels
// Channel: is the data transfer between the server and the client, pipeline: is the Handler chain collection of business logic
// MSG is the message sent by the client
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Server read thread:"+Thread.currentThread().getName());
System.out.println("server ctx="+ctx);
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline(); // It is a two-way link, outbound, inbound
// convert MSG to a byteBuf
ByteBuf is provided by Netty, not NIO's ByteBuffer.
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("The client sends the following message:"+byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("The client address is:"+channel.remoteAddress());
}
// The data has been read
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, client",CharsetUtil.UTF_8));
}
// To handle exceptions, it is usually necessary to close the channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); }}Copy the code
NettyClient
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
// As a client, it is simpler
// First create an event loop group to keep the connection to the server
NioEventLoopGroup group = new NioEventLoopGroup();
try{
// Create a client boot object to generate a Netty client
// The server uses -serverBootstrap
Bootstrap bootstrap = new Bootstrap();
// After creating a bootStrap object, you can set the parameters of the object
bootstrap
.group(group)The first step is to set up an event loop group
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() { // The second step is to set the handler
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(newNettyClientHandler()); }});// After the boot class is set, bind the port
// ChannelFuture involves netty's asynchronous model
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1".6668).sync();
// Set listener for channel closure,?? Where do I write the code to listen?
channelFuture.channel().closeFuture().sync();
}finally{ group.shutdownGracefully(); }}}Copy the code
NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
// This method is triggered when the channel is ready
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client:"+ctx);
// When ready, send a message to the server
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello server", CharsetUtil.UTF_8));
}
// There is a read event in the channel
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf= (ByteBuf) msg;
System.out.println("Server reply message:"+ byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("Server address:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}Copy the code
The service side
- The default number of workGroup threads is: Number of CPU cores x 2. If the number of local CPU cores is 4, the number of workGroup threads is 8
- Each time a client establishes a connection, it allocates a thread for processing
- When the ninth client arrives, it starts processing from scratch, using the first thread
The client