1. 选择合适的I/O模型
- 阻塞I/O(BIO):适用于连接数较少且固定的场景。在文件传输中,如果客户端和服务器之间的连接相对稳定,且传输文件的操作不需要同时处理大量并发请求,可以选择BIO。BIO在进行Socket读写操作时会阻塞当前线程,直到操作完成。例如:
try (Socket socket = new Socket("localhost", 12345);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 进行文件读写操作
} catch (IOException e) {
e.printStackTrace();
}
- 非阻塞I/O(NIO):适合处理大量并发连接的场景。NIO使用多路复用器(Selector)来监控多个通道(Channel)的状态,当某个通道有数据可读或可写时,Selector会通知应用程序进行相应操作。在文件传输中,如果需要同时处理多个客户端的文件传输请求,NIO能显著提升性能。示例代码如下:
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(12345));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
for (SelectionKey key : selectedKeys) {
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
buffer.flip();
// 处理读取的数据
}
}
selectedKeys.clear();
}
- 异步I/O(AIO):在JDK 7引入,是真正意义上的异步I/O。应用程序发起I/O操作后无需等待操作完成,操作系统完成I/O操作后会通知应用程序。在文件传输场景中,如果对响应时间要求极高,且硬件和操作系统支持异步I/O,可以选择AIO。示例代码如下:
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
client.connect(new InetSocketAddress("localhost", 12345)).get();
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = client.read(buffer);
while (!future.isDone()) {
// 可以做其他事情
}
buffer.flip();
// 处理读取的数据
2. 利用多线程提升性能
- 线程的创建:
- 使用
Thread
类:创建一个继承自Thread
类的子类,重写run
方法,在run
方法中实现文件传输逻辑。
class FileTransferThread extends Thread {
private Socket socket;
public FileTransferThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 文件传输操作
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 使用
Runnable
接口:创建一个实现Runnable
接口的类,实现run
方法,然后将该实例传递给Thread
类的构造函数创建线程。
class FileTransferRunnable implements Runnable {
private Socket socket;
public FileTransferRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 文件传输操作
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 创建线程
Thread thread = new Thread(new FileTransferRunnable(socket));
thread.start();
- 使用线程池:通过
ExecutorService
创建线程池来管理线程。线程池可以重用线程,避免频繁创建和销毁线程带来的开销。
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(new FileTransferRunnable(socket));
}
executorService.shutdown();
- 线程的管理:
- 线程池的使用:如上述代码,使用
ExecutorService
创建线程池。可以根据系统资源和预估的并发请求数量来调整线程池的大小。newFixedThreadPool
创建一个固定大小的线程池,newCachedThreadPool
创建一个可缓存的线程池,newSingleThreadExecutor
创建一个单线程的线程池。
- 线程生命周期管理:使用
Thread.join()
方法可以让主线程等待子线程执行完毕。例如在文件传输完成后,主线程可能需要等待所有传输线程完成后再进行后续操作。
Thread thread = new Thread(new FileTransferRunnable(socket));
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
- 与Socket和文件I/O操作的协同工作:
- 在BIO场景下:每个线程负责一个Socket连接的文件传输。在线程的
run
方法中,通过Socket
获取InputStream
和OutputStream
,然后使用FileInputStream
和FileOutputStream
进行文件的读取和写入。
class FileTransferThread extends Thread {
private Socket socket;
public FileTransferThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (InputStream socketInputStream = socket.getInputStream();
OutputStream socketOutputStream = socket.getOutputStream();
FileInputStream fileInputStream = new FileInputStream("sourceFile.txt");
FileOutputStream fileOutputStream = new FileOutputStream("destinationFile.txt")) {
byte[] buffer = new byte[1024];
int length;
while ((length = fileInputStream.read(buffer)) != -1) {
socketOutputStream.write(buffer, 0, length);
}
socketOutputStream.flush();
while ((length = socketInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在NIO场景下:可以使用多个线程来处理不同的通道。例如,一个线程负责监听新连接(
OP_ACCEPT
事件),其他线程负责处理数据读写(OP_READ
和OP_WRITE
事件)。通过Selector
和Channel
的配合,实现高效的文件传输。
- 在AIO场景下:线程主要用于处理I/O操作完成后的回调。当异步I/O操作完成时,操作系统会调用注册的回调函数,在回调函数中可以进行文件传输的后续处理,如关闭连接、记录传输日志等。