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 路径中。
我最近开始尝试使用 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 路径中。