netty series: Please love UDT again

Posted by alphamic on Wed, 26 Jan 2022 01:39:02 +0100

brief introduction

UDT is a very excellent protocol, which can provide high-speed data transmission based on UDP protocol. Unfortunately, in netty 4.1.7, the UDT transport protocol has been marked as Deprecated!

This means that you may never see the UDT protocol in the later netty version

How can excellent protocols be buried? Let's unveil UDT, show its excellent features, and let netty love UDT again.

netty support for UDT

Netty's support for UDT is reflected in a special UDT package to handle UDT related things: package io netty. channel. udt.

This package mainly defines various UDT channels, channel configurations, UDT messages, and tool classes NioUdtProvider that provide ChannelFactory and SelectorProvider

Build a netty service that supports UDT

According to the standard process of netty, it is time to create a netty service.

netty creating a server service is nothing more than creating an EventLoop, creating a ServerBootstrap, binding an EventLoop, and specifying a channel type. It is very simple.

The only difference is the specific childHandler, which may use different processing methods according to different protocols.

Of course, if it is not NioSocketChannel, the corresponding ChannelFactory and SelectorProvider will also change.

Never mind. Let's first look at how to create a netty service that supports UDT:

 final ThreadFactory acceptFactory = new DefaultThreadFactory("accept");
        final ThreadFactory connectFactory = new DefaultThreadFactory("connect");
        final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory, NioUdtProvider.BYTE_PROVIDER);
        final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, connectFactory, NioUdtProvider.BYTE_PROVIDER);

 final ServerBootstrap boot = new ServerBootstrap();
            boot.group(acceptGroup, connectGroup)
                    .channelFactory(NioUdtProvider.BYTE_ACCEPTOR)
                    .option(ChannelOption.SO_BACKLOG, 10)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<UdtChannel>() {
                        @Override
                        public void initChannel(final UdtChannel ch) {
                            ch.pipeline().addLast(
                                    new LoggingHandler(LogLevel.INFO),
                                    new UDTEchoServerHandler());
                        }
                    });
            // Open service
            final ChannelFuture future = boot.bind(PORT).sync();

It can be seen that the difference between UDT and ordinary netty socket service is that its selector and channelFactory are provided by NioUdtProvider.

NioUdtProvider is the content of netty core package. It provides useful encapsulation of UDT. We don't need to know too much about the internal implementation of UDT to use UDT protocol. Isn't it wonderful.

Abnormal attack

If a partner excitedly tries to run the above code, it is a pity that you will get an exception, which is probably similar to the following:

package com.barchart.udt can't find!

what? Using the class in the netty package directly will report an error? Who can bear it!

Let's analyze carefully. There is only one new class here, NioUdtProvider. Open the source code of NioUdtProvider. In the import column, we are surprised to find that packages that do not belong to netty are referenced. These packages report errors:

import com.barchart.udt.SocketUDT;
import com.barchart.udt.TypeUDT;
import com.barchart.udt.nio.ChannelUDT;
import com.barchart.udt.nio.KindUDT;
import com.barchart.udt.nio.RendezvousChannelUDT;
import com.barchart.udt.nio.SelectorProviderUDT;

Although it's weird, we still need to find these dependency packages to run the program. After my hard work, I finally found the following dependency packages:

<dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-core</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-bundle</artifactId>
            <version>2.3.0</version>
        </dependency>

Netty's core package actually depends on a third-party library, which may be why netty is ready to remove its support for UDT.

TypeUDT and KindUDT

If you look at the specific information of classes in barchart, you will find that the author of this package has a hobby. He likes to bring a UDT behind the class.

When you see that all classes on the full screen end in UDT, yes, it is the package barchart that netty UDT depends on.

We can't say that the package developed by Daniel is bad, but it looks a little tired

In the barchart package, there are two core classes used to distinguish UDT type and kind, called TypeUDT and KindUDT respectively

Type and kind don't seem to make much difference when translated into Chinese. However, there are still great differences between the two in UDT.

