NIO
Java在1.4版本开始,引入了NIO,称为Java New IO。又称老的阻塞IO为OIO(Old IO)。
NIO与OIO对比:
-
OIO是面向流,操作的是字节。NIO引入了Buffer和Channel,操作的是缓冲区。
-
OIO是阻塞的,NIO是非阻塞的
-
OIO没有Selector的概念
NIO的三大组件如下所示。
Buffer
数据存储的媒介。
Buffer其实是对数组的包装,定义了一些属性来保证同时读写。有ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer一些属性fer,ShortBuffer,MappedByteBuffer。使用最多的是ByteBuffer。Buffer的缓冲区没有定义在Buffer类中,而是定义在子类中。如ByteBuffer中定义了byte[]作为缓冲区。
为了记录读写的位置,Buffer类提供了一些重要属性。
-
capacity
缓冲区的容量,表示Buffer类能存储的数据大小,一旦初始化就不能改变。不是表示字节数,而是表示能存储的数据对象数。比如CharBuffer的capacity是100,能100个char。 -
position
Buffer类有两种模式:
-
读模式:表示读取数据的位置。创建Buffer时为0,读取数据后移动到下一个位置,limit为读取的上限,当position的值为limit时,表示没有数据可读。
-
写模式:表示下一个可写的位置。创建Buffer时为0,写入数据后移动到下一个位置,capacity为写入的上限,当position的值为capacity时,表示Buffer没有空间可写入。
读写模式怎么切换呢?刚创建Buffer时为写模式,写入数据后要想读取,必须切换为读模式,可以调用flip
方法:此时会将limit设置为写模式的position值,表示可以读取的最大位置。position设为0,表示从头开始读取。
写模式:
读模式:
3.limit
-
读模式:读取的最大位置
-
写模式:可写入的最大上限。
4.mark
调用mark()方法将position设置到mark中,再调用reset()方法将mark的值恢复到position属性中。重新从position读取。
重要方法
allocate
allocate创建一个Buffer并分配空间:
@Testpublic void testAllocate() {IntBuffer intBuffer = IntBuffer.allocate(20);System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
可以看到创建缓冲区后处于写模式,初始position为0,limit和capacity为allocate传入的参数。
put
创建Buffer后,可以调用put往Buffer中填充数据,只要保证数据的类型和Buffer的类型保持一致。
@Testpublic void testPut() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
写入5个元素后,position变为5,指向第6个位置,而limit,capacity没有变化。
flip
写入数据后不能直接读取,而是需要调用flip转换为读模式。
@Testpublic void testFlip() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
可以看到,flip切换为读模式后,position设为0,limit的值是之前写入的position的值。同时清除mark标记。查看flip源码:
public Buffer flip() {limit = position;position = 0;mark = -1;return this;}
不然,切换为读取后,position的值已修改,不清除mark标记会导致position混乱。
那么,读取完成后怎么切换为写模式,继续写入呢?可以调用clear清空Buffer或compact压缩Buffer。
clear
@Testpublic void testClear() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("读取数据并处理");while (intBuffer.hasRemaining()) {int i = intBuffer.get();System.out.println(i);}System.out.println("读取数据完成");System.out.println();System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println();System.out.println("清空Buffer");intBuffer.clear();System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
可以看到调用clear后,Buffer的状态和allocate时的状态一致。
compact
@Testpublic void testCompact() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("读取数据并处理");for (int i = 0; i < 2; i++) {System.out.println(intBuffer.get());}System.out.println("读取数据完成");System.out.println();System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println();System.out.println("压缩Buffer");intBuffer.compact();System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
看到调用compact后会将position到limit范围内(不包含limit位置)的元素往缓冲区的头部移动。上面的例子中读取后剩余3个元素,compact后position的值为3.
get
当调用flip切换为读模式后,就可以调用get获取数据了。
@Testpublic void testGet() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("切换flip");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println(intBuffer.get());System.out.println("调用get()后");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println(intBuffer.get(1));System.out.println("调用get(i)后");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
看出调用get()获取数据后会改变position的值,而调用get(int i)则不会改变position的值
。
rewind
读取完所有数据后,可以重新读取吗?可以调用rewind。
@Test
public void testRewind() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("切换flip");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println("读取数据并处理");while (intBuffer.hasRemaining()) {System.out.println(intBuffer.get());}System.out.println("读取数据完成");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println();intBuffer.rewind();System.out.println("调用rewind后");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());
}
可以看到调用rewind后会将position设置为0,limit不变。查看JDK源码:
public Buffer rewind() {position = 0;mark = -1;return this;}
发现会清除mark标记。
mark()和reset()
mark()将当前的position保存到mark属性中,reset()将mark属性设置到position,可以从position开始读取了。
@Testpublic void testMarkReset() {IntBuffer intBuffer = IntBuffer.allocate(20);for (int i = 0; i < 5; i++) {intBuffer.put(i);}intBuffer.flip();System.out.println("切换flip");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println("读取数据并处理");int j = 0;while (intBuffer.hasRemaining()) {if (j == 3) {intBuffer.mark();}System.out.println(intBuffer.get());j++;}System.out.println("读取数据完成");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());System.out.println();intBuffer.reset();System.out.println("调用reset后");System.out.println("position:"+intBuffer.position());System.out.println("limit:"+intBuffer.limit());System.out.println("capacity:"+intBuffer.capacity());}
调用reset后将position重置为mark属性了。
Channel
数据传输的媒介。一个Channel表示一个Socket连接。更通用的来说,一个Channel表示一个文件描述符,可以表示文件,网络连接,硬件设备等。最重要的Channel有四种:FileChannel,SocketChannel,ServerSocketChannel,DatagramChannel。
FileChannel
文件通道,用于文件的读写。
读取
首先通过FileInputStream.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
@Test
public void testGetFileChannel() {try(FileInputStream fileInputStream = new FileInputStream(filePath)) {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);FileChannel channel = fileInputStream.getChannel();int read = channel.read(byteBuffer);if (read == -1) {return;}System.out.println("读到"+read+"字节");byteBuffer.flip();byte[] bytes = new byte[read];byteBuffer.get(bytes);System.out.println("数据是:" + new String(bytes));channel.close();} catch (IOException e) {e.printStackTrace();}}
写入
首先通过FileInputStream.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
@Testpublic void testWrite() {try(FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("你好啊".getBytes(StandardCharsets.UTF_8));FileChannel channel = fileOutputStream.getChannel();byteBuffer.flip();int len;while ((len = channel.write(byteBuffer)) >0) {System.out.println("写入"+len+"字节");}channel.close();} catch (IOException e) {e.printStackTrace();}}
写入成功后,查看文件内容:
RandomAccessFile
首先通过RandomAccessFile.getChannel()获取FileChannel,然后调用int read(ByteBuffer dst)方法读取数据到ByteBuffer中并返回读到的字节数。
@Test
public void testRandomAccessFile() {try(RandomAccessFile randomAccessFile = new RandomAccessFile(filePath,"r")) {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);FileChannel channel = randomAccessFile.getChannel();int read = channel.read(byteBuffer);if (read == -1) {return;}System.out.println("读到"+read+"字节");byteBuffer.flip();byte[] bytes = new byte[read];byteBuffer.get(bytes);System.out.println("数据是:" + new String(bytes));channel.close();} catch (IOException e) {e.printStackTrace();}
}
关闭
使用完Channel后调用close方法关闭通道。
强制刷新到磁盘
通过write写入数据后,数据先保存到缓冲区中,要保证数据写入磁盘,必须调用channel.force将数据刷新到磁盘。
例子
看个简单例子,通过Channel复制文件。
@Test
public void testCopyFile() {String srcFilePath = "/home/shigp/图片/1.jpg";String destFilePath = "/home/shigp/图片/2.jpg";try (FileInputStream fileInputStream = new FileInputStream(srcFilePath);FileOutputStream fileOutputStream = new FileOutputStream(destFilePath)) {Path path = Path.of(destFilePath);if (!Files.exists(path)) {Files.createFile(path);}FileChannel srcChannel = fileInputStream.getChannel();FileChannel destChannel = fileOutputStream.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(4096);int len;while((len = srcChannel.read(byteBuffer)) > 0) {byteBuffer.flip();int size;while((size = destChannel.write(byteBuffer)) > 0) {}byteBuffer.clear();}} catch (IOException e) {e.printStackTrace();}
}
除了Channel的操作外,还需注意Buffer的读写状态切换。以上的效率不是很高,可以使用FileChannel.transferTo方法。
@Test
public void testTransferTo() {String srcFilePath = "/home/shigp/图片/1.jpg";String destFilePath = "/home/shigp/图片/2.jpg";try (FileInputStream fileInputStream = new FileInputStream(srcFilePath);FileOutputStream fileOutputStream = new FileOutputStream(destFilePath)) {Path path = Path.of(destFilePath);if (!Files.exists(path)) {Files.createFile(path);}File file = new File(srcFilePath);FileChannel srcChannel = fileInputStream.getChannel();FileChannel destChannel = fileOutputStream.getChannel();for (long count = srcChannel.size(); count > 0; ) {long transfer = srcChannel.transferTo(srcChannel.position(), count, destChannel);count -= transfer;}destChannel.force(true);} catch (IOException e) {e.printStackTrace();}
}
SocketChannel,ServerSocketChannel
涉及网络连接的通道有两个,一个是SocketChannel,负责数据的传输,对应OIO中的Socket; 一个是ServerSocketChannel,负责监听连接,对应OIO中的ServerSocket。
SocketChannel和ServerSocketChannel都支持阻塞和非阻塞两种模式。都是通过调用configureBlocking方法实现的,参数false表示设置为非阻塞模式,参数true表示设置为阻塞模式。
SocketChannel通过read从Channel中读取数据到Buffer。通过write将Buffer中的数据写入到Channel中。在关闭Channel前,可以通过调用shutdownOutput终止输出,发送一个输出结束标志。在调用close关闭通道。
DatagramChannel
DatagramChannel也可以通过调用configureBlocking(false)将Channel设置为非阻塞模式。
DatagramChannel调用receive从Channel中读取数据到Buffer中。通过send将Buffer中的数据发送到Channel中。但是要指定接收数据的IP和端口,因为UDP是面向无连接的。调用close关闭Channel。
Selector
一个线程管理多个Socket连接。Selector的作用是完成IO多路复用,通道的注册,监听,事件查询。通道通过register的方式注册到Selector中。register方法有两个参数:第一个参数是要注册到的Selector,第二个参数是Channel感兴趣的IO事件类型:
- SelectionKey.OP_READ:可读
- OP_WRITE:可写
- OP_CONNECT:建立连接
- OP_ACCEPT:接收
如果要注册多个事件类型,每个事件类型用或运算符(|)连接。
那是不是什么类型的Channel都可以注册到Selector中?并不是。只有继承了SelectableChannel的通道才能被注册到Selector中,且Channel不能被设置为阻塞。
Channel注册到Selector后,可以调用Selector的select方法获取已被Selector监控且IO已就绪的IO事件及相应的通道,也就是SelectionKey。通过SelectionKey可以获取到已就绪的IO事件类型,发生的Channel。然后根据IO事件类型做不同的处理。处理完后将SelectionKey移除,避免下次重复调用。
SElector的select方法有多个重载版本:
- select():阻塞调用,直到有一个Channel发生了感兴趣的IO事件
- select(long timeout):和select() 一样,但是最长阻塞时间为timeout毫秒
- selectNow():非阻塞,不管是否发生感兴趣的IO事件都立刻返回。
例子,实现一个Discard服务器和客户端
Discard服务器仅接收客户端通道的数据并直接丢弃,然后关闭客户端。
public class DiscardServer {public static void startServer() throws Exception{// TCP服务端ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 设置为非阻塞serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",10880));DatagramChannel datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);datagramChannel.bind(new InetSocketAddress("127.0.0.1", 10890));Selector selector = Selector.open();// 将通道注册到Selector中,并设置感兴趣的IO事件为accept,也就是接收客户端连接serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);datagramChannel.register(selector, SelectionKey.OP_READ);// 发生了注册的感兴趣的IO事件while (selector.select() > 0) {// 获取感兴趣的IO事件集合Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey selectionKey = iterator.next();if (selectionKey.isAcceptable()) {//连接客户端ServerSocketChannel sChannel = (ServerSocketChannel) selectionKey.channel();SocketChannel channel = sChannel.accept();// 设置为非阻塞channel.configureBlocking(false);// 将客户端通道注册到Selector中,设置感兴趣的IO事件为readchannel.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {// 从客户端通道中读取SelectableChannel selectableChannel = selectionKey.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);if (selectableChannel instanceof SocketChannel) {SocketChannel channel = (SocketChannel) selectableChannel;int len;while ((len = channel.read(byteBuffer)) > 0) {byteBuffer.flip();System.out.println("读取的数据是:" + new String(byteBuffer.array(), 0 ,byteBuffer.limit()));byteBuffer.clear();}// 关闭客户端channel.close();} else if (selectableChannel instanceof DatagramChannel) {DatagramChannel channel = (DatagramChannel) selectableChannel;SocketAddress receive = channel.receive(byteBuffer);byteBuffer.flip();System.out.println("从"+ receive +"读取的数据是:" + new String(byteBuffer.array(), 0 ,byteBuffer.limit()));byteBuffer.clear();// 关闭客户端channel.close();}}// 移除selectionKeyiterator.remove();}}selector.close();serverSocketChannel.close();}public static void main(String[] args) {try {startServer();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}
}
TCP客户端:
public class DiscardTCPClient {public static void startClient() throws Exception {SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);// 连接Discard服务器socketChannel.connect(new InetSocketAddress("127.0.0.1", 10880));while(!socketChannel.finishConnect()) {}System.out.println("已经与服务器建立连接");ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("你好啊".getBytes(StandardCharsets.UTF_8));byteBuffer.flip();socketChannel.write(byteBuffer);socketChannel.shutdownOutput();socketChannel.close();}public static void main(String[] args) {try {startClient();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}
}
UDP客户端:
public class DiscardUDPClient {public static void startClient() throws Exception {DatagramChannel datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("发送UDP".getBytes(StandardCharsets.UTF_8));byteBuffer.flip();datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 10890));datagramChannel.close();}public static void main(String[] args) {try {startClient();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}
}
例子,实现传输文件的服务器和客户端
服务器
public class SendFileServer {public final static String QUERY_FILE_PATH = "/home/shigp/图片";public static void main(String[] args) {try {startServer();} catch (Exception e) {e.printStackTrace();}}public static void startServer() throws Exception {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.bind(new InetSocketAddress("localhost",10900));Selector selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (selector.select() > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {SelectionKey selectionKey = iterator.next();if (selectionKey.isAcceptable()) {ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();SocketChannel channel = serverSocketChannel1.accept();channel.configureBlocking(false);System.out.println("接受连接");channel.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {System.out.println("可读");SocketChannel channel = (SocketChannel) selectionKey.channel();ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();if (byteBuffer == null) {byteBuffer = ByteBuffer.allocate(2048);} else {System.out.println("start,position="+byteBuffer.position());}int len;int tmpPosition = 0;int tmpLimit = 0;int sum = 0;while ((len = channel.read(byteBuffer)) > 0) {sum += len;System.out.println("1.读取字节数:"+len+",position="+byteBuffer.position());if (byteBuffer.position() < 4) {continue;}byteBuffer.flip();tmpPosition = byteBuffer.position();tmpLimit = byteBuffer.limit();System.out.println("while,limit="+byteBuffer.limit()+",position="+byteBuffer.position());int tmpFileNameSize = byteBuffer.getInt();System.out.println("while,limit="+byteBuffer.limit()+",position="+byteBuffer.position());byteBuffer.position(tmpPosition);byteBuffer.limit(tmpLimit);byteBuffer.compact();if (tmpLimit >= tmpFileNameSize + 4){System.out.println("2.读取字节数:"+len+",position="+byteBuffer.position());break;}System.out.println("3.读取字节数:"+len+",position="+byteBuffer.position());}byteBuffer.flip();System.out.println("1234,limit="+byteBuffer.limit()+",position="+byteBuffer.position());tmpPosition = byteBuffer.position();tmpLimit = byteBuffer.limit();int tmpFileNameSize = byteBuffer.getInt();byteBuffer.position(tmpPosition);byteBuffer.limit(tmpLimit);if (tmpLimit >= tmpFileNameSize + 4){System.out.println("1.读取到的文件名是:" + new String(byteBuffer.array(), 4 , tmpFileNameSize));channel.register(selector, SelectionKey.OP_WRITE);selectionKey.attach(new String(byteBuffer.array(), 4 , tmpFileNameSize));} else {System.out.println("position="+byteBuffer.position());byteBuffer.compact();selectionKey.attach(byteBuffer);}} else if (selectionKey.isWritable()) {System.out.println("可写");SocketChannel channel = (SocketChannel) selectionKey.channel();String fileName = (String) selectionKey.attachment();System.out.println(fileName);File file = traverseFolder(new File(QUERY_FILE_PATH), fileName);ByteBuffer allocate = ByteBuffer.allocate(8);if (file == null) {System.out.println("没有找到文件:" + fileName);allocate.putLong(-1L);allocate.flip();while (channel.write(allocate) > 0) {}} else {allocate.putLong(file.length());allocate.flip();while (channel.write(allocate) > 0) {}System.out.println("写入文件大小成功,"+file.length());FileChannel channel1 = new FileInputStream(file).getChannel();for (long count = file.length(); count > 0;) {long transfer = channel1.transferTo(channel1.position(), count, channel);count -= transfer;}System.out.println("写入文件内容成功");channel1.close();}channel.close();}iterator.remove();}}}public static File traverseFolder(File folder,String fileName) {if (!folder.exists()) {return null;}if (folder.isDirectory()) {for (File file : folder.listFiles()) {if (file.isDirectory()) {traverseFolder(file,fileName);} else if (fileName.equals(file.getName())){return file;}}}return null;}
}
客户端
public class SendFileClient {
public static void main(String[] args) {try {receive();} catch (Exception e) {e.printStackTrace();}
}public static void receive() throws Exception {String fileName = "Spark教程.docx";String destFileName = "/home/shigp/文档/client";File file1 = new File(destFileName);if (!file1.exists()) {file1.mkdirs();}File file = new File(destFileName + File.separator + fileName);// if (file.exists()) {
// file.createNewFile();
// }FileChannel fileChannel = new FileOutputStream(destFileName + File.separator + fileName).getChannel();SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("localhost",10900));while (!socketChannel.finishConnect()) {}byte[] bytes = fileName.getBytes(StandardCharsets.UTF_8);ByteBuffer byteBuffer = ByteBuffer.allocate(2048);byteBuffer.putInt(bytes.length);byteBuffer.flip();while (socketChannel.write(byteBuffer) >0) {}byteBuffer.clear();byteBuffer.put(bytes);byteBuffer.flip();while (socketChannel.write(byteBuffer) >0) {}System.out.println("写入成功");System.out.println("开始接收文件");byteBuffer.clear();int len;long fileSize = 0L;int tpmPosition = 0;int tmpLimit = 0;long readFileSize = 0L;boolean isReadFileSize = true;int sum =0;int writeBytes = 0;while ((len = socketChannel.read(byteBuffer)) >= 0) {if (len == 0){continue;}sum += len;byteBuffer.flip();tmpLimit=byteBuffer.limit();tpmPosition = byteBuffer.position();if (isReadFileSize) {if (tmpLimit >= 8) {fileSize = byteBuffer.getLong();byteBuffer.position(8);isReadFileSize=false;readFileSize += (tmpLimit-8);} else {byteBuffer.position(tpmPosition);}} else {readFileSize += len;if (readFileSize>=fileSize) {byteBuffer.limit(tmpLimit);break;}}byteBuffer.limit(tmpLimit);boolean hasRemaining = byteBuffer.hasRemaining();int size = 0;if (!isReadFileSize) {while (hasRemaining && (size = fileChannel.write(byteBuffer)) > 0) {hasRemaining = byteBuffer.hasRemaining();writeBytes += size;}byteBuffer.clear();}}if (byteBuffer.hasRemaining()) {int size = 0;while ((size=fileChannel.write(byteBuffer)) > 0) {writeBytes += size;}}System.out.println("sum="+sum);System.out.println("readFileSize="+readFileSize+",fileSize="+fileSize);System.out.println("写入字节="+writeBytes);if (fileSize >=0) {if (readFileSize>=fileSize) {System.out.println("接收文件成功");} else {System.out.println("接收文件失败");}} else {System.out.println("文件不存在");}fileChannel.force(true);fileChannel.close();socketChannel.close();
}
}