BIO and NIO

  • Java Blocking API The early Java API (Java.net) only supported so-called blocking functions provided by the local system socket library. The server listens for connection events until the connection is established, and then returns a new Socket for communication between the client and the server. Accessors such as readLine() block until a string is read.

Disadvantages: 1. At any time, there may be a large number of threads in sleep state, just waiting for input or output data, resulting in resource waste. 2, need to allocate memory for each thread, concurrency is easy to reach the system bottleneck.

  • Java non-blocking apis in addition to BIO, the native socket library also provides non-blocking calls. Java support for non-blocking I/O was introduced in 2002 in the Java nio package in JDK1.4. Setsockopt () can be used to configure the socket to return immediately if there is no data to read or write. 2. You can use the operating system’s event notification API to register a set of non-blocking sockets and determine if any of them already have data to read and write.
Basic concepts of Netty
  • A channel, a basic construct of Java NIO, can be thought of as a carrier for incoming and outgoing data that can be turned on or off, connected or disconnected.

  • A callback is a method that passes a reference to one method to another method for it to call. Netty internally uses callbacks to handle events, such as ChannelHandler’s channelActive() method being called when a new connection is established.

  • Future Future provides another way to notify an application when an operation is complete. This object is a placeholder for the result of a one-step operation that will be completed at some point in the future and provides access to its results. The interface Java JDK preset. Utils. Concurrent. The Future, but its implementation, provided only allow manual check corresponding operation is completed, or block until it completes. Netty provides an implementation of it, ChannelFuture, for use when performing asynchronous operations. ChannelFuture provides several additional methods that enable us to register one or more instances of ChannelFutureListener. The listening callback method operationComplete() will be called when the corresponding operation completes. The listener can then determine whether the operation completed successfully or failed. If there is an error, we can retrieve the resulting Throwable. This eliminates the need to manually check whether the operation is complete. Each Netty outbound I/O operation returns a ChannelFuture; In other words, they don’t block. Netty is completely asynchronous and event-driven.

  • Events Netty uses different events to notify us of state changes or the status of operations. Allows us to trigger actions based on events that have already occurred.

  • ChannelHandler Netty’s ChannelHandler provides the basic abstraction for the processor. Netty provides a number of defined ChannelHandler implementations out of the box, including ChannelHandler for various protocols. Internally, ChannelHandlers themselves use events and futures, and yes, they become consumers of the same abstractions your application will use.

  • EventLoop Netty abstracts the Selector from the application by firing events, eliminating all distribution code that would otherwise need to be written manually. Internally, an EventLoop will be assigned to each Channel to handle all events.

The core components of Netty
  • The basic I/O operations of a channel interface (bind(), connect(), read(), and write()) rely on primitives provided by the underlying network transport. In Java network programming, the basic construct is the Class Socket. The API provided by Netty’s Channel interface greatly reduces the complexity of using the Socket class directly. In addition, channels are the root of an extensive class hierarchy with many predefined, specialized implementations, and a short partial listing follows. EmbeddedChannel, LocalServerChannel, niodata agramchannel, NioSctpChannel, NioSocketChannel
  • EventLoop Interface A Netty core abstraction is defined in EventLoop to handle events that occur during the lifetime of a connection. Relationships among channels, EventLoop, Thread, and EventLoopGroup
    • An EventLoopGroup contains one or more Eventloops
    • An EventLoop is bound to only one Thread during its lifetime
    • All I/O events processed by EventLoop will be processed on its proprietary Thread
    • A Channel registers with only one EventLoop during its lifetime
    • An EventLoop may be assigned to one or more Chennel.
  • ChannelFuture Interface All I/O operations on the Netty are asynchronous. Because an operation may not return immediately, we need a way to determine its outcome at a later point in time. Netty provides the ChannelFuture interface for this purpose, and its addListener() method registers a ChannelFutureListener to be notified when an operation completes (successfully or not)
  • From an application developer’s perspective, the main component of Netty is the ChannelHandler, which acts as a container for all the application logic that handles inbound and outbound data. This is possible because the ChannelHandler method is triggered by a network event.
  • ChannelPipeline

The ChannelPipeline provides a container for the ChannelHandler chain and defines an API for propagating inbound and outbound event flows on that chain. When a Channel is created, it is automatically assigned to the proprietary ChannelPipeline

ByteBuf API

