CLOVER🍀

That was when it all began.

TCP Echo Server/Clientを書いて、Netty io_uringを試してみる

これは、なにをしたくて書いたもの?

Nettyのio_uringを少し見てみたいな、ということで。

io_uring

まずは、io_uring自体について。

io_uringは、非同期IO用のAPIです。カーネル5.1から導入されたそうです。

https://kernel.dk/io_uring.pdf

io_uringについては、以下あたりも見てみました。

Ubuntu Manpage: io_uring - Asynchronous I/O facility

Welcome to Lord of the io_uring — Lord of the io_uring documentation

Linuxにおける非同期IOの実装について - Qiita

io_uringで高速IO処理(?) | κeenのHappy Hacκing Blog

ソケットAPIが遅すぎる?新たなio_uringを試す!. 新しいAPIが作られるたびに、私たちは、古いAPIを置き換えるだけで高速化という… | by FUJITA Tomonori | nttlabs | Medium

io_uringの作者が書いたこちらのページが、io_uringの概要を把握するのによいかもしれません。

What is io_uring? — Lord of the io_uring documentation

どういうものかというと、以下のように2つのキューを使う仕組みのようです。

  • リクエスト送信用、リクエスト完了通知用の2つのキューを持つ
    • この2つのキューは、カーネルとユーザー空間で共有される
  • タスク(ファイルの読み書き、クライアント接続の受け入れなど)があると、送信キューエントリー(SQE)として送信キューの末尾に追加する
  • カーネルは送信されたリクエストを処理して、完了キューイベント(CQE)として完了キューの末尾に追加する

なので、io_uringという名前はカーネルとユーザー空間の間でコミュニケーションを行うリングバッファーをインターフェースとすることに
由来するようです。

The very name io_uring comes from the fact that the interfaces uses ring buffers as the main interface for kernel-user space communication.

パフォーマンス面では、カーネルとユーザー空間でキューが共有されているため以下の点が有利なようです。

  • データのコピーを避けることができる
  • システムコールを減らすことができる
    • ユーザー空間のキューとやり取りすればよい
    • 複数のリクエストをまとめてカーネルに送信することができる
    • 送信したエントリー(SQE)をカーネルにポーリングさせることができる

Submission Queue Polling — Lord of the io_uring documentation

ライブラリとしては、liburingを使うことが勧められています。

GitHub - axboe/liburing

Netty io_uring

Nettyのincubatorプロジェクトとして、io_uringベースのトランスポートライブラリが作られています。

GitHub - netty/netty-incubator-transport-io_uring

現時点でのバージョンは、0.0.16.Finalです。

io_uring APIを直接使っているみたいですね。

https://github.com/netty/netty-incubator-transport-io_uring/blob/netty-incubator-transport-parent-io_uring-0.0.16.Final/transport-native-io_uring/src/main/c/syscall.c

Nettyでのリリースに関するブログエントリー。

Netty.news: Netty/Incubator/Transport/Native/io_uring 0.0.1.Final released

Netty.news: Netty/Incubator/Transport/Native/io_uring 0.0.3.Final released

情報がほとんどないのですが、GitHubREADME.mdやブログエントリーを見る限り、既存のNettyの使い方から大きく変わりそうな
雰囲気はないので、サンプルを参考にTCP Echo Server/Clientを書いてみることにしましょう。

https://github.com/netty/netty/tree/netty-4.1.85.Final/example/src/main/java/io/netty/example

https://github.com/netty/netty/tree/netty-4.1.85.Final/example/src/main/java/io/netty/example/echo

環境

今回の環境は、こちら。

Ubuntu Linux 22.04 LTSです。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
Codename:       jammy


$ uname -srvmpio
Linux 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

カーネル5.1以上ですね。

Java

$ java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu122.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.7 (b89d5959fcde851dcb1c8946a785a163f14e1e29)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.5, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-58-generic", arch: "amd64", family: "unix"

準備

Maven依存関係などは、こちら。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.netty.incubator</groupId>
            <artifactId>netty-incubator-transport-native-io_uring</artifactId>
            <version>0.0.16.Final</version>
            <classifier>linux-x86_64</classifier>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-handler</artifactId>
            <version>4.1.85.Final</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.24.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M7</version>
            </plugin>
        </plugins>
    </build>

Nettyでio_uringを使うには、netty-incubator-transport-native-io_uringがあればOKです。
netty-handlerは、ログ出力で使うLoggingHandlerクラスを使うために入れています。

netty-incubator-transport-native-io_uringを使う時には、classifierを指定する必要があります。

        <dependency>
            <groupId>io.netty.incubator</groupId>
            <artifactId>netty-incubator-transport-native-io_uring</artifactId>
            <version>0.0.16.Final</version>
            <classifier>linux-x86_64</classifier>
        </dependency>

