我如何知道Netty ByteBuf中是否没有要读取的数据?

时间:2018-08-16 06:52:23

标签: java netty

我是Netty的新手。关于文件传输的问题困扰了我好几天。我想将图片文件从客户端发送到服务器。

下面的代码是可执行的。但是只有我关闭服务器才能强制打开接收到的图像文件。否则,它显示“ 您似乎无权查看此文件。请检查权限,然后重试”。因此,当我使用 ByteBuf.isReadable()在ByteBuf中没有数据时,我想关闭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));
}

}

这是FileServerHandler

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();
    }
}
}

1 个答案:

答案 0 :(得分: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已经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);
}