Netty framework learning - Transmission

Posted by ExpendableDecoy on Sun, 30 Jan 2022 19:29:31 +0100


summary

The data flowing through the network always has the same type: bytes. How these bytes are transmitted mainly depends on what we call network transmission. Users do not care about the details of transmission, only whether bytes are reliably sent and received

If you use Java network programming, you will find that sometimes when you need to support high concurrency connections, and then you try to switch blocking transmission to non blocking transmission, you will encounter problems because of the difference between the two APIs. Netty provides a common API, which makes the conversion easier.


Traditional transmission mode

This article describes how to use JDK API only to implement blocking (OIO) and non blocking versions (NIO) of applications

The blocking network is programmed as follows:

public class PlainOioServer {

    public void server(int port) throws IOException {
        // Bind the server to the specified port
        final ServerSocket socket = new ServerSocket(port);
        try {
            while (true) {
                // Receive connection
                final Socket clientSocket = socket.accept();
                System.out.println("Accepted connection from " + clientSocket);
                // Create a new thread to handle the connection
                new Thread(() -> {
                    OutputStream out;
                    try {
                        out = clientSocket.getOutputStream();
                        // Write messages to connected clients
                        out.write("Hi\r\n".getBytes(StandardCharsets.UTF_8));
                        out.flush();
                        // Close connection x
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            clientSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This code can handle a medium number of concurrent clients, but with the increase of concurrent connections, you decide to use asynchronous network programming, but the asynchronous API is completely different

The non blocking version is as follows:

public class PlainNioServer {

    public void server(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ssocket = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        // Bind the server to the selected port
        ssocket.bind(address);
        // Open the Selector to process the Channel
        Selector selector = Selector.open();
        // Register the ServerSocket with the Selector to accept the connection
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi\r\n".getBytes());
        while (true) {
            try {
                // Waiting for a new event to be processed, the blocking will continue until the next incoming event
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
                break;
            }
            Set<SelectionKey> readKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    // Check whether the event is a new connection that is ready to be accepted
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        // Accept the client and register it with the selector
                        client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate());
                        System.out.println("Accepted connection from " + client);
                    }
                    // Check whether the socket is ready to write data
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        while (buffer.hasRemaining()) {
                            // Write data to connected clients
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        client.close();
                    }
                } catch (IOException exception) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        cex.printStackTrace();
                    }
                }
            }
        }
    }
}

As you can see, blocking and non blocking code are very different. If you completely rewrite the program in order to achieve non blocking, it is undoubtedly very difficult


Netty based transmission

The blocking network processing using Netty is as follows:

public class NettyOioServer {

    public void server(int port) throws Exception {
        final ByteBuf buf = Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi\n\r", StandardCharsets.UTF_8));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    // Use blocking mode
                    .channel(OioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                    new SimpleChannelInboundHandler<>() {
                                        @Override
                                        protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
                                            ctx.writeAndFlush(buf.duplicate())
                                                    .addListener(ChannelFutureListener.CLOSE);
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

The non blocking version as like the blocking version is as like as two peas, only two places need to be changed.

EventLoopGroup group = new NioEventLoopGroup();
b.group(group).channel(NioServerSocketChannel.class);

Transmission API

The core of the transport API is the interface Channel, which is used for all IO operations. Each Channel will be assigned a ChannelPipeline and ChannelConfig. ChannelConfig contains all configuration settings of the Channel. ChannelPipeline holds all ChannelHandler instances that will be applied to inbound and outbound data and events

In addition to accessing the allocated ChannelPipeline and ChannelConfig, other methods of Channel can also be used

Method namedescribe
eventLoopReturns the EventLoop assigned to the Channel
pipelineReturns the ChannelPipeline assigned to the Channel
isActiveReturns true if the Channel is active
localAddressReturns the local SocketAddress
remoteAddressReturns the remote SocketAddress
writeWrite data to remote node
flushFlush the previously written data to the bottom layer for transmission
writeAndFlushThis is equivalent to calling write() and then flush()

Built in transmission

Netty has built-in transports that can be used out of the box, but they support different protocols, so you must choose a transport that is compatible with the protocol used by your application

namepackagedescribe
NIOio.netty.channel.socket.nioUsing Java nio. Channels package as the foundation
Epollio.netty.channel.epollOn non blocking and niol driven Linux, only niol () can support a variety of transmission features, which are faster than niol () and niol ()
OIOio.netty.channel.socket.oioUsing Java Net package as the basis
Localio.netty.channel.localA local transport that can communicate through a pipeline within the VM
Embeddedio.netty.channel.embeddedEmbedded transmission, which allows the use of ChannelHandler without the need for a real network-based transmission, is mainly used for testing

Topics: Java Netty network socket