現時点で指定可能なのは、linux-x86_64linux-aarch_64の2つです。

最後にテストコードも書くので、JUnitとAssertJも入れています。

サンプルコードを書く

それでは、こちらを参考にTCP Echo Server/Clientを書いてみましょう。

https://github.com/netty/netty/tree/netty-4.1.85.Final/example/src/main/java/io/netty/example/echo

サーバー側から書きます。

ハンドラー。

src/main/java/org/littlewings/netty/iouring/EchoServerHandler.java

package org.littlewings.netty.iouring;

import java.nio.charset.StandardCharsets;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String msgAsString = ((ByteBuf) msg).toString(StandardCharsets.UTF_8);

        if (msgAsString.endsWith("\r\n")) {
            msgAsString = msgAsString.substring(0, msgAsString.length() - 2);
        } else if (msgAsString.endsWith("\n")) {
            msgAsString = msgAsString.substring(0, msgAsString.length() - 1);
        }

        System.out.printf("received message = %s%n", msgAsString);

        ByteBuf sendMessage =
                Unpooled.wrappedBuffer(String.format("★%s★", msgAsString).getBytes(StandardCharsets.UTF_8));

        ctx.write(sendMessage);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

メッセージを返す時には、装飾するようにしておきました。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String msgAsString = ((ByteBuf) msg).toString(StandardCharsets.UTF_8);

        if (msgAsString.endsWith("\r\n")) {
            msgAsString = msgAsString.substring(0, msgAsString.length() - 2);
        } else if (msgAsString.endsWith("\n")) {
            msgAsString = msgAsString.substring(0, msgAsString.length() - 1);
        }

        System.out.printf("received message = %s%n", msgAsString);

        ByteBuf sendMessage =
                Unpooled.wrappedBuffer(String.format("★%s★", msgAsString).getBytes(StandardCharsets.UTF_8));

        ctx.write(sendMessage);
    }

Bootstrap。

src/main/java/org/littlewings/netty/iouring/IoUringEchoServer.java

package org.littlewings.netty.iouring;

import java.util.concurrent.ExecutionException;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringServerSocketChannel;

public class IoUringEchoServer implements AutoCloseable {
    int port;

    EventLoopGroup bossGroup;
    EventLoopGroup workerGroup;

    public static void main(String... args) throws InterruptedException, ExecutionException {
        try (IoUringEchoServer server = IoUringEchoServer.newServer(8080)) {
            server.start(true);
        }
    }

    public static IoUringEchoServer newServer(int port) {
        IoUringEchoServer server = new IoUringEchoServer();
        server.port = port;

        return server;
    }

    public void start(boolean block) throws InterruptedException {
        bossGroup = new IOUringEventLoopGroup();
        workerGroup = new IOUringEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap
                .group(bossGroup, workerGroup)
                .channel(IOUringServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .option(ChannelOption.SO_REUSEADDR, true)
                .childHandler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        // pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                        pipeline.addLast(new EchoServerHandler());
                    }
                });

        ChannelFuture future = bootstrap.bind(port).sync();

        if (block) {
            future.channel().closeFuture().sync();
        }
    }

    @Override
    public void close() throws ExecutionException, InterruptedException {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().get();
        }

        if (workerGroup != null) {
            workerGroup.shutdownGracefully().get();
        }
    }
}

ポイントは、EventLoopGroupとしてIOUringEventLoopGroupクラスを使うことと、

        bossGroup = new IOUringEventLoopGroup();
        workerGroup = new IOUringEventLoopGroup();

ChannelとしてIOUringServerSocketChannelクラスを指定することですね。

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap
                .group(bossGroup, workerGroup)
                .channel(IOUringServerSocketChannel.class)

起動を行うstartメソッドの呼び出し時に、truefalseを渡すかでブロックするかどうかを指定できるようにしました。

        if (block) {
            future.channel().closeFuture().sync();
        }

trueだとstartメソッドはブロックします。

クライアント側。

ハンドラー。

src/main/java/org/littlewings/netty/iouring/EchoClientHandler.java

