可以同时使用2个WritableByteChannels吗?

时间:2012-02-09 20:55:26

标签: android nio channel

当我直接写入2个输出流时,一切正常。当我尝试写入2个频道时,第二个看起来似乎没有收到它。

有谁知道是否可以同时写入2个WritableByteChannels?如果没有,还有什么其他的想法,我可以做什么来执行相同的操作仍然使用NIO /频道?

connection2 = new Socket(Resource.LAN_DEV2_IP_ADDRESS, Resource.LAN_DEV2_SOCKET_PORT); 
out2 = connection2.getOutputStream(); 

connection = new Socket(Resource.LAN_HOST_IP_ADDRESS, Resource.LAN_HOST_SOCKET_PORT); 
out = connection.getOutputStream();         

File f = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename);

in = new FileInputStream(f);
fic = in.getChannel();
fsize = fic.size();
channel2 = Channels.newChannel(out2); 
channel = Channels.newChannel(out); 

//Send Header
byte[] p = createHeaderPacket(filename, f.length());
out2.write(p); // Received correctly
out.write(p);  // Received correctly

//Send file
long currPos = 0;
while (currPos < fsize)
{
    if (fsize - currPos < Resource.MEMORY_ALLOC_SIZE)
    {                       
        mappedByteBuffer = fic.map(FileChannel.MapMode.READ_ONLY, currPos, fsize - currPos);
        channel2.write(mappedByteBuffer); // Received correctly
        channel.write(mappedByteBuffer);  // Never received
        currPos = fsize;
    }
    else
    {
        mappedByteBuffer = fic.map(FileChannel.MapMode.READ_ONLY, currPos, Resource.MEMORY_ALLOC_SIZE);
        channel2.write(mappedByteBuffer); // Received correctly
        channel.write(mappedByteBuffer);  // Never received
        currPos += Resource.MEMORY_ALLOC_SIZE;
    }
}

2 个答案:

答案 0 :(得分:2)

尝试:

channel2.write(mappedByteBuffer.duplicate());
channel.write(mappedByteBuffer);

理解NIO缓冲区的方法是牢记其基本属性:

  • 底层数据存储(通常是普通的字节数组,但可以是其他东西,例如文件的内存映射区域);
  • 该基础空间内的起点和容量;
  • 您当前的位置在缓冲区中;和
  • 缓冲区的限制

NIO提供的所有缓冲操作都记录了操作如何影响这些属性。例如,WritableByteChannel.write()文档告诉我们:

  • 在0和src.remaining()之间(包括)字节将被写入通道;和
  • 如果count字节被写入,当count返回时,ByteBuffer的位置将增加write()

所以看看原始代码:

channel2.write(mappedByteBuffer); // Received correctly
channel.write(mappedByteBuffer);  // Never received

如果第一次写入将整个剩余的mappedByteBuffer写入channel2,则在该语句mappedByteBuffer.remaining()将为零之后,对channel的写入将不会写入任何字节所有

因此我的建议是在第一次写作时使用ByteBuffer.duplicate()。此方法返回一个新的ByteBuffer对象:

  • 共享原始缓冲区的底层存储(因此您不会在内存中对要写入两次的实际字节进行不必要的复制);但
  • 有自己的位置(和剩余)值,所以当channel2.write()调整(重复)ByteBuffer的位置时,它会在原始缓冲区中保持位置不变,

所以channel.write()仍然会收到预期的字节范围。

作为替代方案,你也可以写:

mappedByteBuffer.mark(); // store the current position
channel2.write(mappedByteBuffer);
mappedByteBuffer.reset(); // move position to the previously marked position
channel.write(mappedByteBuffer);

我也倾向于同意EJP的观点,即你可能没有在这里充分利用MappedByteBuffer。您可以将复制循环简化为:

ByteBuffer buffer = ByteBuffer.allocate(Resource.MEMORY_ALLOC_SIZE);
while (fic.read(buffer) >= 0) {
    buffer.flip();
    channel2.write(buffer.duplicate());
    channel.write(buffer);
}

此处read()方法通过从通道读取的字节数增加位置,然后flip()方法将限制设置为该位置,并将位置设置回0 ,这意味着您刚读取的字节位于write()将消耗的剩余范围内。

然而,你会注意到EJP的循环比这复杂一点。那是因为通道上的写操作可能不一定会写入每个剩余的字节。 (write()文档给出了以非阻塞模式打开的网络套接字示例。)但是,示例代码(以及ByteBuffer.compact()文档中的类似示例)依赖于您是只写一个频道;当你写两个不同的通道时,你必须处理两个通道可能接受不同字节数的事实。所以:

ByteBuffer buffer = ByteBuffer.allocate(Resource.MEMORY_ALLOC_SIZE);
while (fic.read(buffer) >= 0) {
    buffer.flip();

    buffer.mark();
    while (buffer.hasRemaining()) {
        channel2.write(buffer);
    }

    buffer.reset():
    while (buffer.hasRemaining()) {
        channel.write(buffer);
    }

    buffer.clear();
}

答案 1 :(得分:1)

当然可以同时使用多个频道,但更多的是发送文件的可怕方式。创建大量MappedByteBuffers会导致各种问题,因为底层映射区域永远不会被释放。只需将其作为普通通道打开并使用规范的NIO复制循环:

while (in.read(buffer) >= 0 || buffer.position() > 0)
{
    buffer.flip();
    out.write(buffer);
    buffer.compact();
}