我有一个使用netty进行tcp通信的示例服务器/客户端应用程序。
我面临的问题是当服务器以较慢的速度将消息推送到连接的客户端时,客户端会收到所有消息,但是当从服务器推送数据之间没有延迟时,客户端不会收到所有消息。这是我的示例服务器代码
public class NettyServer {
public NettyServer() throws InterruptedException {
final ClientChannelHolder clientChannelHolder = new ClientChannelHolder();
NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(9000)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("Client connected");
ch.pipeline().addLast(new ClientChannelHandler(clientChannelHolder));
}
});
ChannelFuture future = bootstrap.bind().addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("Server bound ");
}
});
synchronized (clientChannelHolder) {
// wait for client to be conncted
clientChannelHolder.wait();
}
for (int i = 0 ; i < 2; i++) {
// loop to broadcast to all connected clients
clientChannelHolder.broadCast(i);
//Thread.sleep(1000); if I add this sleep client works fine and gets all messages
}
}
public static void main(String[] args) throws InterruptedException {
NettyServer nettyServer = new NettyServer();
}
// collection for all connected clients.
public static class ClientChannelHolder {
private List<Channel> clientChannels = new LinkedList<>();
private synchronized void addClientChannel(Channel socketChannel) {
// notify the cleint is connected
notify();
clientChannels.add(socketChannel);
}
private synchronized void removeClientChannel(SocketChannel socketChannel) {
clientChannels.remove(socketChannel);
}
public void broadCast(int msgId) {
for (Channel channel : clientChannels) {
byte[] bytes = ("server msg " + msgId).getBytes(CharsetUtil.UTF_8);
System.out.println(bytes.length);
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeByte((byte)1);
byteBuf.writeInt(1);
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
channel.writeAndFlush(byteBuf).addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (future.isSuccess()) {
System.out.println("Write successfull");
}
}
});
}
}
}
public static class ClientChannelHandler extends ChannelInboundHandlerAdapter{
private final ClientChannelHolder clientChannelHolder;
public ClientChannelHandler(ClientChannelHolder clientChannelHolder) {
this.clientChannelHolder = clientChannelHolder;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
clientChannelHolder.addClientChannel(ctx.channel());
System.out.println("Client connected");
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
in.markReaderIndex();
byte version = in.readByte();
int msgType = in.readInt();
int msgLength = in.readInt();
byte[] msgArray = new byte[msgLength];
if (in.readableBytes() < msgLength) {
in.resetReaderIndex();
return;
}
in.readBytes(msgArray, 0, msgLength);
System.out.println(
"Server received: " + new String(msgArray, CharsetUtil.UTF_8));
}
}
}
如果我在sleep语句之前删除注释,则客户端会获取所有消息。但是,通过这个评论的睡眠,客户端会在其间丢失许多消息,即使服务器打印了#34;写成功&#34;
这是我的侧边代码: 公共类NettyClient {
public NettyClient(final int clientId , NioEventLoopGroup eventLoopGroup, final ClientChannelHolder clientChannelHolder)
throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress("localhost", 9000))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
clientChannelHolder.addClientChannel(ch);
ch.pipeline().addLast(new ClientChannelHandler(clientId));
}
});
bootstrap.connect().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println(clientId + "Client connected");
}
});
}
public static class ClientChannelHolder {
private List<Channel> clientChannels = new LinkedList<>();
private synchronized void addClientChannel(Channel socketChannel) {
clientChannels.add(socketChannel);
}
private synchronized void removeClientChannel(SocketChannel socketChannel) {
clientChannels.remove(socketChannel);
}
public void broadCast(int msgId) {
for (Channel channel : clientChannels) {
byte[] bytes = ("cleint msg" + msgId).getBytes(CharsetUtil.UTF_8);
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeByte(1);
byteBuf.writeInt(1);
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
channel.writeAndFlush(byteBuf);
}
}
}
public static class ClientChannelHandler extends ChannelInboundHandlerAdapter {
private int clientId = 1;
public ClientChannelHandler(int clientId){
this.clientId = clientId;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
in.markReaderIndex();
byte version = in.readByte();
int msgType = in.readInt();
int msgLength = in.readInt();
System.out.println(msgLength);
byte[] msgArray = new byte[msgLength];
if (in.readableBytes() < msgLength) {
in.resetReaderIndex();
return;
}
in.readBytes(msgArray, 0, msgLength);
System.out.println(clientId +
" Client received: " + new String(msgArray, CharsetUtil.UTF_8));
}
}
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
ClientChannelHolder clientChannelHolder = new ClientChannelHolder();
NettyClient nettyClient = new NettyClient(1, group, clientChannelHolder);
}
答案 0 :(得分:0)
TL; DR:您编写了客户端处理程序,假设扩展ByteToMessageDecoder
,而是扩展ChannelInboundHandlerAdapter
。
在Netty中,有不同的方法来读取传入的数据,但这是令人困惑的,因为TCP的编写方式,即基于流的方式。这意味着虽然TCP保证所有数据按顺序进入您的应用程序,但它不保证任何数据包大小,也不保证传入数据包的大小。
使用您的代码,您可以从最简单的类扩展,即ChannelInboundHandlerAdapter
。这意味着您的程序会在TCP将其提供给您的程序时获取所有传入数据。虽然这通常(但并非总是如此,想到数据包丢失)适用于数据发送发送间隔1秒,但它会随着数据包发送频率而崩溃。
要了解这出错的原因,请对包含2个协议特定数据包的单个ByteBuf
进行映像,如果执行了代码,则基本上只读取第一个。
通过查看原始客户端代码,看起来它是为了扩展ByteToMessageDecoder
而不是ChannelInboundHandlerAdapter
而编写的,正如使用markReaderIndex
和{{1}所看到的那样,所以更改父类(和重写的方法)是第一步。
resetReaderIndex
然而,这不是最终的解决方案,因为有两个问题需要解决:
public class ClientChannelHandler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
in.markReaderIndex();
byte version = in.readByte();
int msgType = in.readInt();
int msgLength = in.readInt();
System.out.println(msgLength);
byte[] msgArray = new byte[msgLength];
if (in.readableBytes() < msgLength) {
in.resetReaderIndex();
return;
}
in.readBytes(msgArray, 0, msgLength);
System.out.println(clientId +
" Client received: " + new String(msgArray, CharsetUtil.UTF_8));
}
}
需要输出,因此它知道何时读取数据包。ByteToMessageDecoder
调用会出错。对于第一个问题,我们将通过在打印和解码之间拆分处理程序来解决问题。
readInt
与之前的问题相比,解决问题很容易。当我们的信息太小时,我们只需要返回。
public class ClientChannelDecoderHandler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
in.markReaderIndex();
byte version = in.readByte();
int msgType = in.readInt();
int msgLength = in.readInt();
System.out.println(msgLength);
byte[] msgArray = new byte[msgLength];
if (in.readableBytes() < msgLength) {
in.resetReaderIndex();
return;
}
in.readBytes(msgArray, 0, msgLength);
out.add(clientId +
" Client received: " + new String(msgArray, CharsetUtil.UTF_8));
}
}
public static class ClientChannelPrinterHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println((String)msg);
}
}
// When making your bootstrap netty object:
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
clientChannelHolder.addClientChannel(ch);
ch.pipeline().addLast(new ClientChannelDecodeHandler(clientId));
ch.pipeline().addLast(new ClientChannelPrintingHandler(clientId));
}
});