package org.littlewings.netty.iouring;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.BlockingQueue;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    BlockingQueue<String> queue;

    public EchoClientHandler(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String msgAsString = ((ByteBuf) msg).toString(StandardCharsets.UTF_8);
        System.out.printf("received message = %s%n", msgAsString);

        try {
            queue.put(msgAsString);
        } catch (InterruptedException e) {
            // no-op
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

Bootstrap。

src/main/java/org/littlewings/netty/iouring/IoUringEchoClient.java

package org.littlewings.netty.iouring;

import java.io.Console;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringSocketChannel;

public class IoUringEchoClient implements AutoCloseable {
    String host;
    int port;

    BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

    EventLoopGroup group;

    Channel channel;

    public static void main(String... args) throws InterruptedException {
        try (IoUringEchoClient client = IoUringEchoClient.newClient("localhost", 8080)) {
            client.start();

            Console console = System.console();

            while (true) {
                String message = console.readLine("> ");

                if ("exit".equals(message)) {
                    break;
                } else {
                    client.sendMessage(message);
                    TimeUnit.MILLISECONDS.sleep(500L);
                }
            }
        }
    }

    public static IoUringEchoClient newClient(String host, int port) {
        IoUringEchoClient client = new IoUringEchoClient();
        client.host = host;
        client.port = port;

        return client;
    }

    public void start() throws InterruptedException {


        group = new IOUringEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(group)
                .channel(IOUringSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        // pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                        pipeline.addLast(new EchoClientHandler(queue));
                    }
                });

        channel = bootstrap.connect(host, port).sync().channel();
    }

    public String sendMessage(String message) throws InterruptedException {
        ByteBuf msg = Unpooled.wrappedBuffer(message.getBytes(StandardCharsets.UTF_8));
        channel.writeAndFlush(msg);

        return queue.take();
    }

    @Override
    public void close() throws InterruptedException {
        if (channel != null) {
            channel.close().sync();
        }

        if (group != null) {
            group.shutdownGracefully().sync();
        }
    }
}

io_uringと全然関係ないですが、BlockingQueueを使ってサーバー側から返ってきたメッセージを呼び出し元に返せるようにしました。

    public String sendMessage(String message) throws InterruptedException {
        ByteBuf msg = Unpooled.wrappedBuffer(message.getBytes(StandardCharsets.UTF_8));
        channel.writeAndFlush(msg);

        return queue.take();
    }

mainメソッドから起動した時は、対話的なアプリケーションにしているのですが

    public static void main(String... args) throws InterruptedException {
        try (IoUringEchoClient client = IoUringEchoClient.newClient("localhost", 8080)) {
            client.start();

            Console console = System.console();

            while (true) {
                String message = console.readLine("> ");

                if ("exit".equals(message)) {
                    break;
                } else {
                    client.sendMessage(message);
                    TimeUnit.MILLISECONDS.sleep(500L);
                }
            }
        }
    }

これは、Memcachedのクライアントサンプルを参考にしています。

https://github.com/netty/netty/blob/netty-4.1.85.Final/example/src/main/java/io/netty/example/memcache/binary/MemcacheClient.java

動作確認

では、動作確認してみましょう。

サーバー側を起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.netty.iouring.IoUringEchoServer

telnetで確認。

$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

結果。

Hello World
★Hello World★

この時、サーバー側ではコンソールにこのように出力されます。

received message = Hello World

次に、作成したクライアントを使ってみます。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.netty.iouring.IoUringEchoClient

こんな感じになりますね。

> Hello World
received message = ★Hello World★
> こんにちは、世界
received message = ★こんにちは、世界★

exitで終了します。

> exit

それから、サーバー・クライアント双方とも以下のコメントアウトを解除すると

                        pipeline.addLast(new LoggingHandler(LogLevel.INFO));

Nettyのログが出力されるようになります。

サーバー側。

1月 22, 2023 12:09:54 午前 io.netty.handler.logging.LoggingHandler channelRegistered
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] REGISTERED
1月 22, 2023 12:09:54 午前 io.netty.handler.logging.LoggingHandler channelActive
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] ACTIVE
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler channelRead
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] READ: 11B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 20 57 6f 72 6c 64                |Hello World     |
+--------+-------------------------------------------------+----------------+
received message = Hello World
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler write
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] WRITE: 17B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e2 98 85 48 65 6c 6c 6f 20 57 6f 72 6c 64 e2 98 |...Hello World..|
|00000010| 85                                              |.               |
+--------+-------------------------------------------------+----------------+
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler channelReadComplete
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] READ COMPLETE
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] FLUSH
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler channelRead
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] READ: 24B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af e3 |................|
|00000010| 80 81 e4 b8 96 e7 95 8c                         |........        |
+--------+-------------------------------------------------+----------------+
received message = こんにちは、世界
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler write
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] WRITE: 30B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e2 98 85 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 |................|
|00000010| 81 af e3 80 81 e4 b8 96 e7 95 8c e2 98 85       |..............  |
+--------+-------------------------------------------------+----------------+
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler channelReadComplete
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] READ COMPLETE
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xfc878418, L:/127.0.0.1:8080 - R:/127.0.0.1:54800] FLUSH
1月 22, 2023 12:10:07 午前 io.netty.handler.logging.LoggingHandler channelReadComplete
情報: [id: 0xfc878418, L:/127.0.0.1:8080 ! R:/127.0.0.1:54800] READ COMPLETE
1月 22, 2023 12:10:07 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xfc878418, L:/127.0.0.1:8080 ! R:/127.0.0.1:54800] FLUSH
1月 22, 2023 12:10:07 午前 io.netty.handler.logging.LoggingHandler channelInactive
情報: [id: 0xfc878418, L:/127.0.0.1:8080 ! R:/127.0.0.1:54800] INACTIVE
1月 22, 2023 12:10:07 午前 io.netty.handler.logging.LoggingHandler channelUnregistered
情報: [id: 0xfc878418, L:/127.0.0.1:8080 ! R:/127.0.0.1:54800] UNREGISTERED