TypeUDT represents the pattern of UDT socket. It has two values, stream and datagram:

    STREAM(1),
    DATAGRAM(2),

Indicates whether the data transmission is in the form of byte stream or in the format of data message.

KindUDT is used to distinguish between server and client. It has three modes:

ACCEPTOR,
CONNECTOR,
RENDEZVOUS

The server mode corresponds to the ACCEPTOR, which is used to listen and receive connections Its representative is ServerSocketChannelUDT, which returns a CONNECTOR by calling the accept() method

The CONNECTOR mode can be used on both the client side and the server side. Its representative class is SocketChannelUDT

If it is on the server side, it is generated by calling the accept method on the server side. If it is on the client side, it indicates the connection between the client side and the server side.

Another mode is the RENDEZVOUS mode. This pattern means that each side of the connection has symmetrical and equivalent channels. That is, a two-way pattern. Its representative class is RENDEZVOUS channel UDT.

Building ChannelFactory

The two types and three Kind mentioned above are used to define channels, so if they are mixed, six different channelfactories will be generated, namely:

public static final ChannelFactory<UdtServerChannel> BYTE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
            TypeUDT.STREAM, KindUDT.ACCEPTOR);

public static final ChannelFactory<UdtChannel> BYTE_CONNECTOR = new NioUdtProvider<UdtChannel>(
            TypeUDT.STREAM, KindUDT.CONNECTOR);

public static final ChannelFactory<UdtChannel> BYTE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
            TypeUDT.STREAM, KindUDT.RENDEZVOUS);

public static final ChannelFactory<UdtServerChannel> MESSAGE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
            TypeUDT.DATAGRAM, KindUDT.ACCEPTOR);

public static final ChannelFactory<UdtChannel> MESSAGE_CONNECTOR = new NioUdtProvider<UdtChannel>(
            TypeUDT.DATAGRAM, KindUDT.CONNECTOR);

public static final ChannelFactory<UdtChannel> MESSAGE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
            TypeUDT.DATAGRAM, KindUDT.RENDEZVOUS);

These channelfactories generate new channels by calling the newChannel() method.

However, in the root node, these channels finally call the from method of SelectorProviderUDT to generate channels.

SelectorProviderUDT

There are two kinds of SelectorProviderUDT according to different types of UDT, namely:

public static final SelectorProviderUDT DATAGRAM = 
    new SelectorProviderUDT(TypeUDT.DATAGRAM);

    public static final SelectorProviderUDT STREAM = 
    new SelectorProviderUDT(TypeUDT.STREAM);

You can generate the corresponding channel by calling its three methods:

    public RendezvousChannelUDT openRendezvousChannel() throws IOException {
        final SocketUDT socketUDT = new SocketUDT(type);
        return new RendezvousChannelUDT(this, socketUDT);
    }

        public ServerSocketChannelUDT openServerSocketChannel() throws IOException {
        final SocketUDT serverSocketUDT = new SocketUDT(type);
        return new ServerSocketChannelUDT(this, serverSocketUDT);
    }

        public SocketChannelUDT openSocketChannel() throws IOException {
        final SocketUDT socketUDT = new SocketUDT(type);
        return new SocketChannelUDT(this, socketUDT);
    }

Using UDT

After setting up the netty server, the rest is to write a Handler to process the data.

Here we simply write back the data written by the client. The client creates a message first:

message = Unpooled.buffer(UDTEchoClient.SIZE);
 message.writeBytes("www.flydean.com".getBytes(StandardCharsets.UTF_8));

Then write to the server:

    public void channelActive(final ChannelHandlerContext ctx) {
        log.info("channel active " + NioUdtProvider.socketUDT(ctx.channel()).toStringOptions());
        ctx.writeAndFlush(message);
    }

The server receives through the channelRead method:

public void channelRead(final ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

summary

The above is the principle of using UDT in netty and a simple example.

Examples of this article can be referred to: learn-netty4

This article has been included in http://www.flydean.com/40-netty-udt-support/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to find!

Welcome to my official account: "those things in procedure", understand technology, know you better!

Topics: Java Netty