Netty’s data processing API is exposed through two components –abstract class ByteBuf and Interface ByteBufHolder. Here are some advantages of the ButeBuf API:

  • It can be extended by user-defined buffer types.
  • Through the built-in compound buffer type is absorbed transparent zero copy.
  • Easy to grow on demand (similar to JDK StringBuilder)
  • Switching between read and write modes does not require calling the flip() method of ByteBuffer
  • Read and write use different indexes
  • Supports chained invocation of methods
  • Support for reference counting
  • Support the pooling
ByteBuf class -Netty data container

1. How ByteBuf works ByteBuf maintains two different indexes: one for reading and one for writing. When read from BytebBuf, readerIndex is incremented by the number of words in the read area. Similarly, when you write ByteBuf, its writeIndex is incremented. A common ByteBuf pattern for storing data in the JVM’s heap space is called a backing array, which provides quick allocation and deallocation without using pooling. Direct buffer: The contents of the direct buffer reside outside of the regular heap that is garbage collected. The downside is that they are expensive to allocate and release compared to heap-based buffers. Compound buffer: Provides an aggregated view for multiple BytebuFs. You can also remove instances of ByteBuf, which is a feature completely missing from the JDK’s ByteBuffer.

Byte level operations

ButeBuf provides many ways to modify its data beyond basic read and write operations.

  • Random access indexes ByteBuf indexes range from 0 to capacity()-1

  • Sequential access index ByteBuf is divided into three extents-discarded bytes that have been read, and unread bytes for which more bytes can be added

  • Throwable Bytes Segments labeled throwable bytes contain bytes that have been read. They can be discarded, reclaiming space with a call to discardReadBytes(). The initial size of this segment, stored in readerIndex, is 0 and increments when the “read” operation is performed (the “get” operation does not move the readerIndex).

  • Readable bytes The “readable bytes” segment of ByteBuf stores actual data. The default value of readerIndex for newly allocated, wrapped, or copied buffers is 0. Any operation whose name begins with “read” or “skip” retrieves or skips the data in the current readerIndex and is incremented by the number of bytes read.

  • Index management defines the mark(int readLimit) and reset() methods in the JDK’s InputStream. These are used to mark the current position in the stream and reset the stream to that position, respectively. In the same way, You can set and reposition ByteBuf readerIndex and writerIndex by calling markReaderIndex(),markWriterIndex(),resetReaderIndex() and ResetWriterIndex (). These are similar to InputStream calls, except that there is no readlimit parameter to specify when flags become invalid. You can also move an indicator to a specified location by calling readerIndex(int) or writerIndex(int). On an index to any invalid position will lead to abnormal IndexOutOfBoundsException. A call to clear() sets both readerIndex and writerIndex to 0.

  • A query operation has several ways of determining the index of a specified value in the buffer. The simplest is to use the indexOf() method. More complex searches perform methods that take ByteBufProcessor as an argument. This interface defines a method, Boolean process(Byte Value), which reports whether the input value is a value being sought.

  • Read /write Operations Read /write operations fall into two main categories: gget()/set() operations start at a given index and remain unchanged Read ()/write() operations start at a given index, apply to the number of bytes accessed, and increment the current write or read index

  • ByteBufHolder Interface From time to time we encounter situations where we need to store various attribute values in addition to valid actual data. Netty provides ByteBufHolder to handle this common situation. ByteBufHolder also provides advanced features for Netty, such as buffer pools, where the ByteBuf that holds the actual data can be borrowed from the pool and automatically released if needed.

  • ByteBuf Allocation ByteBufAllocator To reduce the overhead of allocating and freeing memory, Netty supports the pool class ByteBufAllocator, which can be used to allocate instances of any of the ByteBuf types we have described. It is up to the application to decide whether to use a pool

  • Unpooled Cache If ByteBufAllocator is not referenced, the above method cannot access ByteBuf. For this use case Netty provides a utility class called Unpooled, which provides static helper methods to create Unpooled instances of ByteBuf.

  • ByteBufUtil ByteBufUtil static helper methods to manipulate ByteBuf, because the API is generic and has nothing to do with using pools, these methods are already implemented outside of the allocation class.

