跳至主要内容

Netty(4): Event, Channel, Buffer

Netty(4): Event, Channel, Buffer

In Netty, the source of data is not the abstraction in Java OIO, like Stream, but Event. One of obvious differences between Stream & Event is how we handle IO request. In OIO, we read/write from Stream and block if it is not available; In Netty, we are notified when corresponding event happens. Netty have much common in this design with Java NIO.

In this blog, we will dive into Netty's similarity between Java NIO: event, channel, buffer.

enter image description here

Event

The following is event propagation methods we have introduced in Handler part.

  • Inbound event propagation methods:
    • [ChannelHandlerContext.fireChannelRegistered()]
    • [ChannelHandlerContext.fireChannelActive()]
    • [ChannelHandlerContext.fireChannelRead(Object)]
    • [ChannelHandlerContext.fireChannelReadComplete()]
    • [ChannelHandlerContext.fireExceptionCaught(Throwable)]
    • [ChannelHandlerContext.fireUserEventTriggered(Object)]
    • [ChannelHandlerContext.fireChannelWritabilityChanged()]
    • [ChannelHandlerContext.fireChannelInactive()]
    • [ChannelHandlerContext.fireChannelUnregistered()]
  • Outbound event propagation methods:
    • [ChannelOutboundInvoker.bind(SocketAddress, ChannelPromise)]
    • [ChannelOutboundInvoker.connect(SocketAddress, SocketAddress, ChannelPromise)]
    • [ChannelOutboundInvoker.write(Object, ChannelPromise)]
    • [ChannelHandlerContext.flush()]
    • [ChannelHandlerContext.read()]
    • [ChannelOutboundInvoker.disconnect(ChannelPromise)]
    • [ChannelOutboundInvoker.close(ChannelPromise)]
    • [ChannelOutboundInvoker.deregister(ChannelPromise)]

In Java, we don’t have such clear definition of all kinds of IO events, NIO just provides some const in SelectionKey like following to do event dispatch:

OP_READ
OP_WRITE
OP_CONNECT
OP_ACCEPT

Future & Callback

In the event-driven world, we react via callback/listener. There is actually one more kind of callback except Handler:

ChannelFuture future = ctx.write(res);  
if (close) {
  // after write is finished, then close  
  future.addListener(ChannelFutureListener.CLOSE);  
}

As we have introduced above, we can trigger outbound events like write, close. Of course, those method is not blocking, so it return ChannelFuture (Actually, almost all methods in ChannelOutboundInvoker return ChannelFuture). So, we can addListener to react when previous action is done.

Sync vs Get

If we look close the method of ChannelFuture, we can find a sync method which

Waits for this future until it is done

So what’s the differences between get of java.util.concurrent.Futre?

  • They are both wait and re-throw exception;
  • sync return a Future<V>, get return V, so sync can chain channel IO operation like following shows;
b.bind(PORT).sync().channel().closeFuture().sync();

Channel

The following is the Channel hierarchy for NIO selector based channel:

AbstractNioChannel
|
AbstractNioMessageChannel
|                         \
NioDatagramChannel       NioServerSocketChannel


AbstractNioChannel
|
AbstractNioByteChannel
|
NioSocketChannel

The main differences between three concrete implementation (NioDatagramChannel, NioServerSocketChannel, NioSocketChannel) is how they maps bytes into Java class when read/write, which affect our handler process (If the type is not we wanted, we may need extra Encoder/Decoder).

NioServerSocketChannel is used to accept remote connection, so it will convert data to NioSocketChannel

protected int doReadMessages(List<Object> buf) throws Exception {  
    SocketChannel ch = SocketUtils.accept(javaChannel());  
  
    try {  
        if (ch != null) {  
            buf.add(new NioSocketChannel(this, ch));  
            return 1;  
        }
    } catch (...) {...}

NioSocketChannel, on the other hand, is responsible for real communication, read data into ByteBuf (Netty buffer abstraction)

@Override  
protected int doReadBytes(ByteBuf byteBuf) throws Exception {  
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();  
    allocHandle.attemptedBytesRead(byteBuf.writableBytes());  
    return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());  
}

NioDatagramChannel will read data and convert to DatagramPacket as #doReadMessages shows.

Buffer

ByteBuf

As the official document introduced, the Netty ByteBuf has three main advantages over NIO ByteBuffer:

User friendliness

We have to flip ByteBuffer when we change from reading to writing or in reverse, which is very easy to forget and cause some error, this is because ByteBuffer has only one index of our position. In contrast, ByteBuf has two index, one for read, one for write.

Furthermore, it provides richer utility method:

it has accessor methods for signed and unsigned integers, searches, and strings.

Extensibility

This is easy to understand: ByteBuffer can’t be extended as here explained. However, ByteBuf can as it designed.

Performance

ByteBuf is faster in following two aspects:

  • It will not zeroing buffer when allocated;
  • It’s allocation/deallocation is not relying on GC but reference count: this performance is gain is relied on the cost to allocate a direct buffer is relative high (we will dive into detail of it in next blog);

Ref

Written with StackEdit.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (