杰瑞科技汇

java socket 大文件

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();

关键注意事项

  1. 内存管理

    • 避免一次性加载整个文件到内存
    • 使用适当大小的缓冲区(通常4KB-8KB)
  2. 网络优化

    • 考虑使用TCP_NODELAY禁用Nagle算法
    • 调整缓冲区大小以适应网络条件
  3. 错误处理

    • 处理网络中断
    • 验证文件完整性(如校验和)
    • 实现断点续传功能
  4. 安全性

    • 验证文件类型和大小
    • 实现身份验证
    • 考虑使用SSL/TLS加密传输
  5. 性能监控

    • 显示传输进度
    • 记录传输时间和速率

高级功能实现

断点续传

// 客户端发送已传输字节数
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大文件传输实现,可以根据具体需求选择合适的方案进行优化和扩展。

分享:
扫描分享到社交APP
上一篇
下一篇