ChannelHandler
  • 1 channelUnregistered Channel is created but not registered with an Eventloop. 2 channelRegistered Channel is registered with an Eventloop. 3 The channelActive Channel is active (connected to the remote host) and can now receive and send data. 4 channelInactive Channel Is inactive and not connected to the remote host

  • ChannelHandler lifecycle 1 handlerAdded When channelHandler is added to ChannelPipeline 2 handlerRemoved when channelHandler is removed from Called when ChannelPipeline removes 3 exceptionCaught Called when ChannelPipeline throws an exception

  • ChannelHandler Subinterface Netty provides two major ChannelHandler subinterfaces: ChannelInboundHandler – Handles inbound data and all state change events ChannelOutboundHandler – handles outbound data, allowing various operations to be intercepted

  • Resource management When you are through ChannelInboundHandler. ChannelRead (…). Or ChannelOutboundHandler. Write (…). To process data, it is important to ensure that resources do not leak when processing them. To make it easier for users to find missed releases, Netty includes a ResourceLeakDetector that samples 1% of the allocated buffer to check for leaks in the application. Because 1% of the sample, the overhead is small.

ChannelPipeline

ChannelPipeline is a series of ChannelHandler instances. Inbound and outbound events flowing through a Channel can be intercepted by ChannelPipeline. ChannelPipeline allows users to handle their own inbound and outbound events. And the interaction between handlers in the pipeline.

Every time a new Channel is created, a new Channel pipeline is created, and the new Channel is bound to the Channel. The association is permanent; A Channel can neither attach to another Channel nor detach from the current one. This is all done by Netty, without the need for special handling by developers. A ChannelPipeline is used to save ChannelHandler associated with a Channel. The ChannelPipeline can be modified by dynamically adding and removing the ChannelHandler ChannelPipeline There are rich API calls to respond to inbound and outbound events.

ChannelHandlerContext

An instance is created when ChannelHandler is added to ChannelPipeline, the interface ChannelHandlerContext, which represents the association between ChannelHandler and ChannelPipeline. The ChannelHandlerContext interface manages the interactions between ChannelHandlers associated with the same ChannelPipeline

The ChannelHandlerContext contains a number of methods, some of which also appear in the Channel and ChannelPipeline itself. If you call these methods through a Channel or ChannelPipeline instance, they propagate throughout the pipeline. By contrast, the same method called on an instance of ChannelHandlerContext will simply start with the current ChannelHandler and propagate to the next ChannelHandler capable of handling events in the relevant pipeline.

EventLoop interface

Continuous events occurring over a continuous lifecycle is a fundamental function of any network framework. Netty uses interface IO.net ty.EventLoop. Netty uses two basic apis: concurrency and network programming. An EventLoop will be driven by a Thread that never changes, and tasks (Runnable or Callable) can be submitted directly to the EventLoop implementation for immediate or scheduled execution. Depending on the configuration and available cores, multiple Eventloop instances may be created to optimize resource usage, and a single Eventloop may be assigned to multiple channels.

Netty Decoder

Netty provides a rich abstract base class for decoders that can be easily implemented as custom decoders. There are two main categories:

  • Decoding byte to message (ByteToMessageDecoder and ReplayingDecoder)
  • The Decoder is responsible for converting “inbound” data from one format to another. Netty’s decoder is an abstract implementation of the ChannelInboundHandler. In practice, using a decoder is as simple as formatting the inbound data and passing it to the next ChannelInboundHandler in the ChannelPipeline for processing. This is very flexible, we can put decoders in ChannelPipeline, reuse logic.
Netty Encoder

Netty also provides a set of classes for you to write Encoder. These classes provide the exact opposite of decoder methods, as shown below:

Encoding from message to byte MessageToByteEncoder Encoding from message to message MessageToMessageEncoder

Netty abstract Codec classes

We talk about decoders and encoders as separate entities, but sometimes it is more useful to have both inbound and outbound data and information transformations in the same class. The abstract Codec classes in Netty do this by combining decoders and encoders in pairs to provide the same operations for both bytes and messages (these classes implement ChannelInboundHandler and ChannelOutboundHandler).

  • random
  • Advantages of ByteBuf over ByteBuffer
  • How to allocate and access memory used by ByteBuf.
  • Netty threading model
  • interface EventLoop

Custom codecs commonly used by protocols

  • Encoders and decoders
  • Netty encoders and decoders

Netty support for advanced application-layer protocols

  • How to use WebSocket protocol to realize bidirectional communication between Web server and client