【问题标题】:How can I know if there is no data to read in Netty ByteBuf?如何知道 Netty ByteBuf 中是否没有数据可读取?
【发布时间】:2019-01-23 01:36:45
【问题描述】:

我是 Netty 的新手。文件传输有一个问题让我困惑了好几天。我想将 图像 文件从客户端发送到服务器。

下面的代码是可执行的。但是只有我强行shutdown服务器才能正常打开接收到的图片文件。否则,它会显示“您似乎没有查看此文件的权限。请检查权限并重试”。因此,我想在 ByteBuf 中没有数据时使用 ByteBuf.isReadable() 关闭文件输出流,但 ServerHandler 中的方法 channelRead 中的 else 块永远无法到达。没用。

另外,如果发送文本文件,在服务器存活时可以正常打开。 我不想每次传输后都关闭服务器。请给我一些解决它的建议。

这是 FileClientHandler

public class FileClientHandler extends ChannelInboundHandlerAdapter {

private int readLength = 8;

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    sendFile(ctx.channel());
}

private void sendFile(Channel channel) throws IOException {
    File file = new File("C:\\Users\\xxx\\Desktop\\1.png");
    FileInputStream fis = new FileInputStream(file);
    BufferedInputStream bis = new BufferedInputStream(fis);

    for (;;) {
        byte[] bytes = new byte[readLength];
        int readNum = bis.read(bytes, 0, readLength);
        // System.out.println(readNum);
        if (readNum == -1) {
            bis.close();
            fis.close();
            return;
        }
        sendToServer(bytes, channel, readNum);
    }
}

private void sendToServer(byte[] bytes, Channel channel, int length)
        throws IOException {
    channel.writeAndFlush(Unpooled.copiedBuffer(bytes, 0, length));
}

}

这是文件服务器处理程序

public class FileServerHandler extends ChannelInboundHandlerAdapter {

private File file = new File("C:\\Users\\xxx\\Desktop\\2.png");
private FileOutputStream fos;

public FileServerHandler() {
    try {
        if (!file.exists()) {
            file.createNewFile();
        } else {
            file.delete();
            file.createNewFile();
        }
        fos = new FileOutputStream(file);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    try {
        ByteBuf buf = (ByteBuf) msg;
        if (buf.isReadable()) {
            buf.readBytes(fos, buf.readableBytes());
            fos.flush();
        } else {
            System.out.println("I want to close fileoutputstream!");
            buf.release();
            fos.flush();
            fos.close();
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}
}

【问题讨论】:

  • 似乎某些进程锁定了文件:如果您在基于 unix 的系统中工作,请尝试检查文件权限。或者在文件传输完成后尝试关闭通道(在服务器和客户端上)。但首先,只需传输后尝试关闭通道
  • 感谢您的评论。我在 Windows 上工作。对我来说关键是我怎么知道文件传输是在 ServerHandler 中完成的。我在 ClientHandler 中发送后尝试关闭通道,如下所示:fis.getChannel().close();。但这是徒劳的。你知道为什么文本文件可以打开而图片不能吗?

标签: java netty


【解决方案1】:

修复服务器端

在 Netty 世界中,有多个“事件”:

在这些“事件”中,您可能已经知道channelRead 的作用(因为您使用它),但您似乎需要的另一个是channelInactive。当另一个端点关闭连接时调用它,您可以像这样使用它:

@Override
public void channelInactive(ctx) {
    System.out.println("I want to close fileoutputstream!");
    fos.close();
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    try {
        ByteBuf buf = (ByteBuf) msg;
        // if (buf.isReadable()) { // a buf should always be readable here
            buf.readBytes(fos, buf.readableBytes());
        //    fos.flush(); // flushing is always done when closing
        //} else {
        //    System.out.println("I want to close fileoutputstream!");
        //    buf.release(); // Should be placed in the finally block
        //    fos.flush();
        //    fos.close();
        //}
    } catch (Exception e) {
         e.printStackTrace();
    } finally {
         buf.release(); // Should always be done, even if writing to the file fails
    }
}

但是,服务器如何知道连接已关闭?目前,客户端并没有关闭服务器,而是一直在后台运行,以保持连接处于活动状态。

修复客户端

要正确关闭来自客户端的连接,我们需要调用channel.close(),但是,我们不能直接在返回行之前插入它,因为这会导致发送数据和关闭网络连接之间的竞争条件层,可能会丢弃数据。

为了正确处理这些情况,Netty 使用了Future 系统,该系统允许代码在异步操作发生后处理事件。

幸运的是,Netty 已经 has a build in solution 为此,我们只需要将其连接起来。要将这个解决方案连接到我们的代码,我们必须跟踪 Netty 的 write 方法发出的最新 ChannelFuture

为了正确实现这个解决方案,我们将sendToServer更改为返回write方法的结果:

private ChannelFuture sendToServer(byte[] bytes, Channel channel, int length)
        throws IOException {
    return channel.writeAndFlush(Unpooled.copiedBuffer(bytes, 0, length));
}

然后我们跟踪这个返回值,并在我们想要关闭连接时添加一个包含 Netty 内置解决方案的侦听器:

ChannelFuture lastFuture = null;
for (;;) {
    byte[] bytes = new byte[readLength];
    int readNum = bis.read(bytes, 0, readLength);
    // System.out.println(readNum);
    if (readNum == -1) {
        bis.close();
        fis.close();
        if(lastFuture == null) { // When our file is 0 bytes long, this is true
            channel.close();
        } else {
            lastFuture.addListener(ChannelFutureListener.CLOSE);
        }
        return;
    }
    lastFuture = sendToServer(bytes, channel, readNum);
}

【讨论】:

  • 非常感谢!解决方案是完美的。它不仅帮助我修复了这个错误,而且对 Netty 有了更好的了解。但是你知道为什么不关闭通道就可以打开文本文件而不能打开图像吗?再次感谢您的热情。
  • 大多数文本编辑器都允许您打开仍在按设计编写的文件,想想日志文件之类的文件。这对于图像查看器来说是不同的,通常是打开文件以进行写入意味着图像仍在下载/其他内容,并且无法正确查看。由于我们现在关闭文件的Outputstream,因此图像查看器不再将文件视为正在打开以进行写入
  • 我明白了……你说得对!我有最后一个问题。那就是我发现 if(lastFuture == null) {channel.close();} 即使文件是 0 KB 也永远不会执行。当未来返回为空?如果你知道,请告诉我,我将不胜感激。
猜你喜欢
  • 1970-01-01
  • 2016-01-14
  • 2013-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 1970-01-01
相关资源
最近更新 更多