Netty 对象回显示例服务器 channelRead 方法不适用于我的自定义对象

Netty object echo example server channelRead method not working with my custom objects

我最近开始尝试使用 Netty 来创建视频游戏 server/client 关系。我正在使用 Netty 4.1.X 源提供的 Netty "Object Echo" 示例。客户端和服务器包含在不同的项目中。 class "VersionInfo" 在服务器和客户端项目中均可用,但包名称不同。

我遇到的问题是,当我将自定义对象从客户端发送到服务器时,没有收到。但是,如果我发送一个 String 对象,它会完美地工作。

服务器:

    public class Server {
    private int port;

    public Server(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ServerChannelInitializer())
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // Bind and start to accept incoming connections.
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            channelFuture.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        try {
            new Server(1337).run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务器通道初始化程序:

    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    public void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
        pipeline.addLast(new ObjectEchoServerHandler());
    }
}

对象回显服务器处理程序:

public class ObjectEchoServerHandler extends ChannelInboundHandlerAdapter {

    public static final String VERSION = "1.0.0";

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("[Server] Channel Active");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // business logic here.
        System.out.println("[Server] channelRead()");

        if (msg instanceof VersionInfo) {
            VersionInfo versionInfo = (VersionInfo) msg;
            String clientVersion = versionInfo.version;
            System.out.println("[Server] Version Info Received! Client Version: " + clientVersion + " Local Version: " + VERSION);

            if (clientVersion.equals(VERSION)) {
                VersionInfo success = new VersionInfo();
                success.versionChecked = true;
                ctx.write(success);
                System.out.println("[Server] Version Check Passed!");

            } else {
                // send client version info with false boolean, meaning server version not the same.
                ctx.write(msg);
                System.out.println("[Server] Version Check Failed!");

            }
        } else if (msg instanceof String) {
            ctx.write("[Server] " + msg);
        } else {
            System.out.println("ERROR! Received unknown object! Class name: " + msg.getClass().getSimpleName());
        }
        //ctx.write(msg);
    }

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

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.out.println("ExceptionCaught");
        cause.printStackTrace();
        ctx.close();
    }
}

VersionInfo Class(在客户端和服务器上,但包名称不同):

public class VersionInfo {
    public String version = "1.0.0";
    public boolean versionChecked = false;
}

现在进入客户端代码:

public class Client {
    public static void main(String[] args) throws Exception {

        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.TCP_NODELAY, true);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.handler(new ClientChannelInitializer());

            // Start the client.
            ChannelFuture channelFuture = bootstrap.connect("localhost", 1337).sync(); // (5)

            // Wait until the connection is closed.
            channelFuture.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

ClientChannelInitializer:

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
        pipeline.addLast(new ObjectEchoClientHandler());
    }
}

最后是 ObjectEchoClientHandler:

public class ObjectEchoClientHandler extends ChannelInboundHandlerAdapter {

    private final String string = "This is a test message! :)";
    private final VersionInfo versionInfo = new VersionInfo();


    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // Send the first message if this handler is a client-side handler.
        System.out.println("[Client] Sending Version Info");
        ctx.writeAndFlush(versionInfo); // this.. does nothing?
        //ctx.writeAndFlush(string); // this works
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("[Client] channelRead()");

        // Check client version
        if (msg instanceof VersionInfo) {
            VersionInfo versionInfo = (VersionInfo) msg;
            System.out.println("[Client] Received Version Info");

            if (versionInfo.versionChecked) {
                System.out.println("[Client] Version Check Passed!");
            } else {
                ctx.close(); // try closing the connection
                System.out.println("[Client] Version Check Failed!");
            }
        } else if (msg instanceof String) {

            System.out.println(msg.toString());

            // Echo back the received object to the server.
            ctx.write(string);
        } else {
            System.out.println("ERROR! Received unknown object! Class name: " + msg.getClass().getSimpleName());
        }
    }

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

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

在 ObjectEchoClientHandler channelActive 方法中发送字符串效果很好。发送自定义 POJO/Object 不会调用 ObjectEchoServerHandler channelRead 方法。

可能是什么原因造成的?

编辑 #1: 按照 Nicholas 的建议在下方添加日志文本。抱歉,围绕日志记录调试的块引用。如果没有它,Stack Overflow 编辑器不会让我提交它。

VersionInfo class 更改为反映相同的包名称。新 class:

package com.nettytest.iocommon;

public class VersionInfo {
    public String version = "1.0.0";
    public boolean versionChecked = false;
}

发送自定义 Pojo 后的客户端输出

Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler channelRegistered SEVERE: [id: 0x237af0b8] REGISTERED Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler connect SEVERE: [id: 0x237af0b8] CONNECT: localhost/127.0.0.1:1337 [Client] Sending Version Info Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler channelActive SEVERE: [id: 0x237af0b8, L:/127.0.0.1:52830 - R:localhost/127.0.0.1:1337] ACTIVE
Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler write SEVERE: [id: 0x237af0b8, L:/127.0.0.1:52830 - R:localhost/127.0.0.1:1337] WRITE: com.nettytest.iocommon.VersionInfo@7544bac Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler flush SEVERE: [id: 0x237af0b8, L:/127.0.0.1:52830 - R:localhost/127.0.0.1:1337] FLUSH

收到自定义 Pojo 后服务器输出

Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler channelRegistered [Server] Channel Active SEVERE: [id: 0xcdffaece, L:/127.0.0.1:1337 - R:/127.0.0.1:52830] REGISTERED Mar 22, 2017 1:09:43 PM io.netty.handler.logging.LoggingHandler channelActive
SEVERE: [id: 0xcdffaece, L:/127.0.0.1:1337 - R:/127.0.0.1:52830] ACTIVE

调试此问题的一种方法是将 LoggingHandler 添加到您的服务器管道。像这样:

import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.logging.LogLevel;
....
final LoggingHandler loggingHandler = new LoggingHandler(getClass(), 
LogLevel.ERROR);
....
pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
pipeline.addLast("logger", loggingHandler);
pipeline.addLast(new ObjectEchoServerHandler());
....

我最初的怀疑是,由于您传递的 pojo 类型在客户端和服务器上具有不同的包名称(意味着它们不相同 class),因此客户端上的 ObjectDecoder 失败了,因为它在其 class 路径中找不到 class。为了使其正常工作,class 必须存在于服务器和客户端 class 路径中。