Pourquoi ? Vous avez ce blog ? J'ai lu des choses connexes récemment. Il n'y a aucun problème à simplement utiliser Socket pour la programmation, mais cela n'établit que quelques concepts de base. On ne peut toujours rien faire pour résoudre le véritable problème.
Lorsque j'ai besoin de transférer des fichiers, je constate que je viens d'envoyer les données (données binaires), mais certaines informations sur le fichier sont perdues (extension de fichier). Et je ne peux utiliser qu'un seul Socket pour envoyer un fichier à chaque fois, et il n'y a aucun moyen d'envoyer des fichiers en continu (car je compte sur la fermeture du flux pour terminer l'envoi des fichiers, ce qui signifie que je ne connais pas la longueur du fichier , je ne peux donc envoyer des fichiers que car une connexion Socket représente un fichier).
Ces problèmes me dérangent depuis longtemps. Je suis allé sur Internet pour chercher brièvement, mais je n'ai trouvé aucun exemple prêt à l'emploi (peut-être que je ne les ai pas trouvés). ). Quelqu'un l'a mentionné, vous pouvez #🎜 vous-même 🎜#DefinitionProtocol pour l'envoyer. Cela a piqué mon intérêt et j'ai eu l'impression d'avoir compris quelque chose, car je venais d'apprendre le cours Réseau Informatique Pour être honnête, je n'ai pas très bien appris, mais je connaissais le concept. de réseau informatique.
Au cours des réseaux informatiques, de nombreux protocoles ont été évoqués, et j'ai aussi eu la notion de protocoles sans le savoir. J'ai donc trouvé une solution :Définir moi-même un protocole simple sur la couche TCP. En définissant le protocole, le problème est résolu.
Le rôle du protocoleEnvoyer des données de l'hôte 1 à l'hôte 2. Du point de vue de la couche application, ils ne peuvent voir que les données de l'application, mais nous pouvons voir à travers le diagramme, on peut voir que les données commencent à partir de l'hôte 1 et qu'un en-tête est ajouté aux données pour chaque couche inférieure, puis se propage sur le réseau lorsqu'elles atteignent l'hôte 2, un en-tête est supprimé pour chaque couche supérieure. Lorsqu'il atteint la couche application, il n'y a que des données. (Voici juste une brève explication. En fait, ce n'est pas assez rigoureux, mais cela suffit pour une simple compréhension.) #Ainsi, je peux définir moi-même un protocole simple, mettre certaines informations nécessaires dans l'en-tête du protocole, puis laisser le programme informatique analyser les informations de l'en-tête du protocole par lui-même, et chaque message de protocole équivaut à un fichier. De cette façon, plusieurs protocoles constituent plusieurs fichiers. Et les protocoles peuvent être distingués. Sinon, si plusieurs fichiers sont transmis en continu, la transmission n'a aucun sens si le flux d'octets appartenant à chaque fichier ne peut pas être distingué.Définir le format d'envoi des données (protocole)
Le format d'envoi ici (je me sens un peu similaire au protocole du réseau informatique, donc je l'appelle a Juste un simple accord).Format d'envoi : en-tête de données + corps de données
En-tête de données : une donnée d'une longueur d'un octet, représentant le contenu du fichier taper. Remarque : Comme le type de chaque fichier est différent et que la longueur est également différente, nous savons que l'en-tête du protocole a généralement une longueur fixe (nous ne considérons pas ceux de longueur variable), j'utilise donc une relation de mappage, c'est-à-dire autrement dit, un numéro d'octet représente le type d'un fichier.Donnez un exemple, comme suit :
key#🎜 🎜 #valeur
txt | |
2 | |
3#🎜 🎜 # | |
4 | |
Remarque : ce que je fais ici est une simulation, je n'ai donc besoin que de tester quelques types. Corps de données : La partie données du fichier (données binaires). CodeClientClasse d'en-tête de protocole 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; } } Copier après la connexion Envoyer la classe de fichier 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; } } Copier après la connexion Remarque :
Classe de test 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"); } } } Copier après la connexion Côté serveurClasse d'analyse de protocole 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)); } } Copier après la connexion Remarque :
if (exit == -1) { System.out.println("文件上传结束!"); break; } Copier après la connexion Classe de test Testez simplement une connexion ici. Ceci est juste un exemple illustratif. 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"); } } } Copier après la connexion Résultats des testsClient Serveur Répertoire de fichiers source Celui-ci contient les cinq types de fichiers que j'ai testés. Faites attention à comparer les informations de taille du fichier. Pour les tests IO, j'aime utiliser les tests d'image et de vidéo, car ce sont des fichiers très spéciaux. S'il y a une légère erreur (moins ou plus d'octets), le fichier sera fondamentalement endommagé. , et les performances L'image ne s'affiche pas correctement et la vidéo ne peut pas être lue normalement. Répertoire des fichiers de destination |
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!