Java Socket 大文件传输实现指南
在Java中使用Socket进行大文件传输需要考虑多个方面,包括性能优化、内存管理和错误处理,以下是详细的实现方案:
基本实现方案
服务器端代码
import java.io.*;
import java.net.*;
public class FileServer {
public static void main(String[] args) {
int port = 12345;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("服务器启动,等待连接...");
Socket clientSocket = serverSocket.accept();
System.out.println("客户端已连接: " + clientSocket.getInetAddress());
// 接收文件名
DataInputStream dis = new DataInputStream(clientSocket.getInputStream());
String fileName = dis.readUTF();
System.out.println("接收文件名: " + fileName);
// 接收文件大小
long fileSize = dis.readLong();
System.out.println("文件大小: " + fileSize + " bytes");
// 接收文件内容
FileOutputStream fos = new FileOutputStream("received_" + fileName);
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = dis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
System.out.printf("已接收: %.2f%%\n",
(totalBytesRead * 100.0) / fileSize);
}
fos.close();
dis.close();
clientSocket.close();
System.out.println("文件接收完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码
import java.io.*;
import java.net.*;
public class FileClient {
public static void main(String[] args) {
String host = "localhost";
int port = 12345;
String filePath = "large_file.zip";
try (Socket socket = new Socket(host, port);
FileInputStream fis = new FileInputStream(filePath);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream())) {
// 发送文件名
dos.writeUTF(new File(filePath).getName());
// 发送文件大小
long fileSize = new File(filePath).length();
dos.writeLong(fileSize);
// 发送文件内容
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesSent = 0;
while ((bytesRead = fis.read(buffer)) != -1) {
dos.write(buffer, 0, bytesRead);
totalBytesSent += bytesRead;
System.out.printf("已发送: %.2f%%\n",
(totalBytesSent * 100.0) / fileSize);
}
dos.flush();
System.out.println("文件发送完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
优化方案
使用NIO提高性能
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
public class NioFileServer {
public static void main(String[] args) {
int port = 12345;
try (ServerSocketChannel serverChannel = ServerSocketChannel.open();
Selector selector = Selector.open()) {
serverChannel.socket().bind(new InetSocketAddress(port));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO服务器启动,等待连接...");
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (true) {
selector.select();
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isAcceptable()) {
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端已连接: " + clientChannel.getRemoteAddress());
}
if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
try {
// 接收文件名
StringBuilder fileName = new StringBuilder();
while (true) {
buffer.clear();
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) break;
buffer.flip();
while (buffer.hasRemaining()) {
fileName.append((char) buffer.get());
}
if (fileName.toString().endsWith("\n")) {
fileName = new StringBuilder(fileName.substring(0, fileName.length() - 1));
break;
}
}
// 接收文件大小
buffer.clear();
clientChannel.read(buffer);
buffer.flip();
long fileSize = buffer.getLong();
// 接收文件内容
Path outputPath = Paths.get("received_" + fileName.toString());
Files.createFile(outputPath);
long totalBytesRead = 0;
while (totalBytesRead < fileSize) {
buffer.clear();
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) break;
buffer.flip();
Files.write(outputPath, buffer.array(),
StandardOpenOption.APPEND);
totalBytesRead += bytesRead;
System.out.printf("已接收: %.2f%%\n",
(totalBytesRead * 100.0) / fileSize);
}
System.out.println("文件接收完成");
clientChannel.close();
} catch (IOException e) {
System.err.println("客户端断开连接");
key.cancel();
clientChannel.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用缓冲流和分块传输
// 服务器端优化
BufferedInputStream bis = new BufferedInputStream(clientSocket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("received_" + fileName));
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// 更新进度
}
bos.close();
bis.close();
关键注意事项
-
内存管理:
- 避免一次性加载整个文件到内存
- 使用适当大小的缓冲区(通常4KB-8KB)
-
网络优化:
- 考虑使用TCP_NODELAY禁用Nagle算法
- 调整缓冲区大小以适应网络条件
-
错误处理:
- 处理网络中断
- 验证文件完整性(如校验和)
- 实现断点续传功能
-
安全性:
- 验证文件类型和大小
- 实现身份验证
- 考虑使用SSL/TLS加密传输
-
性能监控:
- 显示传输进度
- 记录传输时间和速率
高级功能实现
断点续传
// 客户端发送已传输字节数
long bytesTransferred = new File("partial_file").length();
dos.writeLong(bytesTransferred);
// 服务器端从指定位置继续写入
RandomAccessFile raf = new RandomAccessOutputStream("received_" + fileName, "rw");
raf.seek(bytesTransferred);
多线程传输
// 将文件分成多个块并行传输
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 4; i++) {
final int part = i;
futures.add(executor.submit(() -> {
// 传输文件的一部分
}));
}
for (Future<?> future : futures) {
future.get();
}
方案提供了从基础到高级的Java Socket大文件传输实现,可以根据具体需求选择合适的方案进行优化和扩展。
