⇧ 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環境だと、
⇧ /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 CodeでJavaの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を導入します。
適当な場所に展開(解凍)。
■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ファイルをコンパイルして実行できるようにしておくのだけど、
⇧ CUIだと、ソースフォルダを追加するだけということができないっぽい...
さらに、Java関連のコマンドにパスを通していないと、-sourcepathのパスなど絶対パス指定しないと機能しないっぽい。
サーバーサイド側のJavaのプログラムを実行。
クライアント側のJavaのプログラムを実行。
サーバーサイド側にファイルが転送されました。
結局のところ、FileChannelとか使えなかったけど...
それにしても、ただ、サーバー間でファイルを転送したいだけなのに、Javaの標準APIの実装例の情報が少ないのが不思議ですな...
フレームワークとかを使えってことなんかね?
毎度モヤモヤ感が半端ない...
今回はこのへんで。