文字コードや改行コードを考慮したファイル転送

以前このエントリこのエントリでファイル転送プログラムを作ってみたが、クライアント側とサーバ側で文字コードや改行コードが違う場合について考慮されていないので、それらを考慮したファイル転送プログラムを作る。

—–



以前の転送方式では全てバイナリでデータを送っていたことになる為、クライアントがWindowsでサーバがUNIXであったりすると、テキストファイルの改行コードの違いがあるので、サーバ上でテキストファイルが正しく扱えない可能性がある。その問題をクリアするため、クライアント側はファイルをBufferedReaderで一行ずつ読み込み、PrintWriterでサーバに転送する。サーバ側はソケットのInputStreamから一行ずつ読み込み、ファイルに書き込むことにする。
さらにだが、プロトコルの組み方によってはファイルを転送し終わった後になにかしらの転送を再び行わなければならない場合もあると思う。例えばファイルを複数転送する場合に、1ファイルにつき1回ソケット接続を確立していたのではリソースが勿体無いと思うので、一回の接続で全ての処理をすませたいと思うだろう。これを実現する為に、ファイル転送が終わったらEOT=”\u0004″を送ることでサーバ側に受信終了をリクエストする。もしこういった受信終了を指示する何かを送らないと、サーバ側はいつまで経ってもソケットのInputStreamからデータ読み込もうとし続けてロック状態になってしまう。

SocketServer.java

import java.io.*;
import java.net.*;
public class SocketServer {
private static ServerSocket ss;
private static Socket s;
private static BufferedReader br;
private static PrintWriter pw;
private static InputStream is;
public static void main(String[] args) {
try {
ss = new ServerSocket(15000);
s = ss.accept();
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream("C:\\Temp\\efg.txt"), "EUC_JP"));
pw = new PrintWriter(new FileOutputStream("C:\\Temp\\efg.txt"));
is = s.getInputStream();
String line;
while ((line = br.readLine()) != null) {
if (line.equals("\u0004")) {
break;
}
pw.println(line);
}
byte[] b = new byte[1024];
is.read(b, 0, 1024);
String str;
str = new String(b);
System.out.println("Received -> " + str.substring(0, str.indexOf("\u0004")));
}
catch (IOException ioe) {
ioe.printStackTrace();
}
finally {
try {
ss.close();
s.close();
br.close();
pw.close();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}

ClientSocket.jaba

import java.io.*;
import java.net.*;
public class SocketClient {
private static Socket s;
private static BufferedReader br;
private static PrintWriter pw;
private static OutputStream os;
public static void main(String[] args) {
try {
s = new Socket("127.0.0.1", 15000);
br = new BufferedReader(new FileReader("C:\\Temp\\abc.txt"));
os = s.getOutputStream();
pw = new PrintWriter(s.getOutputStream(), true);
String line;
while ((line = br.readLine()) != null) {
pw.println(line);
}
pw.println("\u0004");
StringBuffer sb = new StringBuffer();
sb.append("Sending message");
sb.append("\u0004");
os.write(sb.toString().getBytes());
os.close();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
finally {
try {
s.close();
br.close();
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}

サーバ→クライアントの順に起動すれば、abc.txtの内容がefg.txtとして転送されることが確認できると思う。当然のことながら、この転送方式はテキストファイルにしか使用できないことは注意してもらいたい。
さて、ここでクライアント側とサーバ側で文字コードが違うとする。Javaはプログラム内部では文字コードをUnicodeとして扱い、何も指定しなければ文字コードを出力するときはそのサーバの文字コードに合わせた出力を自動的に行ってくれる。よって上記のプログラムでは、クライアントがSJIS、サーバがEUCといった状態で実行すると、efg.txtはEUC方式に従って保存されるということになる。
もし「サーバ側に保存するときにクライアント側と同じ文字コードで保存したい」というようなことがあれば、SocketServer.javaでコメントアウトされているPrintWriterのコンストラクタのような書き方を指定すれば、出来上がるファイルの文字コードを指定できる。後はこのコンストラクタが実行される前に、クライアント側の文字コードをサーバ側に転送してやるといいだろう。