クライアント側。

1月 22, 2023 12:09:54 午前 io.netty.handler.logging.LoggingHandler channelRegistered
情報: [id: 0xbe67cbc7] REGISTERED
1月 22, 2023 12:09:54 午前 io.netty.handler.logging.LoggingHandler connect
情報: [id: 0xbe67cbc7] CONNECT: localhost/127.0.0.1:8080
1月 22, 2023 12:09:54 午前 io.netty.handler.logging.LoggingHandler channelActive
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] ACTIVE
> Hello World
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler write
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] WRITE: 11B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 20 57 6f 72 6c 64                |Hello World     |
+--------+-------------------------------------------------+----------------+
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] FLUSH
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler channelRead
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] READ: 17B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e2 98 85 48 65 6c 6c 6f 20 57 6f 72 6c 64 e2 98 |...Hello World..|
|00000010| 85                                              |.               |
+--------+-------------------------------------------------+----------------+
received message = ★Hello World★
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler channelReadComplete
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] READ COMPLETE
1月 22, 2023 12:09:58 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] FLUSH
> こんにちは、世界
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler write
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] WRITE: 24B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af e3 |................|
|00000010| 80 81 e4 b8 96 e7 95 8c                         |........        |
+--------+-------------------------------------------------+----------------+
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] FLUSH
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler channelRead
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] READ: 30B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e2 98 85 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 |................|
|00000010| 81 af e3 80 81 e4 b8 96 e7 95 8c e2 98 85       |..............  |
+--------+-------------------------------------------------+----------------+
received message = ★こんにちは、世界★
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler channelReadComplete
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] READ COMPLETE
1月 22, 2023 12:10:04 午前 io.netty.handler.logging.LoggingHandler flush
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] FLUSH
> exit
1月 22, 2023 12:10:05 午前 io.netty.handler.logging.LoggingHandler close
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 - R:localhost/127.0.0.1:8080] CLOSE
1月 22, 2023 12:10:05 午前 io.netty.handler.logging.LoggingHandler channelInactive
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 ! R:localhost/127.0.0.1:8080] INACTIVE
1月 22, 2023 12:10:05 午前 io.netty.handler.logging.LoggingHandler channelUnregistered
情報: [id: 0xbe67cbc7, L:/127.0.0.1:54800 ! R:localhost/127.0.0.1:8080] UNREGISTERED

テストを書く

最後に、テストコードを書いておきます。

src/test/java/org/littlewings/netty/iouring/IoUringEchoTest.java

package org.littlewings.netty.iouring;

import java.util.concurrent.ExecutionException;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class IoUringEchoTest {
    @Test
    void echoClientServer() throws InterruptedException, ExecutionException {
        String host = "localhost";
        int port = 8080;

        try (IoUringEchoServer server = IoUringEchoServer.newServer(port);
             IoUringEchoClient client = IoUringEchoClient.newClient(host, port)) {
            server.start(false);
            client.start();

            assertThat(client.sendMessage("Hello World"))
                    .isEqualTo("★Hello World★");

            assertThat(client.sendMessage("Hello Netty"))
                    .isEqualTo("★Hello Netty★");

            assertThat(client.sendMessage("こんにちは 世界"))
                    .isEqualTo("★こんにちは 世界★");
        }
    }
}

こんなところでしょうか。

まとめ

Netty io_uringを試してみました。

そもそもio_uringをよく知らなかったので、io_uring自体を調べるにそれなりに時間を使いましたが。直接は使っていないので、理解が
進んだかというと微妙なところです。

こういう世界にも、いずれ踏み込んだ方がいいのかな、とは時々思います。

あと、Nettyを使ったプログラムを久しぶりに書いたので、これもこれでけっこう苦労しました。