亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

關(guān)于Java單個TCP(Socket)連接發(fā)送多個文件的問題

 更新時間:2023年04月11日 10:31:03   作者:CrazyDragon_King  
這篇文章主要介紹了關(guān)于Java單個TCP(Socket)連接發(fā)送多個文件的問題,每次我只能使用一個 Socket 發(fā)送一個文件,沒有辦法做到連續(xù)發(fā)送文件,本文來解決這個問題,需要的朋友可以參考下

使用一個TCP連接發(fā)送多個文件

為什么會有這篇博客? 最近在看一些相關(guān)方面的東西,簡單的使用一下 Socket 進(jìn)行編程是沒有的問題的,但是這樣只是建立了一些基本概念。對于真正的問題,還是無能為力。

當(dāng)我需要進(jìn)行文件的傳輸時,我發(fā)現(xiàn)我好像只是發(fā)送過去了數(shù)據(jù)(二進(jìn)制數(shù)據(jù)),但是關(guān)于文件的一些信息卻丟失了(文件的擴展名)。而且每次我只能使用一個 Socket 發(fā)送一個文件,沒有辦法做到連續(xù)發(fā)送文件(因為我是依靠關(guān)閉流來完成發(fā)送文件的,也就是說我其實是不知道文件的長度,所以只能以一個 Socket 連接代表一個文件)。

這些問題困擾了我好久,我去網(wǎng)上簡單的查找了一下,沒有發(fā)現(xiàn)什么現(xiàn)成的例子(可能沒有找到吧),有人提了一下,可以自己定義協(xié)議進(jìn)行發(fā)送。 這個倒是激發(fā)了我的興趣,感覺像是明白了什么,因為我剛學(xué)過計算機網(wǎng)絡(luò)這門課,老實說我學(xué)得不怎么樣,但是計算機網(wǎng)絡(luò)的概念我是學(xué)習(xí)到了。

計算機網(wǎng)絡(luò)這門課上,提到了很多協(xié)議,不知不覺中我也有了協(xié)議的概念。所以我找到了解決的辦法:自己在 TCP 層上定義一個簡單的協(xié)議。 通過定義協(xié)議,這樣問題就迎刃而解了。

協(xié)議的作用

從主機1到主機2發(fā)送數(shù)據(jù),從應(yīng)用層的角度看,它們只能看到應(yīng)用程序數(shù)據(jù),但是我們通過圖是可以看出來的,數(shù)據(jù)從主機1開始,每向下一層數(shù)據(jù)會加上一個首部,然后在網(wǎng)絡(luò)上進(jìn)行傳播,當(dāng)?shù)竭_(dá)主機2后,每向上一層會去掉一個首部,達(dá)到應(yīng)用層時,就只有數(shù)據(jù)了。(這里只是簡單的說明一下,實際上這樣還是不夠嚴(yán)謹(jǐn),但是對于簡單的理解是夠了。)

在這里插入圖片描述

所以,我可以自己定義一個簡單的協(xié)議,將一些必要的信息放在協(xié)議頭部,然后讓計算機程序自己解析協(xié)議頭部信息,而且每一個協(xié)議報文就相當(dāng)于一個文件。這樣多個協(xié)議就是多個文件了。而且協(xié)議之間是可以區(qū)分的,不然的話,連續(xù)傳輸多個文件,如果無法區(qū)分屬于每個文件的字節(jié)流,那么傳輸是毫無意義的。

定義數(shù)據(jù)的發(fā)送格式(協(xié)議)

這里的發(fā)送格式(我感覺和計算機網(wǎng)絡(luò)中的協(xié)議有點像,也就稱它為一個簡單的協(xié)議吧)。

發(fā)送格式:數(shù)據(jù)頭+數(shù)據(jù)體

數(shù)據(jù)頭:一個長度為一字節(jié)的數(shù)據(jù),表示的內(nèi)容是文件的類型。 注:因為每個文件的類型是不一樣的,而且長度也不相同,我們知道協(xié)議的頭部一般是具有一個固定長度的(對于可變長的那些我們不考慮),所以我采用一個映射關(guān)系,即一個字節(jié)數(shù)字表示一個文件的類型。

舉一個例子,如下:

keyvalue
0txt
1png
2jpg
3jpeg
4avi

注:這里我做的是一個模擬,所以我只要測試幾種就行了。

數(shù)據(jù)體: 文件的數(shù)據(jù)部分(二進(jìn)制數(shù)據(jù))。

代碼

客戶端

協(xié)議頭部類

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;
	}
}

發(fā)送文件類

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;

