こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

締切り済みの質問

socketでのバイナリファイルの扱い方

javaのsocketを用いてファイルの送信サーバ、受信クライアントを作成しているのですが
テストプログラムとしてスレッド化せずに送信・受信部のみ作りました。

症状はバイナリデータの転送がうまくできないことです。ソースを抜粋すると
サーバ側で
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());//socketはSocketクラス
BufferedInputStream in1 = new BufferedInputStream( new FileInputStream("test1.bmp"));
BufferedInputStream in2 = new BufferedInputStream( new FileInputStream("test2.bmp"));

while( (c = in1.read() ) != -1 ){
  out.write(c);
}
while( (c = in2.read() ) != -1 ){
  out.write(c);
}

クライアント側で
BufferedOutputStream out1 = new BufferedOutputStream( new FileOutputStream("test1.out.bmp") );
BufferedOutputStream out2 = new BufferedOutputStream( new FileOutputStream("test2.out.bmp") );

BufferedInputStream in = new BufferedInputStream( s.getInputStream() );
while( ( c=in.read() ) != -1 ){
  out1.write(c);
}
while( ( c=in.read() ) != -1 ){
  out2.write(c);
}

とやっています。
クライアント側で1回目のin.readを抜けるときにはサーバ側は既に2回のwriteを終わっているようで
ファイルはtest1.out.bmpにマージされていました。

サーバ側の1回目のwriteのオブジェクトがcloseしていないことが原因だと思い1回目のwriteが終わった時点でout.close()とした
のですがそうやるとsocketも閉じてしまいsocket closeのエラーが生じてしまいました。

このようにならず1本のコネクションでバイナリのデータを複数送信する方法をご存知でしたら教えてください。

[環境]
win2ksp1a,j2sdk1.4.1_03,java経験4ヶ月程度

投稿日時 - 2003-10-07 22:17:50

QNo.673946

暇なときに回答ください

このQ&Aは役に立ちましたか?

4人が「このQ&Aが役に立った」と投票しています

回答(5)

ANo.5

 転送するファイルの先頭に長さ情報を付加し、
受け取り側でその情報を取り出して、ファイルを分ければ一つの接続で複数のファイルが転送できます。
以下がその例です。

-----サーバ側-----

import java.io.*;
import java.net.*;

public class binse {

public static void main(String args[]) {

File f;
BufferedOutputStream out;
BufferedInputStream in;
int i, c;

try {

ServerSocket s = new ServerSocket(12345);
Socket socket = s.accept();
out = new BufferedOutputStream(socket.getOutputStream());

for (i = 0; i < args.length; i ++) {

f = new File(args[i]);
in = new BufferedInputStream(new FileInputStream(f));
out.write(Long.toString(f.length()).getBytes("ISO-8859-1"));
out.write(0);

while((c = in.read()) != -1) { out.write(c); };
in.close();

};

out.close();
socket.close();
s.close();

} catch (Exception e) { System.out.println(e.toString()); };

};

}

-----クライアント側-----

import java.net.*;
import java.io.*;

public class bincl {

public static void main(String args[]) {

BufferedOutputStream out = null;
BufferedInputStream in;
int i, j, c;
byte b[] = new byte[10];
boolean f;

try {

Socket socket = new Socket(InetAddress.getByName("localhost"), 12345);
in = new BufferedInputStream(socket.getInputStream());

i = 0;
j = 0;
f = true;
while((c = in.read()) != -1) {

if (f) {
if (c != 0) b[i ++] = (byte)c;
else {i = Integer.parseInt(new String(b, 0, i, "ISO-8859-1"));
out = new BufferedOutputStream(new FileOutputStream(Integer.toString(j ++)));
if (i == 0) out.close();
else f = false;
};
}
else {
out.write(c);
if ((-- i) <= 0) {
f = true;
out.close();
i = 0;
};
};

};

in.close();
socket.close();

} catch (Exception e) { System.out.println(e.toString()); };

};

}

投稿日時 - 2008-09-21 13:59:55

