Netty ByteBuf中如何判断是否没有可读数据?

6

我是一个Netty的新手。有一个与文件传输相关的问题一直困扰着我。我想从客户端向服务器发送图像文件。

下面的代码是可执行的。但只有当我强制关闭服务器后,我才能正常打开接收到的图像文件。否则,它会显示“看起来您没有权限查看此文件。检查权限并重试”。因此,我想在ByteBuf中没有数据时使用ByteBuf.isReadable()关闭fileoutputstream,但是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的系统中工作,请尝试检查文件权限。或者在文件传输完成后尝试关闭通道(服务器和客户端都要关闭)。但首先,只需尝试在传输后关闭通道 - dorintufar
谢谢您的评论。我正在使用Windows。对我来说关键是如何知道ServerHandler中的文件传输是否完成。我尝试在ClientHandler中发送后关闭通道,像这样:**fis.getChannel().close();**。但是没有效果。您知道为什么文本文件可以打开但图像文件不能吗? - Leisure Dong
1个回答

3

修复服务器端

在 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已经有了内置的解决方案,我们只需要将其连接起来。为了将此解决方案连接到我们的代码中,我们必须跟踪Netty的写入方法发出的最新ChannelFuture
为了正确实施这个解决方案,我们将更改sendToServer方法,使其返回写入方法的结果。
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。但是你知道为什么文本文件可以在不关闭通道的情况下打开,而图像却不能吗?再次感谢你的热情。 - Leisure Dong
大多数文本编辑器允许您打开正在编写的文件,例如日志文件。但是对于图像查看器来说情况不同,通常打开一个正在写入的文件意味着该图像仍在下载或进行其他操作,因此无法正确查看。由于我们现在关闭了向文件的“输出流”,因此图像查看器不再将该文件视为正在写入。 - Ferrybig
我明白了...你说得对!我有最后一个问题。就是我发现**if(lastFuture == null) {channel.close();}**即使文件为0 KB,也从未执行过。当返回的future为空时会发生什么?如果你知道,请告诉我,我会非常感激。 - Leisure Dong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接