/**
 * 模擬文件傳輸協(xié)議:
 * 協(xié)議包含一個頭部和一個數(shù)據(jù)部分。
 * 頭部為 9 字節(jié),其余為數(shù)據(jù)部分。
 * 規(guī)定頭部包含:文件的類型、文件數(shù)據(jù)的總長度信息。
 * */
public class FileTransfer {
	private byte[] header = new byte[9];   //協(xié)議的頭部為9字節(jié),第一個字節(jié)為文件類型,后面8個字節(jié)為文件的字節(jié)長度。
	
	/**
	 *@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);
				//將文件數(shù)據(jù)作為數(shù)據(jù)部分寫入流中
				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. 發(fā)送完一個文件后需要強制刷新一下。因為我是使用的緩沖流,我們知道為了提高發(fā)送的效率,并不是一有數(shù)據(jù)就發(fā)送,而是等待緩沖區(qū)滿了以后再發(fā)送,因為 IO 過程是很慢的(相較于 CPU),所以如果不刷新的話,當(dāng)數(shù)據(jù)量特別小的文件時,可能會導(dǎo)致服務(wù)器端接收不到數(shù)據(jù)(這個問題,感興趣的可以去了解一下。),這是一個需要注意的問題。(我測試的例子有一個文本文件只有31字節(jié))。
  2. getLong() 方法將一個 long 型數(shù)據(jù)轉(zhuǎn)為 byte 型數(shù)據(jù),我們知道 long 占8個字節(jié),但是這個方法是我從Java源碼里面抄過來的,有一個類叫做 DataOutputStream,它有一個方法是 writeLong(),它的底層實現(xiàn)就是將 long 轉(zhuǎn)為 byte,所以我直接借鑒過來了。(其實,這個也不是很復(fù)雜,它只是涉及了位運算,但是寫出來這個代碼就是很厲害了,所以我選擇直接使用這段代碼,如果對于位運算感興趣,可以參考一個我的博客:位運算)。

測試類

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");
		}
	}
}

服務(wù)器端

協(xié)議解析類

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;

/**
 * 接受客戶端傳過來的文件數(shù)據(jù),并將其還原為文件。
 * */
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("無法創(chuàng)建輸出路徑");
			}
		}
		
		while (true) {	
			//先讀取文件的頭部信息
			int exit = bis.read(header, 0, header.length);
			
			//當(dāng)最后一個文件發(fā)送完,客戶端會停止,服務(wù)器端讀取完數(shù)據(jù)后,就應(yīng)該關(guān)閉了,
			//否則就會造成死循環(huán),并且會批量產(chǎn)生最后一個文件,但是沒有任何數(shù)據(jù)。
			if (exit == -1) {
				System.out.println("文件上傳結(jié)束!");
				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;
					/**
					 * 當(dāng)文件最后一部分不足1024時,直接讀取此部分,然后結(jié)束。
					 * 文件已經(jīng)讀取完成了。
					 * */
					int last = (int)(len-count);
					if (last < 1024 && last > 0) {
						//這里不考慮網(wǎng)絡(luò)原因造成的無法讀取準(zhǔn)確的字節(jié)數(shù),暫且認(rèn)為網(wǎng)絡(luò)是正常的。
						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. 這個將 byte 轉(zhuǎn)為 long 的方法,相信大家也能猜出來了。DataInputStream 有一個方法叫 readLong(),所以我直接拿來使用了。(我覺得這兩段代碼寫的非常好,不過我就看了幾個類的源碼,哈哈!)
  2. 這里我使用一個死循環(huán)進(jìn)行文件的讀取,但是我在測試的時候,發(fā)現(xiàn)了一個問題很難解決:什么時候結(jié)束循環(huán)。 我一開始使用 client 關(guān)閉作為退出條件,但是發(fā)現(xiàn)無法起作用。后來發(fā)現(xiàn),對于網(wǎng)絡(luò)流來說,如果讀取到 -1 說明對面的輸入流已經(jīng)關(guān)閉了,因此使用這個作為退出循環(huán)的標(biāo)志。如果刪去了這句代碼,程序會無法自動終止,并且會一直產(chǎn)生最后一個讀取的文件,但是由于無法讀取到數(shù)據(jù),所以文件都是 0 字節(jié)的文件。 (這個東西產(chǎn)生文件的速度很快,大概幾秒鐘就會產(chǎn)生幾千個文件,如果感興趣,可以嘗試一下,但是最好快速終止程序的運行,哈哈!
if (exit == -1) {
	System.out.println("文件上傳結(jié)束!");
	break;   
}

測試類

這里只測試一個連接就行了,這只是一個說明的例子。

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");
		}	
	}
}

測試結(jié)果

Client

在這里插入圖片描述

Server

在這里插入圖片描述

源文件目錄 這里面包含了我測試的五種文件。注意對比文件的大小信息,對于IO的測試,我喜歡使用圖片和視頻測試,因為它們是很特殊的文件,如果錯了一點(字節(jié)少了、多了),文件基本上就損壞了,表現(xiàn)為圖片不正常顯示,視頻無法正常播放。

在這里插入圖片描述

目的文件目錄

在這里插入圖片描述

總結(jié)

這個問題應(yīng)該是解決了,我這里經(jīng)過測試,應(yīng)該是沒有問題的了。我的代碼寫的不是太好,有時候都沒有怎么思考,想到哪就寫到哪,這樣看來還是有很大問題。這個例子的代碼很簡單,不過我發(fā)現(xiàn)了一個很有趣的問題,因為我最近看到了一個手寫 Http 服務(wù)器的(使用Java簡單的寫一個。),自己也嘗試了一下(還沒看完)。 我們知道 HTTP 協(xié)議,也是具有響應(yīng)頭和響應(yīng)體,我覺得我這個和 HTTP 協(xié)議有點相似,雖然我的想法很簡陋,但是好像確實是有點相似,可能我看到的東西,對我也有了影響。

到此這篇關(guān)于關(guān)于Java單個TCP(Socket)連接發(fā)送多個文件的問題的文章就介紹到這了,更多相關(guān)單個(Socket)TCP發(fā)送多個文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中的類加載與類卸載方式

    Java中的類加載與類卸載方式

    這篇文章主要介紹了Java中的類加載與類卸載方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • mybatis-plus指定字段模糊查詢的實現(xiàn)方法

    mybatis-plus指定字段模糊查詢的實現(xiàn)方法

    最近項目中使用springboot+mybatis-plus來實現(xiàn),所以下面這篇文章主要給大家介紹了關(guān)于mybatis-plus實現(xiàn)指定字段模糊查詢的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • Java 二分查找算法的實現(xiàn)

    Java 二分查找算法的實現(xiàn)

    這篇文章主要介紹了Java 如何實現(xiàn)二分查找算法,幫助大家更好的理解和學(xué)習(xí)Java 算法,感興趣的朋友可以了解下
    2020-09-09
  • 帶你快速入門掌握Spring的那些注解使用

    帶你快速入門掌握Spring的那些注解使用

    注解是個好東西,注解是Java語法,被Java編譯器檢查,可以減少配置錯誤,這篇文章主要給大家介紹了關(guān)于Spring的那些注解使用的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • Java Predicate及Consumer接口函數(shù)代碼實現(xiàn)解析

    Java Predicate及Consumer接口函數(shù)代碼實現(xiàn)解析

    這篇文章主要介紹了Java Predicate及Consumer接口函數(shù)代碼實現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • java將m3u8格式轉(zhuǎn)成視頻文件的方法

    java將m3u8格式轉(zhuǎn)成視頻文件的方法

    這篇文章主要介紹了如何java將m3u8格式轉(zhuǎn)成視頻文件,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • Java黑科技:replace首個替換一秒搞定

    Java黑科技:replace首個替換一秒搞定

    要實現(xiàn)只替換第一個匹配項,可以使用Java中的String類的replaceFirst方法,該方法接受兩個參數(shù),第一個參數(shù)是要替換的字符串或正則表達(dá)式,第二個參數(shù)是替換后的字符串,需要的朋友可以參考下
    2023-10-10
  • java使用hadoop實現(xiàn)關(guān)聯(lián)商品統(tǒng)計

    java使用hadoop實現(xiàn)關(guān)聯(lián)商品統(tǒng)計

    本篇文章java使用hadoop實現(xiàn)關(guān)聯(lián)商品統(tǒng)計,可以實現(xiàn)商品的關(guān)聯(lián)統(tǒng)計,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2016-10-10
  • java并發(fā)編程專題(一)----線程基礎(chǔ)知識

    java并發(fā)編程專題(一)----線程基礎(chǔ)知識

    這篇文章主要介紹了java并發(fā)編程線程的基礎(chǔ)知識,文中講解非常詳細(xì),幫助大家更好的學(xué)習(xí)JAVA并發(fā)編程,感興趣想學(xué)習(xí)JAVA的可以了解下
    2020-06-06
  • Java+opencv3.2.0實現(xiàn)重映射

    Java+opencv3.2.0實現(xiàn)重映射

    這篇文章主要為大家詳細(xì)介紹了Java+opencv3.2.0實現(xiàn)重映射的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02

最新評論