NIO
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并分配空间:
@Test
public 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的类型保持一致。
@Test
public 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转换为读模式。
@Test
public 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
@Test
public 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
@Test
public 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获取数据了。
@Test
public 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开始读取了。
@Test
public 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中并返回读到的字节数。
@Test
public 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事件为read
channel.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();
}
}
// 移除selectionKey
iterator.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();
}
}