java 服务端通过FileChannel.transferTo()传输文件到客户端只传输8M的问题
在写socket传输文件过程中想着通过零拷贝api加快传输速度,但是发现文件总是只能传过去8M。代码如下://服务端代码public static void server() {Selector selector = null;ServerSocketChannel server = null;RandomAccessFile randomAccessFile = null;try {//创建一个
在写socket传输文件过程中想着通过零拷贝api加快传输速度,但是发现文件总是只能传过去8M。代码如下:
//服务端代码
public static void server() {
Selector selector = null;
ServerSocketChannel server = null;
RandomAccessFile randomAccessFile = null;
try {
//创建一个server端,监听8080端口,设置非阻塞
server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
//注册selector,事件为接受连接
selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
//打开一个文件流通道
randomAccessFile = new RandomAccessFile("D:\\Program Files\\Java\\jre1.8.0_60.zip","r");
FileChannel fileChannel = randomAccessFile.getChannel();
while(selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
//处理连接事件
if(key.isAcceptable()){
SocketChannel accept = server.accept();
System.out.println("服务端开始写出数据");
long start = System.currentTimeMillis();
//将文件发送到客户端
fileChannel.transferTo(0,randomAccessFile.length(),accept);
System.out.println("服务端写出完成,耗时: "
+ (System.currentTimeMillis() - start)
+ " ms,写出文件大小:" + randomAccessFile.length()/1024/1024 + "MB");
}
//移除当前事件
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
randomAccessFile.close();
selector.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
目标文件大小为61M
//客户端代码
public static void client(){
SocketChannel client = null;
Selector selector = null;
RandomAccessFile randomAccessFile = null;
try {
client = SocketChannel.open(new InetSocketAddress(8080));
client.configureBlocking(false);
selector = Selector.open();
client.register(selector,SelectionKey.OP_READ);
randomAccessFile = new RandomAccessFile("D:\\Program Files\\Java\\test.zip","rw");
FileChannel channel = randomAccessFile.getChannel();
while(selector.select() > 0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("客户端开始写入数据");
long length = 0;
long start = System.currentTimeMillis();
while(client.read(buf) > 0){
buf.flip();
length += buf.limit();
channel.write(buf);
buf.clear();
}
System.out.println("客户端写入完成,耗时:"
+ (System.currentTimeMillis() - start)
+ " ms,写入文件大小:" + length/1024/1024 + "MB");
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
client.close();
selector.close();
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
可以看到只写出了8M文件。这里可以设置服务端transferTo每次写8M循环直到文件传输完毕为止,但是问题并没有解决。经过对源码debug后发现问题如下:
FileChannelImpl
这里调用的transferTo方法是有FileChannelImpl实现的,FileChannelImpl在初始化的时候会创建一个FileDispatcherImpl对象
而在FileDispatcherImpl对象的静态代码块中有一个isFastFileTransferRequested方法
static {
IOUtil.load();
fastFileTransfer = isFastFileTransferRequested();
}
static boolean isFastFileTransferRequested() {
String fileTransferProp = java.security.AccessController.doPrivileged(
new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty("jdk.nio.enableFastFileTransfer");
}
});
boolean enable;
//从上面可以看到,如果没有设置jdk.nio.enableFastFileTransfer这个启动参数
//那么这个enable就是false的
if ("".equals(fileTransferProp)) {
enable = true;
} else {
enable = Boolean.parseBoolean(fileTransferProp);
}
return enable;
}
然后再看到FileChannelImpl的transferTo方法
这里的target是一个SocketChannel,所以这个方法进入,最终会走到这个判断
SelectableChannel sc = (SelectableChannel)target;
if (!nd.canTransferToDirectly(sc))
//返回的是-6
return IOStatus.UNSUPPORTED_CASE;
boolean canTransferToDirectly(java.nio.channels.SelectableChannel sc) {
//这个就是之前被设置为false的变量,所以最终会返回false。
return fastFileTransfer && sc.isBlocking();
}
也就是说返回的是一个小于0的数,所以进入下一个判断。这个方法我们将看到结果了
private long transferToTrustedChannel(long position, long count,
WritableByteChannel target)
throws IOException
{
//这里返回的true
boolean isSelChImpl = (target instanceof SelChImpl);
//false
if (!((target instanceof FileChannelImpl) || isSelChImpl))
return IOStatus.UNSUPPORTED;
// Trusted target: Use a mapped buffer
long remaining = count;
while (remaining > 0L) {
//关键在这, MAPPED_TRANSFER_SIZE是8M大小,也就是这个size每次传输8M
//通过循环完成全部文件的传输
long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
try {
MappedByteBuffer dbb = map(MapMode.READ_ONLY, position, size);
try {
// ## Bug: Closing this channel will not terminate the write
int n = target.write(dbb);
assert n >= 0;
remaining -= n;
//这里是true,会进入。也就是说传输了8M就break了!
if (isSelChImpl) {
// one attempt to write to selectable channel
break;
}
assert n > 0;
position += n;
} finally {
unmap(dbb);
}
} catch (ClosedByInterruptException e) {
// target closed by interrupt as ClosedByInterruptException needs
// to be thrown after closing this channel.
assert !target.isOpen();
try {
close();
} catch (Throwable suppressed) {
e.addSuppressed(suppressed);
}
throw e;
} catch (IOException ioe) {
// Only throw exception if no bytes have been written
if (remaining == count)
throw ioe;
break;
}
}
return count - remaining;
}
所以通过源码可以看到,因为transferTo的目标对象是一个socketChannel,而又没有设置启动参数jdk.nio.enableFastFileTransfer,所以传输8M文件后就直接返回了。
那么解决办法就很简单了,启动参数带上jdk.nio.enableFastFileTransfer就可以了。
更多推荐
所有评论(0)