※当サイトの記事には、広告・プロモーションが含まれます。

Javaの標準APIでサーバー間のファイル転送を実現してみる

nazology.net

⇧ amazing...

Javaの標準APIでサーバー間のファイル転送を実現してみる

何やら、思った以上にネットの情報が無い感じだったので、ChatGPTに聞いてみた。

ちなみに、WSL 2(Windows Subsystem for Linux 2)のLinux環境と、Windowsとのやり取りなので、疑似的なサーバー間のファイルを転送になりますが。

Javaの標準APIで、サーバー間のファイル転送を実現するとなってくると、

  • クライアント側
    • File.copy
    • FileChannel
    • SocketChannel
    • Socket
  • サーバーサイド側
    • ServerSocket
    • SocketChannel
    • Socket

のあたりが候補として上がってくる感じらしい。

ファイルのデータがバイナリとかも考慮するんであれば、File.copyは対応してないっぽいので、FileChannelとかを使っていく感じになるんかね。

で、Linux環境だと、

gigazine.net

⇧ /varディレクトリ配下にディレクトリを作成して、転送されてきたファイルを配置する感じになるんかね?

転送先の配置ディレクトリを作成。

Dockerfile を修正してコンテナを作り直します。

ホストのディレクトリをマウントしておかないと、Dockerのコンテナ内のJavaの処理でエラーになるので、マウントしてDockerのコンテナを生成・起動。

docker run -v /home/project-java:/home/ts0818/project-java -v /var/data/file:/var/data/file --name java-container -it java-programing:v1 /bin/bash    

Dockerコンテナ、Visual Studio CodeJavaのDockerコンテナに接続し、[プロジェクトのルートディレクトリ]/src/main/javaを、ソースフォルダに追加。

■/home/project-java/src/main/java/ftp/DirectoryFileReceiverServer.java

package ftp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.*;

public class DirectoryFileReceiverServer {
    private static final String RECEIVE_DIR = "/var/data/file";
    private static final int SERVER_PORT = 12345;

    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
            System.out.println("Server started. Waiting for connections...");

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("Client connected from " + socket.getInetAddress());
                receiveFiles(socket, RECEIVE_DIR);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void receiveFiles(Socket socket, String destinationDirectory) {
        try (DataInputStream dis = new DataInputStream(socket.getInputStream())) {
            // サーバーサイドに配置するディレクトリを受信
            String serverDirectory = dis.readUTF();
            System.out.println("Received directory: " + serverDirectory);

            while (true) {
                if (!receiveFile(dis, destinationDirectory)) {
                    break; // End of transmission
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static boolean receiveFile(DataInputStream dis, String destinationDirectory) {
        try {
            // Receive file name
            String fileName = dis.readUTF();
            if (fileName.equals("END_OF_TRANSMISSION")) {
                System.out.println("End of transmission.");
                return false;
            }

            // Receive file content
            Path filePath = Paths.get(destinationDirectory, fileName);
            try (OutputStream fos = Files.newOutputStream(filePath)) {
                int bytesRead;
                while ((bytesRead = dis.readInt()) != -1) {
                    byte[] buffer = new byte[bytesRead];
                    dis.readFully(buffer);
                    fos.write(buffer, 0, bytesRead);
                }
            }

            System.out.println("Received file: " + fileName);

        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }
}

続いて、クライアント側。

Windows側をクライアントのサーバーとして利用する感じにしたいので、WindowsにOpenJDK 21を導入します。

jdk.java.net

適当な場所に展開(解凍)。

■C:\Users\Toshinobu\Desktop\soft_work\java_work\ftp\src\main\java\ftp\DirectoryFileSenderClient.java

package ftp;
import java.io.*;
import java.net.Socket;
import java.nio.file.*;
import java.util.Objects;

public class DirectoryFileSenderClient {
    private static final String SEND_HOST = "172.24.91.141";
    private static final int CLIENT_PORT = 12345;
    private static final int BUFFER_SIZE = 8192;
    private static final String SOURCE_DIRECTORY = "C:\\Users\\Toshinobu\\Desktop\\soft_work\\java_work\\ftp\\file";
    private static final String SERVER_DIRECTORY = "/var/data/file";

    public static void main(String[] args) {
        try (Socket socket = new Socket(SEND_HOST, CLIENT_PORT)) {
            sendFiles(socket, SOURCE_DIRECTORY, SERVER_DIRECTORY);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void sendFiles(Socket socket, String sourceDirectory, String serverDirectory) {
        try (DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()))) {
            // サーバーサイドに配置するディレクトリを送信
            dos.writeUTF(serverDirectory);

            // ディレクトリ内のファイルを送信
            Files.walk(Paths.get(sourceDirectory))
                    .filter(Files::isRegularFile)
                    .forEach(file -> sendFile(dos, file));

            // 送信完了を通知
            dos.writeUTF("END_OF_TRANSMISSION");

            System.out.println("All files sent.");

        } catch (IOException e) {
            System.out.println("[ERROR]sendFiles");
            e.printStackTrace();
        }
    }

    private static void sendFile(DataOutputStream dos, Path filePath) {
        try (InputStream fis = Files.newInputStream(filePath)) {
            // Send file name
            dos.writeUTF(filePath.getFileName().toString());

            // Send file content
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                dos.writeInt(bytesRead); // Send the length of the upcoming data
                dos.write(buffer, 0, bytesRead);
            }

            // Mark the end of the file
            dos.writeInt(-1);

            System.out.println("[send fileName]" + filePath.getFileName());

        } catch (IOException e) {
            System.out.println("[ERROR]sendFile");
            e.printStackTrace();
        }
    }
}

そしたらば、サーバサイド側に転送したいファイルをWindowsの指定のディレクトリに配置。

クライアント側のJavaファイルをコンパイルして実行できるようにしておくのだけど、

www.baeldung.com

CUIだと、ソースフォルダを追加するだけということができないっぽい...

さらに、Java関連のコマンドにパスを通していないと、-sourcepathのパスなど絶対パス指定しないと機能しないっぽい。

サーバサイド側のJavaファイルをコンパイル

サーバーサイド側のJavaのプログラムを実行。

クライアント側のJavaのプログラムを実行。

サーバーサイド側にファイルが転送されました。

結局のところ、FileChannelとか使えなかったけど...

それにしても、ただ、サーバー間でファイルを転送したいだけなのに、Javaの標準APIの実装例の情報が少ないのが不思議ですな...

フレームワークとかを使えってことなんかね?

毎度モヤモヤ感が半端ない...

今回はこのへんで。