Verwenden Sie eine TCP-Verbindung, um mehrere Dateien zu senden
Warum dieser Blog? Ich habe kürzlich einige verwandte Dinge gelesen. Es ist kein Problem, Socket einfach zum Programmieren zu verwenden, aber dies legt nur einige grundlegende Konzepte fest. Gegen das eigentliche Problem lässt sich noch immer nichts unternehmen.
Wenn ich Dateien übertragen muss, stelle ich fest, dass ich scheinbar gerade die Daten gesendet habe (Binärdaten), aber einige Informationen über die Datei verloren gegangen sind (die Dateierweiterung). Und ich kann jedes Mal nur einen Socket verwenden, um eine Datei zu senden, und es gibt keine Möglichkeit, Dateien kontinuierlich zu senden (da ich darauf angewiesen bin, den Stream zu schließen, um das Senden von Dateien abzuschließen, was bedeutet, dass ich die Länge der Datei tatsächlich nicht kenne , also kann ich nur eine Socket-Verbindung verwenden, die eine Datei darstellt).
Diese Probleme haben mich schon lange beschäftigt, um eine einfache Suche durchzuführen, aber ich habe keine vorgefertigten Beispiele gefunden (vielleicht habe ich sie nicht gefunden). Definieren Sie das Protokoll selbst und senden Sie es. Das weckte mein Interesse und ich hatte das Gefühl, etwas verstanden zu haben, denn ich hatte gerade den Kurs „Computernetzwerk“ gelernt. Ehrlich gesagt habe ich nicht sehr gut gelernt, aber ich habe das Konzept von Computernetzwerken gelernt. Im Computernetzwerkkurs wurden viele Protokolle erwähnt, und ich hatte unbewusst das Konzept von Protokollen. Also habe ich eine Lösung gefunden:
Ich definiere selbst ein einfaches Protokoll auf der TCP-Ebene.
Durch die Definition des Protokolls wird das Problem gelöst. Die Rolle des Protokolls
Senden Sie Daten von Host 1 an Host 2. Aus der Perspektive der Anwendungsschicht können sie nur die Anwendungsdaten sehen, aber wir können sie durch das Diagramm sehen. Die Daten beginnen bei Host 1. und jede Richtung Die nächste Datenschicht fügt einen Header hinzu und breitet sich dann im Netzwerk aus. Wenn sie Host 2 erreicht, wird ein Header für jede aufwärts gerichtete Schicht entfernt. Wenn sie die Anwendungsschicht erreicht, gibt es nur Daten. (Dies ist nur eine kurze Erklärung. Tatsächlich ist dies nicht streng genug, aber für ein einfaches Verständnis reicht es aus.)
Ich kann also selbst ein einfaches Protokoll definieren und einige notwendige Informationen in das Protokoll einfügen Lassen Sie dann das Computerprogramm die Protokoll-Header-Informationen selbst analysieren, und jede Protokollnachricht entspricht einer Datei. Auf diese Weise sind mehrere Protokolle mehrere Dateien. Und die Protokolle können unterschieden werden, wenn mehrere Dateien kontinuierlich übertragen werden, wenn der zu jeder Datei gehörende Bytestrom nicht unterschieden werden kann.
Definieren Sie das Datensendeformat (Protokoll)
Das Sendeformat hier (ich denke, es ähnelt ein wenig dem Protokoll in Computernetzwerken, also nennen wir es ein einfaches Protokoll).
Sendeformat: Datenkopf + Datenkörper
Datenkopf: Daten mit einer Länge von einem Byte, die den Dateityp angeben. Hinweis: Da der Typ jeder Datei unterschiedlich ist und auch die Länge unterschiedlich ist, wissen wir, dass der Header des Protokolls im Allgemeinen eine feste Länge hat (wir berücksichtigen keine mit variabler Länge), daher verwende ich eine Zuordnungsbeziehung Das heißt, eine Bytezahl repräsentiert den Typ einer Datei.
Nennen Sie ein Beispiel wie folgt:
Schlüssel
Wert |
|
0
txt |
|
1
png |
|
2
jpg |
|
3
jpeg |
|
4
avi |
|
Hinweis: Was ich hier mache, ist eine Simulation, daher muss ich nur ein paar Typen testen.
Datenkörper: Der Datenteil der Datei (Binärdaten).
Code
Client
Protokoll-Header-Klasse
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;
}
}
Nach dem Login kopieren
Dateiklasse senden
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;
}
}
Nach dem Login kopieren
Hinweis:
Nach dem Senden einer Datei müssen Sie die Aktualisierung erzwingen. Da ich einen Pufferstrom verwende, wissen wir, dass wir zur Verbesserung der Sendeeffizienz keine Daten senden, sobald Daten vorhanden sind, sondern vor dem Senden warten, bis der Puffer voll ist, da der E/A-Prozess sehr langsam ist ( Wenn Sie also nicht aktualisieren und das Datenvolumen sehr gering ist, empfängt der Server möglicherweise keine Daten (Wenn Sie interessiert sind, können Sie mehr über dieses Problem erfahren. ), dies ist ein Problem das braucht Aufmerksamkeit. (Das von mir getestete Beispiel hatte eine Textdatei, die nur 31 Byte groß war).
getLong()
Methode konvertiert Long-Daten in Byte-Daten, aber ich habe diese Methode aus dem Java-Quellcode kopiert, und eine ihrer Methoden ist writeLong(). Die zugrunde liegende Implementierung besteht darin, Long in Byte umzuwandeln, daher habe ich es mir direkt ausgeliehen. (Eigentlich ist das nicht sehr kompliziert. Es umfasst nur Bitoperationen, aber das Schreiben dieses Codes ist sehr leistungsfähig, daher habe ich mich entschieden, diesen Code direkt zu verwenden. Wenn Sie sich für Bitoperationen interessieren, können Sie auf meinen Blog verweisen: Bitoperationsoperation ).
Testklasse
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");
}
}
}
Nach dem Login kopieren
Serverseite
Protokollanalyseklasse
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));
}
}
Nach dem Login kopieren
Hinweis:
Ich glaube, dass jeder diese Methode zum Konvertieren von Byte in Long erraten kann. DataInputStream hat eine Methode namens readLong(), also habe ich sie direkt verwendet. (Ich denke, diese beiden Codeteile sind sehr gut geschrieben, aber ich habe mir nur den Quellcode einiger Klassen angesehen, haha!)
Hier verwende ich eine Endlosschleife zum Lesen von Dateien, aber beim Testen habe ich festgestellt Es gibt ein Problem, das schwer zu lösen ist: Wann soll die Schleife beendet werden? Ursprünglich habe ich das Herunterfahren des Clients als Beendigungsbedingung verwendet, aber festgestellt, dass es nicht funktioniert. Später wurde entdeckt, dass bei Netzwerk-Streams das Lesen von -1 bedeutet, dass der entgegengesetzte Eingabestream geschlossen wurde, sodass dies als Zeichen zum Verlassen der Schleife verwendet wird. Wenn dieser Code gelöscht wird, wird das Programm nicht automatisch beendet und es wird immer die zuletzt gelesene Datei generiert. Da die Daten jedoch nicht gelesen werden können, handelt es sich bei allen Dateien um 0-Byte-Dateien. (Dieses Ding generiert Dateien sehr schnell. Es generiert Tausende von Dateien in etwa ein paar Sekunden. Wenn Sie interessiert sind, können Sie es versuchen, aber es ist am besten, das Programm schnell zu beenden, haha! )
if (exit == -1) {
System.out.println("文件上传结束!");
break;
}
Nach dem Login kopieren
Testklasse
Testen Sie hier einfach eine Verbindung. Dies ist nur ein anschauliches Beispiel.
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");
}
}
}
Nach dem Login kopieren
Testergebnisse
Client
Server
Quelldateiverzeichnis Dieses enthält die fünf Dateitypen, die ich getestet habe. Achten Sie auf den Vergleich der Größeninformationen der Datei. Ich verwende gerne Bild- und Videotests, da es sich um sehr spezielle Dateien handelt. Bei einem geringfügigen Fehler (weniger oder mehr Bytes) wird die Datei grundsätzlich beschädigt , und die Leistung Das Bild wird nicht richtig angezeigt und das Video kann nicht normal abgespielt werden.
Zieldateiverzeichnis
Das obige ist der detaillierte Inhalt vonWie sende ich mehrere Dateien über eine einzige TCP-Verbindung in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!