目次
1 つの TCP 接続を使用して複数のファイルを送信する
コード
クライアント
サーバー側
テスト結果
ホームページ Java &#&チュートリアル Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

Apr 27, 2023 am 08:49 AM
java tcp

    1 つの TCP 接続を使用して複数のファイルを送信する

    なぜこのブログがあるのですか? 最近、関連する書籍を読んでいるのですが、単に Socket を使ってプログラミングするのは問題ありませんが、これはいくつかの基本的な概念を確立するだけです。本当の問題に対してはまだ何もできません。

    ファイルを転送する必要がある場合、データ (バイナリ データ) を送信しただけのように見えますが、ファイルに関する一部の情報 (ファイル拡張子) が失われていることがわかります。また、毎回 1 つのファイルを送信するには 1 つのソケットしか使用できず、連続してファイルを送信する方法はありません (ファイルの送信を完了するにはストリームを閉じることに依存しているため、実際にはファイルの長さがわかりません)したがって、1 つのソケット接続がファイルを表すため、ファイルのみを送信できます。

    これらの問題は長い間私を悩ませてきました。インターネットで簡単に検索しましたが、既成の例は見つかりませんでした (私が見つけられなかったのかもしれません)。 自分でプロトコルを定義して送信できると述べました。 コンピュータネットワークという科目を受講したばかりだったので、とても興味が湧き、なんとなくわかったような気がしました。正直、あまり勉強はできませんでしたが、コンピュータネットワークの概念については学びました。 . .

    コンピュータネットワークの授業ではプロトコルがたくさん出てきますが、私も知らず知らずのうちにプロトコルという概念を持っていました。そこで私は解決策を見つけました。

    自分で TCP 層に単純なプロトコルを定義します。 プロトコルを定義することで、問題は解決されます。

    プロトコルの役割

    ホスト 1 からホスト 2 にデータを送信します。アプリケーション層の観点からは、アプリケーション データしか見ることができませんが、図を通してそれを見ることができます。データはホスト1から始まり、下位層ごとにヘッダが付加されてネットワーク上に広がり、ホスト2に到達すると上位層ごとにヘッダが削除され、アプリケーション層に到達すると、データしかありません。 (これは単なる簡単な説明です。実際、これは十分厳密ではありませんが、簡単に理解するには十分です。)

    Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

    したがって、次のように定義できます。単純なプロトコルでは、プロトコル ヘッダーに必要な情報がいくつか配置され、コンピューター プログラムが独自にプロトコル ヘッダー情報を解析します。各プロトコル メッセージはファイルに相当します。このように、複数のプロトコルは複数のファイルになります。また、プロトコルの区別もできますが、複数のファイルを連続して送信する場合、各ファイルに属するバイトストリームが区別できなければ送信の意味がありません。

    データ送信形式 (プロトコル) を定義します

    ここでの送信形式 (コンピュータ ネットワークのプロトコルに少し似ていると思います。単純なプロトコルと呼びます) 。

    送信形式:データヘッダ データ本体

    データヘッダ:ファイルの種類を示す1バイト長のデータ。注: 各ファイルのタイプが異なり、長さも異なるため、プロトコルのヘッダーは一般に固定長であることがわかっています (可変長のものは考慮しません)。そのため、マッピング関係を使用します。つまり、バイト番号はファイルのタイプを表します。

    次のような例を示します。

    keyvalue0txt1png2 jpg3jpeg4avi

    注: ここで行っているのはシミュレーションなので、テストする必要があるのは数種類だけです。

    データ本体: ファイルのデータ部分(バイナリデータ)。

    コード

    クライアント

    プロトコル ヘッダー クラス

    package com.dragon;
    
    public class Header {
    	private byte type;      //文件类型
    	private long length;      //文件长度
    	
    	public Header(byte type, long length) {
    		super();
    		this.type = type;
    		this.length = length;
    	}
    
    	public byte getType() {
    		return this.type;
    	}
    	
    	public long getLength() {
    		return this.length;
    	}
    }
    ログイン後にコピー

    送信ファイル クラス

    package com.dragon;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.net.Socket;
    
    /**
     * 模拟文件传输协议:
     * 协议包含一个头部和一个数据部分。
     * 头部为 9 字节,其余为数据部分。
     * 规定头部包含:文件的类型、文件数据的总长度信息。
     * */
    public class FileTransfer {
    	private byte[] header = new byte[9];   //协议的头部为9字节,第一个字节为文件类型,后面8个字节为文件的字节长度。
    	
    	/**
    	 *@param src source folder 
    	 * @throws IOException 
    	 * @throws FileNotFoundException 
    	 * */
    	public void transfer(Socket client, String src) throws FileNotFoundException, IOException {
    		File srcFile = new File(src);
    		File[] files = srcFile.listFiles(f->f.isFile());
    		//获取输出流
    		BufferedOutputStream bos = new BufferedOutputStream(client.getOutputStream());
    		for (File file : files) {
    			try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){
    				 //将文件写入流中
    				String filename = file.getName();
    				System.out.println(filename);
    				//获取文件的扩展名
    				String type = filename.substring(filename.lastIndexOf(".")+1);
    				long len = file.length();
    				//使用一个对象来保存文件的类型和长度信息,操作方便。
    				Header h = new Header(this.getType(type), len);
    				header = this.getHeader(h);
    				
    				//将文件基本信息作为头部写入流中
    				bos.write(header, 0, header.length);
    				//将文件数据作为数据部分写入流中
    				int hasRead = 0;
    				byte[] b = new byte[1024];
    				while ((hasRead = bis.read(b)) != -1) {
    					bos.write(b, 0, hasRead);
    				}
    				bos.flush();   //强制刷新,否则会出错!
    			}
    		}
    	}
    	
    	private byte[] getHeader(Header h) {
    		byte[] header = new byte[9];
    		byte t = h.getType();  
    		long v = h.getLength();
    		header[0] = t;                  //版本号
    		header[1] = (byte)(v >>> 56);   //长度
    		header[2] = (byte)(v >>> 48);
    		header[3] = (byte)(v >>> 40);
    		header[4] = (byte)(v >>> 32);
    		header[5] = (byte)(v >>> 24);
    		header[6] = (byte)(v >>> 16);
    		header[7] = (byte)(v >>>  8);
    		header[8] = (byte)(v >>>  0);
    		return header;
    	}
    	
    	/**
    	 * 使用 0-127 作为类型的代号
    	 * */
    	private byte getType(String type) {
    		byte t = 0;
    		switch (type.toLowerCase()) {
    		case "txt": t = 0; break;
    		case "png": t=1; break;
    		case "jpg": t=2; break;
    		case "jpeg": t=3; break;
    		case "avi": t=4; break;
    		}
    		return t;
    	}
    }
    ログイン後にコピー

    注:

    1. ファイルを送信した後、ファイルを強制的に更新する必要があります。バッファ ストリームを使用しているため、送信効率を向上させるために、データがあるとすぐにデータを送信するのではなく、バッファがいっぱいになるまで待ってから送信することがわかります。IO プロセスが非常に遅いため ( CPU に比べて)、更新しない場合、データ量が非常に少ない場合、サーバーはデータを受信できない可能性があります (興味がある場合は、この問題について詳しく学ぶことができます。 )、これは注意が必要な問題です。 (私がテストした例には、わずか 31 バイトのテキスト ファイルがありました)。

    2. getLong() メソッドは、long 型データを byte 型データに変換します。long が 8 バイトを占めることはわかっていますが、このメソッドは Java ソース コードからのものです。そこからコピーすると、DataOutputStream というクラスがあり、そのメソッドの 1 つである writeLong() の基礎となる実装は、long を byte に変換することなので、それを直接借用しました。 (実際には、これはそれほど複雑ではありません。必要なのはビット演算だけですが、このコードを記述することは非常に強力なので、このコードを直接使用することにしました。ビット演算に興味がある場合は、私のブログの 1 つを参照してください: Bit操作操作)。

    テスト クラス

    package com.dragon;
    
    import java.io.IOException;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    //类型使用代号:固定长度
    //文件长度:long->byte 固定长度
    public class Test {
    	public static void main(String[] args) throws UnknownHostException, IOException {
    		FileTransfer fileTransfer = new FileTransfer();
    		try (Socket client = new Socket("127.0.0.1", 8000)) {
    			fileTransfer.transfer(client, "D:/DBC/src");
    		}
    	}
    }
    ログイン後にコピー
    サーバー側

    プロトコル解析クラス

    package com.dragon;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.Socket;
    import java.util.UUID;
    
    /**
     * 接受客户端传过来的文件数据,并将其还原为文件。
     * */
    public class FileResolve {
    	private byte[] header = new byte[9];  
    	
    	/**
    	 * @param des 输出文件的目录
    	 * */
    	public void fileResolve(Socket client, String des) throws IOException {
    		BufferedInputStream bis = new BufferedInputStream(client.getInputStream());
    		File desFile = new File(des);
    		if (!desFile.exists()) {
    			if (!desFile.mkdirs()) {
    				throw new FileNotFoundException("无法创建输出路径");
    			}
    		}
    		
    		while (true) {	
    			//先读取文件的头部信息
    			int exit = bis.read(header, 0, header.length);
    			
    			//当最后一个文件发送完,客户端会停止,服务器端读取完数据后,就应该关闭了,
    			//否则就会造成死循环,并且会批量产生最后一个文件,但是没有任何数据。
    			if (exit == -1) {
    				System.out.println("文件上传结束!");
    				break;   
    			}
    			
    			String type = this.getType(header[0]);
    			String filename  = UUID.randomUUID().toString()+"."+type;
    			System.out.println(filename);
    			//获取文件的长度
    			long len = this.getLength(header);
    			long count = 0L;
    			try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(des, filename)))){
    				int hasRead = 0;
    				byte[] b = new byte[1024];
    				while (count < len && (hasRead = bis.read(b)) != -1) {
    					bos.write(b, 0, hasRead);
    					count += (long)hasRead;
    					/**
    					 * 当文件最后一部分不足1024时,直接读取此部分,然后结束。
    					 * 文件已经读取完成了。
    					 * */
    					int last = (int)(len-count);
    					if (last < 1024 && last > 0) {
    						//这里不考虑网络原因造成的无法读取准确的字节数,暂且认为网络是正常的。
    						byte[] lastData = new byte[last];
    						bis.read(lastData);
    						bos.write(lastData, 0, last);
    						count += (long)last;
    					}
    				}
    			}
    		}
    	}
    	
    	/**
    	 * 使用 0-127 作为类型的代号
    	 * */
    	private String getType(int type) {
    		String t = "";
    		switch (type) {
    		case 0: t = "txt"; break;
    		case 1: t = "png"; break;
    		case 2: t = "jpg"; break;
    		case 3: t = "jpeg"; break;
    		case 4: t = "avi"; break;
    		}
    		return t;
    	}
    	
    	private long getLength(byte[] h) {
    		return (((long)h[1] << 56) +
                    ((long)(h[2] & 255) << 48) +
                    ((long)(h[3] & 255) << 40) +
                    ((long)(h[4] & 255) << 32) +
                    ((long)(h[5] & 255) << 24) +
                    ((h[6] & 255) << 16) +
                    ((h[7] & 255) <<  8) +
                    ((h[8] & 255) <<  0));
    	}
    }
    ログイン後にコピー

    注:

    1. バイトをロングに変換するこの方法は誰もが推測できると思います。 DataInputStream には readLong() というメソッドがあるので、これを直接使用しました。 (これら 2 つのコードは非常によく書かれていると思いますが、私はいくつかのクラスのソース コードを見ただけです、笑!)

    2. ここでは、無限ループを使用して、しかし、テスト中に、解決が難しい問題を発見しました: いつループを終了するか。 最初は終了条件としてクライアントのシャットダウンを使用していましたが、それが機能しないことがわかりました。その後、ネットワーク ストリームの場合、-1 が読み取られると、反対側の入力ストリームが閉じられたことを意味することが判明したため、これはループを終了するための標識として使用されます。このコードを削除してもプログラムは自動終了せず、常に最後に読み込んだファイルが生成されますが、データを読み込むことができないため、ファイルはすべて0バイトのファイルになります。 (これは非常に高速にファイルを生成します。約数秒で数千のファイルが生成されます。興味がある場合は試してみてください。しかし、プログラムをすぐに終了するのが最善です、笑! )

    if (exit == -1) {
    	System.out.println("文件上传结束!");
    	break;   
    }
    ログイン後にコピー

    テスト クラス

    ここで 1 つの接続をテストするだけです。これは単なる例です。

    package com.dragon;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class Test {
    	public static void main(String[] args) throws IOException {
    		try (ServerSocket server = new ServerSocket(8000)){
    			Socket client = server.accept();
    			FileResolve fileResolve = new FileResolve();
    			fileResolve.fileResolve(client, "D:/DBC/des");
    		}	
    	}
    }
    ログイン後にコピー

    テスト結果

    クライアント

    Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

    ##サーバー

    Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

    ソース ファイル ディレクトリ これには、テストした 5 つのファイルが含まれています。ファイルのサイズ情報の比較に注意してください。IO テストでは、非常に特殊なファイルであるため、画像とビデオのテストを使用するのが好きです。わずかなエラー (バイト数の多寡) があると、基本的にファイルは破損します。 、およびパフォーマンス 画像が正しく表示されず、ビデオが正常に再生できません。

    Java で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?

    #宛先ファイル ディレクトリ

    以上がJava で単一の TCP 接続を使用して複数のファイルを送信するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

    ホットAIツール

    Undresser.AI Undress

    Undresser.AI Undress

    リアルなヌード写真を作成する AI 搭載アプリ

    AI Clothes Remover

    AI Clothes Remover

    写真から衣服を削除するオンライン AI ツール。

    Undress AI Tool

    Undress AI Tool

    脱衣画像を無料で

    Clothoff.io

    Clothoff.io

    AI衣類リムーバー

    AI Hentai Generator

    AI Hentai Generator

    AIヘンタイを無料で生成します。

    ホットツール

    メモ帳++7.3.1

    メモ帳++7.3.1

    使いやすく無料のコードエディター

    SublimeText3 中国語版

    SublimeText3 中国語版

    中国語版、とても使いやすい

    ゼンドスタジオ 13.0.1

    ゼンドスタジオ 13.0.1

    強力な PHP 統合開発環境

    ドリームウィーバー CS6

    ドリームウィーバー CS6

    ビジュアル Web 開発ツール

    SublimeText3 Mac版

    SublimeText3 Mac版

    神レベルのコード編集ソフト(SublimeText3)

    Javaの平方根 Javaの平方根 Aug 30, 2024 pm 04:26 PM

    Java の平方根のガイド。ここでは、Java で平方根がどのように機能するかを、例とそのコード実装をそれぞれ示して説明します。

    Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

    Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

    Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

    Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

    Javaのアームストロング数 Javaのアームストロング数 Aug 30, 2024 pm 04:26 PM

    Java のアームストロング番号に関するガイド。ここでは、Java でのアームストロング数の概要とコードの一部について説明します。

    ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

    Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

    Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

    Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

    Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

    この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

    Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

    Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

    See all articles