ANo.4

なるほど。。。
言語仕様とかの話になってしまうとお役に立てませんね。
すみませんです。

>だったらコネクション2回張るのは問題ないのではないですか?
これは2回という意味です。2本ではありません。
コネクションの存在が同時かどうかっていうことですよね?

>「当人同士が面と向かって会話しているのに荷物を手渡すときは郵便局を使っている」といった感じだと思ってしまったので、

まったくそのとおりだと思います。
ただFTPに関するなにかのサイトで呼んだのですが、
ファイル転送について細かな制御を加えるためにFTPではコネクション2本使っているということです。
今回の希望の処理が細かな制御の範囲に入っているので先人(FTPを考えた人たち)の知恵を参考にするのであれば、コネクション2本もしくは2回張るのがいいんでしょうね。

でも、これだと
「なぜ一本では複数ファイルのやり取りができないか」の回答になりませんね。
inputstreamを拡張して、ファイルの終端に来た場合は-2を返すというようなクラスを作っちゃえばできるかもしれませんね。

誰か詳しい人の回答がつくといいですね!

投稿日時 - 2003-10-10 17:55:23

お礼

ありがとうございました。

やはり通常のコーディングでは2本のコネクションor2回のコネクションで処理するのが妥当なようですね。

コードのレベルから考えれば2本のコネクションは当然なのかもしれませんが、やはり現実世界との比較してみるとおかしいなと思いました。

コードレベルでは細やかな処理、ですが実世界では細やかではない気がしますし。

おっしゃるようにInputStreamの拡張、というのがいいかもしれません。検討させていただきます。

「実は簡単にできる」などの回答を期待してもうしばらく締切はしませんがchi-konさんのおかげで問題点もわかってきました。

何度も丁寧なレスありがとうございました。

投稿日時 - 2003-10-11 18:27:02

ANo.3

以下引用
---
ファイルの指定というのは
1個目のファイルを指定してダウンロードした後に2個目のファイルも指定してダウンロード、という手順であるため、
あらかじめどのファイルを送るかを指定することはできないという意味でした。何度も説明不足ですみません。
---

だったらコネクション2回張るのは問題ないのではないですか?
ダウンロードなどの処理時間を考えた場合
コネクション取得にかかる時間なんて対したことないとおもいますが?

上記の仕様だと単一コネクションで複数ファイルのダウンロードの必要性がみられませんが。

投稿日時 - 2003-10-10 09:50:02

補足

ありがとうございます。

>だったらコネクション2回張るのは問題ないのではないですか?
2回張る、ということは一時完全にサーバとクライアント間でコネクション数0になるということでしょうか?それともFTPのように2個張るということでしょうか?
"2回"と"2個"ではかなり変わってしまうと思うので質問を質問で返すようですみませんが確認させてください。

>ダウンロードなどの処理時間を考えた場合
>コネクション取得にかかる時間なんて対したことないとおもいますが?
細かくはわかりませんがおそらく時間はかからないと思います。

>上記の仕様だと単一コネクションで複数ファイルのダウンロードの必要性がみられませんが。
単一コネクションでやらなくては絶対にだめというわけではありません。ただ、今回は単一コネクションでできるかどうかが知りたかったのです。
言語仕様上・コンピュータの仕様上不可なのであれば不可であるという事実を知りたいです。

2個張ればいい、というのは確かにそうなのですが質問にも書いたように1個のコネクションでの方法が知りたくて質問させていただきました。

つまりダウンロードソフトを作りたいというよりは
「1個のコネクションでダウンロードできるソフト」が
作りたいということです。

なぜそんなものが作りたいかというと
同時に2個のコネクションというのは私としては「実装上の理由による仕様」という感じがします。
もしクライアントのreadに終了条件を渡す処理がおこなえるのなら(例えばreadFinish)2個のコネクションを張る必要性は
かなり減ると思います。なのでもし1個のコネクションでデータも受け渡すことができるのならば知りたいと思いました。

