欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

當(dāng)我需要進(jìn)行文件的傳輸時(shí),我發(fā)現(xiàn)我好像只是發(fā)送過(guò)去了數(shù)據(jù)(二進(jìn)制數(shù)據(jù)),但是關(guān)于文件的一些信息卻丟失了(文件的擴(kuò)展名)。而且每次我只能使用一個(gè) Socket 發(fā)送一個(gè)文件,沒(méi)有辦法做到連續(xù)發(fā)送文件(因?yàn)槲沂且揽筷P(guān)閉流來(lái)完成發(fā)送文件的,也就是說(shuō)我其實(shí)是不知道文件的長(zhǎng)度,所以只能以一個(gè) Socket 連接代表一個(gè)文件)。

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

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

協(xié)議的作用

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

在這里插入圖片描述

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

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

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

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

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

舉一個(gè)例子,如下:

keyvalue
0txt
1png
2jpg
3jpeg
4avi

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

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

代碼

客戶端

協(xié)議頭部類

package com.dragon;

public class Header {
	private byte type;      //文件類型
	private long length;      //文件長(zhǎng)度
	
	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é)議包含一個(gè)頭部和一個(gè)數(shù)據(jù)部分。
 * 頭部為 9 字節(jié),其余為數(shù)據(jù)部分。
 * 規(guī)定頭部包含:文件的類型、文件數(shù)據(jù)的總長(zhǎng)度信息。
 * */
public class FileTransfer {
	private byte[] header = new byte[9];   //協(xié)議的頭部為9字節(jié),第一個(gè)字節(jié)為文件類型,后面8個(gè)字節(jié)為文件的字節(jié)長(zhǎng)度。
	
	/**
	 *@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);
				//獲取文件的擴(kuò)展名
				String type = filename.substring(filename.lastIndexOf(".")+1);
				long len = file.length();
				//使用一個(gè)對(duì)象來(lái)保存文件的類型和長(zhǎng)度信息,操作方便。
				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();   //強(qiáng)制刷新,否則會(huì)出錯(cuò)!
			}
		}
	}
	
	private byte[] getHeader(Header h) {
		byte[] header = new byte[9];
		byte t = h.getType();  
		long v = h.getLength();
		header[0] = t;                  //版本號(hào)
		header[1] = (byte)(v >>> 56);   //長(zhǎng)度
		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 作為類型的代號(hào)
	 * */
	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ā)送完一個(gè)文件后需要強(qiáng)制刷新一下。因?yàn)槲沂鞘褂玫木彌_流,我們知道為了提高發(fā)送的效率,并不是一有數(shù)據(jù)就發(fā)送,而是等待緩沖區(qū)滿了以后再發(fā)送,因?yàn)?IO 過(guò)程是很慢的(相較于 CPU),所以如果不刷新的話,當(dāng)數(shù)據(jù)量特別小的文件時(shí),可能會(huì)導(dǎo)致服務(wù)器端接收不到數(shù)據(jù)(這個(gè)問(wèn)題,感興趣的可以去了解一下。),這是一個(gè)需要注意的問(wèn)題。(我測(cè)試的例子有一個(gè)文本文件只有31字節(jié))。
  2. getLong() 方法將一個(gè) long 型數(shù)據(jù)轉(zhuǎn)為 byte 型數(shù)據(jù),我們知道 long 占8個(gè)字節(jié),但是這個(gè)方法是我從Java源碼里面抄過(guò)來(lái)的,有一個(gè)類叫做 DataOutputStream,它有一個(gè)方法是 writeLong(),它的底層實(shí)現(xiàn)就是將 long 轉(zhuǎn)為 byte,所以我直接借鑒過(guò)來(lái)了。(其實(shí),這個(gè)也不是很復(fù)雜,它只是涉及了位運(yùn)算,但是寫出來(lái)這個(gè)代碼就是很厲害了,所以我選擇直接使用這段代碼,如果對(duì)于位運(yùn)算感興趣,可以參考一個(gè)我的博客:位運(yùn)算)。

測(cè)試類

package com.dragon;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

//類型使用代號(hào):固定長(zhǎng)度
//文件長(zhǎng)度:long->byte 固定長(zhǎng)度
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;

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

測(cè)試類

這里只測(cè)試一個(gè)連接就行了,這只是一個(gè)說(shuō)明的例子。

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

測(cè)試結(jié)果

Client

在這里插入圖片描述

Server

在這里插入圖片描述

源文件目錄 這里面包含了我測(cè)試的五種文件。注意對(duì)比文件的大小信息,對(duì)于IO的測(cè)試,我喜歡使用圖片和視頻測(cè)試,因?yàn)樗鼈兪呛芴厥獾奈募?,如果錯(cuò)了一點(diǎn)(字節(jié)少了、多了),文件基本上就損壞了,表現(xiàn)為圖片不正常顯示,視頻無(wú)法正常播放。

在這里插入圖片描述

目的文件目錄

在這里插入圖片描述

總結(jié)

這個(gè)問(wèn)題應(yīng)該是解決了,我這里經(jīng)過(guò)測(cè)試,應(yīng)該是沒(méi)有問(wèn)題的了。我的代碼寫的不是太好,有時(shí)候都沒(méi)有怎么思考,想到哪就寫到哪,這樣看來(lái)還是有很大問(wèn)題。這個(gè)例子的代碼很簡(jiǎn)單,不過(guò)我發(fā)現(xiàn)了一個(gè)很有趣的問(wèn)題,因?yàn)槲易罱吹搅艘粋€(gè)手寫 Http 服務(wù)器的(使用Java簡(jiǎn)單的寫一個(gè)。),自己也嘗試了一下(還沒(méi)看完)。 我們知道 HTTP 協(xié)議,也是具有響應(yīng)頭和響應(yīng)體,我覺(jué)得我這個(gè)和 HTTP 協(xié)議有點(diǎn)相似,雖然我的想法很簡(jiǎn)陋,但是好像確實(shí)是有點(diǎn)相似,可能我看到的東西,對(duì)我也有了影響。

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

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

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

    Java黑科技:replace首個(gè)替換一秒搞定

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

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

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

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

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

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

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

最新評(píng)論