import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @desc 基于Java AIO ---client
**/
public class Client implements CompletionHandler<Integer,Integer>
{
private static Logger log = LoggerFactory.getLogger(Client.class);
// 异步 Socket 通道
private AsynchronousSocketChannel asc;
// 读写共用的缓冲区
private ByteBuffer buffer = ByteBuffer.allocate(1024);
// 当前要读取的文件输入流
private FileInputStream fis;
// 当前要读取的文件通道
private FileChannel fc;
// 待发送文件的最大编号
private int maxFileNo = 2;
public void start() throws IOException, ExecutionException, InterruptedException
{
// 创建 Socket 通道
asc = AsynchronousSocketChannel.open();
// 建立连接,用 Future 模式等待连接的建立
Future<Void> ft = asc.connect(new InetSocketAddress("127.0.0.1",9000));
ft.get();
// 从第 0 个文件开始发送,也可以将文件集合的 iterator 作为参数
this.send(0);
}
public void send(Integer i) throws IOException
{
// 此方法会反复被调用。发送新文件前,需要先确保上一个文件已经被关闭
StreamUtil.close(fis);
StreamUtil.close(fc);
// 读取当前文件,参数 i 为当前要读取的文件编号
String fn = String.format("G:/%d.txt",i);
fis = new FileInputStream(new File(fn));
fc = fis.getChannel();
// 首次读取的时候,读取文件大小,并作为前 8 字节发送
buffer.clear();
// 要发送的字节数为文件大小 + 文件长度变量(Long 类型)所占的 8 字节
buffer.putLong(fc.size() + 8);
// 将文件内容读取到 buffer 。
fc.read(buffer);
// 将 buffer 的 limit 设置为当前 position (有效字节数),position 设置为 0
buffer.flip();
log.info("Write first buffer of file {}",fn);
// 异步发送
asc.write(buffer,i,this);
}
@Override
public void completed(Integer result, Integer attachment)
{
if( result <= 0 )
{
log.info("No written data now. Quit");
return;
}
log.info("Written {} bytes",result);
try
{
// 上次发送完可能没发完,需要继续发送
if(buffer.hasRemaining())
{
asc.write(buffer ,attachment,this);
return;
}
buffer.clear();
// 将文件内容读取到缓冲区
if(fc.read(buffer)>0)
{
buffer.flip();
asc.write(buffer ,attachment,this);
}else
{
// 读不到则说明已经遇到文件尾,表示发送完成。这时可以读取服务器端的响应
result = asc.read(buffer).get();
log.info("Read response {} bytes ",result);
buffer.flip();
// 发送下一个文件
if(attachment < maxFileNo)
{
this.send( attachment + 1);
}else
{
// 关闭输出流,告诉服务端,数据已经发送完成
asc.shutdownOutput();
// 通知主线程继续执行(退出)
synchronized (this)
{
this.notify();
}
}
}
}
catch (Exception e)
{
log.error("Error on send file",e);
this.close();
}
}
@Override
public void failed(Throwable exc, Integer attachment)
{
// 遇到异常,直接关闭
this.close();
SocketAddress sa = null;
try
{
sa = asc.getRemoteAddress();
}
catch (IOException e)
{
log.error("Error on getRemoteAddress",e);
}
log.error("Error on read from {}",sa , exc);
}
public void close()
{
StreamUtil.close(fc);
StreamUtil.close(fis);
StreamUtil.close(asc);
}
public static void main(String[] args)
{
Client client = new Client();
try
{
client.start();
// 阻塞主线程,等待 Client 完成传输并唤醒自己
synchronized (client)
{
client.wait();
}
}
catch (Exception e)
{
log.error("Error on run client",e);
}finally
{
client.close();
}
System.out.println("bye");
}
}
----------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* @desc 基于Java AIO ---Server
**/
public class Server implements CompletionHandler<AsynchronousSocketChannel,Object>
{
private static final Logger log = LoggerFactory.getLogger(Server.class);
private AsynchronousServerSocketChannel assc = null;
public void start() throws IOException
{
//创建服务端 Socket ,开始监听
assc = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(9000));
// 接收新连接,操作系统会在有新连接请求的时候,调用 this 的 completed()
assc.accept(null,this);
}
@Override
public void completed(AsynchronousSocketChannel asc, Object attachment)
{
// 继续受理一个连接
assc.accept(null,this);
// 为当前通道准备一个 Read 对象,此 Read 对象拥有一个 buffer , 并持有当前通道的引用
ReadCH reader = new ReadCH(asc);
asc.read(reader.getBuffer(),0,reader);
}
@Override
public void failed(Throwable exc, Object attachment)
{
this.close();
log.error("Error on accept connection",exc);
}
public void close()
{
StreamUtil.close(assc);
}
public static void main(String[] args)
{
BufferedReader br = null;
Server server = new Server();
try
{
server.start();
String cmd = null;
System.out.println("Enter 'exit' to exit");
br = new BufferedReader(new InputStreamReader(System.in));
while( (cmd = br.readLine()) != null)
{
if("exit".equalsIgnoreCase(cmd))
{
break;
}
}
}
catch (Exception e)
{
log.error("Error on run server",e);
}finally
{
StreamUtil.close(br);
server.close();
}
System.out.println("bye");
}
}
---------------
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel;
/**
* @desc 基于Java AIO -- Server 端的处理对象
**/
public class ReadCH implements CompletionHandler<Integer , Integer>
{
private static final Logger log = LoggerFactory.getLogger(ReadCH.class);
private AsynchronousSocketChannel asc;
private ByteBuffer buffer = ByteBuffer.allocate(1024);
// 当前这笔数据的总字节数
private long total;
// 当前这笔数据已接收到的字节数
private long received;
private FileOutputStream fos;
private FileChannel fc;
public ReadCH(AsynchronousSocketChannel asc)
{
this.asc = asc;
}
public ByteBuffer getBuffer()
{
return buffer;
}
@Override
public void completed(Integer result, Integer attachment)
{
if(result <= 0)
{
// 当客户端调用 Socket 通道的 shutdownOutput 时会进到这里
log.info("No more incoming data now . Quit");
return;
}
received += result;
log.info("Read {}/{}/{} bytes",result,received,total);
try
{
buffer.flip();
// fc 为空,表示是一个新文件
if(fc == null)
{
//首 8 个字节记录了要传输的字节总数(包括这个8个字节)
total = buffer.getLong();
// 打开文件通道,准备写入
fc = fos.getChannel();
}
// 写入(从第 9 个字节开始)
fc.write(buffer);
buffer.clear();
if( received < total)
{
// 没接收完就继续 read
asc.read(buffer,attachment,this);
}else
{
// 接收完则发回响应,依次是应发字节总数、已接收成功的字节数
buffer.putLong(total);
buffer.putLong(received);
buffer.flip();
//write 后等待 I/O 完成
result = asc.write(buffer).get();
log.info("W ritten response {} bytes",result);
// 重设 reader ,准备在此通道上的下一次读取
this.reset();
// 读取下一个文件的数据
this.asc.read(buffer,attachment + 1 , this);
}
}
catch (Exception e)
{
log.error("Error on receive file",e);
this.close();
}
}
@Override
public void failed(Throwable exc, Integer attachment)
{
// 遇到异常,直接关闭
this.close();
log.error("Error on read from{}",exc);
this.close();
}
public void reset()
{
StreamUtil.close(fos);
fos = null;
StreamUtil.close(fc);
fc = null;
buffer.clear();
total = 0 ;
received = 0 ;
}
public void close()
{
this.reset();
StreamUtil.close(asc);
}
}
---------------------------------------------
import java.io.Closeable;
import java.io.IOException;
/**
* @desc 关闭I/O流的工具类
**/
public class StreamUtil
{
public static void close(Closeable p){
if( p == null){
return ;
}
try {
p.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
该博客展示了如何使用Java的异步IO(AIO)实现客户端和服务器之间的文件传输。客户端通过AsynchronousSocketChannel建立连接,读取本地文件内容并发送到服务器。服务器端接收连接,读取文件大小信息,接收文件内容并写入本地,同时返回确认信息。整个过程利用CompletionHandler处理异步操作,实现了高效的数据传输。
1797

被折叠的 条评论
为什么被折叠?