実世界で言えば「当人同士が面と向かって会話しているのに荷物を手渡すときは郵便局を使っている」といった感じだと思ってしまったので、
「当人同士が面と向かって話をしているのならそのまま手渡せたほうがいい」というニュアンスです。

わかりずらい部分も多いかと思いますがアドバイスお願いいたします。不明な点に対する質問もお願いいたします。

投稿日時 - 2003-10-10 12:23:08

ANo.2

#01のものです。
方法1ですが、
クライアントでファイルを指定したとしても
サーバ側で送信する前に送信対象のFileクラスのインスタンスを作成してやればいいのではないでしょうか。
BufferedInputStream in1 = new BufferedInputStream( new FileInputStream("test1.bmp"));
ここを
File file = new File("test1.bmp");
BufferedInputStream in1 = new BufferedInputStream( new FileInputStream(file));
としてやる。

方法2については
まったくそのとおりですね。
汎用的なコードにはならなそうですね。


そういえばFTPとか参考にされましたか?
あんまり詳しくみていないのですが、
FTPではファイル通信用のコネクションと制御用コネクションの2つをつかってファイル転送しているようです。
しかも確かファイルを送信するごとにファイル通信用コネクションは新しく接続していたように思えます。(Passiveのときだけ??)

投稿日時 - 2003-10-09 08:55:09

補足

ありがとうございます。

ファイルの指定というのは
1個目のファイルを指定してダウンロードした後に2個目のファイルも指定してダウンロード、という手順であるため、
あらかじめどのファイルを送るかを指定することはできないという意味でした。何度も説明不足ですみません。

FTPは2つのコネクションを使っているため通信用のソケットをcloseしてもコネクションが途絶えずに済むようです。
私も詳しくは調べられていないのでもう少し調べてみます。
今回の私の問題も2本のコネクションで回避できるのですが、1本でできないかどうか考えています。

クライアントのreadメソッドの終了条件がサーバ側のデータ用ソケットclose以外で適切な方法が見つかればいいのだと思うのですが
それを見つけることができません。

投稿日時 - 2003-10-09 21:19:46

ANo.1

面白そうな問題なのでちょっと考えてみました。

Cliant側は入力ストリームに何個のファイル分がきているかを知ることができません。
もし一つ目のファイルの終了がわかれば書き込み先を変えればファイルは二つになると思います。

ということで!
方法1
通信の最初に今から送るファイルの詳細をクライアントに送る。(何バイトのファイルと何バイトのファイルを送ります)
クライアントは指定されたバイト数読み込んだらファイルの書き込み先を変更する。

方法2
サーバはファイルの終了を表すコードを送り、クライアントはそれを検知してファイルの出力先を変更する。

これでどうでしょうか?
もしかしたら、もっと簡単にできるのかもしれませんね!
ぜひ参考にしたいです。

うーん、気になるなぁ。。。

そういえばHTTPも最初にいまから送信するファイルのデータ量をヘッダに書き込めたような気がするけど
こういうときのために使うのかなぁ
なんておもってみたりなんかして。。。

投稿日時 - 2003-10-08 00:57:36

補足

ご回答ありがとうございます。

方法1についてですが
通信の最初、というのは1個目のバイナリデータ送信の前、ということですよね。
私が抜粋したソースですとそれでOKなのですが、実際にはクライアントが指定したファイルのダウンロードをしたいので、
方法1では実現が難しいです。説明不足ですみません。参考になりました。

方法2についてですが
終了コードを何に決めるのか、によるのかもしれませんが私は「適当な文字列」しか思い浮かびませんでした。
この場合は「適当な文字列」と一致する文字を受信した場合は受信文字列を一時的に変数に保持しておいて次の文字列も
「適当な文字列」の一部であるか判定し・・・といった手順でよろしいのでしょうか。
ただ、終了コードと同一コードがバイナリに含まれていることを考えると若干不安があるのでご意見をお聞かせいただきたいです。

その他不明な点がございましたらご指摘ください。

投稿日時 - 2003-10-09 08:17:03

あなたにオススメの質問