How to send multiple files using a single TCP connection in Java?
Use one TCP connection to send multiple files
Why is there this blog? I have been reading some related things recently. There is no problem in simply using Socket for programming, but this only establishes some basic concepts. Still nothing can be done about the real problem.
When I need to transfer files, I find that I seem to have just sent the data (binary data), but some information about the file is lost (the file extension). And I can only use one Socket to send one file each time, and there is no way to send files continuously (because I rely on closing the stream to complete sending files, which means that I actually don’t know the length of the file, so I can only send files as one Socket connection represents a file).
These problems have troubled me for a long time. I went to the Internet to briefly search, but I didn’t find any ready-made examples (maybe I didn’t find them). Someone mentioned that you can define the protocol yourself. to send. This piqued my interest, and I felt like I understood something, because I had just taken the course Computer Network. To be honest, I didn’t learn very well, but I did learn the concept of computer network. .
In the course of computer network, many protocols were mentioned, and I also had the concept of protocols unknowingly. So I found a solution: define a simple protocol on the TCP layer myself. By defining the protocol, the problem is solved.
The role of the protocol
Send data from host 1 to host 2. From the perspective of the application layer, they can only see the application data, but we can see it through the diagram. The data starts from host 1, and a header is added to the data for each lower layer, and then spreads on the network. When it reaches host 2, a header is removed for each upper layer. When it reaches the application layer, there is only data. (This is just a brief explanation. In fact, this is not rigorous enough, but it is enough for a simple understanding.)
So, I can define one myself A simple protocol puts some necessary information in the protocol header, and then lets the computer program parse the protocol header information by itself, and each protocol message is equivalent to a file. In this way, multiple protocols are multiple files. And the protocols can be distinguished. Otherwise, if multiple files are transmitted continuously, the transmission is meaningless if the byte stream belonging to each file cannot be distinguished.
Define the data sending format (protocol)
The sending format here (I feel it is a bit similar to the protocol in the computer network, let’s call it a simple protocol) .
Sending format: Data header Data body
Data header: A data with a length of one byte, indicating the file type. Note: Because the type of each file is different, and the length is also different, we know that the header of the protocol generally has a fixed length (we do not consider those with variable length), so I use a mapping relationship , that is, a byte number represents the type of a file.
Give an example, as follows:
key | value |
0 | txt |
1 | png |
2 | jpg |
3 | jpeg |
4 | avi |
Note: What I am doing here is a simulation, so I only need to test a few types.
Data body: The data part of the file (binary data).
Code
Client
Protocol header class
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; } }
Send file class
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; } }
Note:
After sending a file, you need to force refresh it. Because I am using a buffer stream, we know that in order to improve the efficiency of sending, we do not send data as soon as there is data, but wait until the buffer is full before sending. Because the IO process is very slow (compared to the CPU), so If you do not refresh, when the data volume is very small, the server may not be able to receive the data (, if you are interested, you can learn more about it.), this is a problem that needs attention. . (The example I tested had a text file that was only 31 bytes).
getLong()
method converts a long type data into byte type data. We know that long occupies 8 bytes, but this method is from the Java source code. Copied from it, there is a class called DataOutputStream. One of its methods is writeLong(). Its underlying implementation is to convert long into byte, so I directly borrowed it. (Actually, this is not very complicated. It only involves bit operations, but writing this code is very powerful, so I chose to use this code directly. If you are interested in bit operations, you can refer to one of my blogs: Bit Operations operation).
Test class
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"); } } }
Server side
Protocol parsing class
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)); } }
Note:
-
I believe everyone can guess this method of converting byte to long. DataInputStream has a method called readLong(), so I used it directly. (I think these two pieces of code are very well written, but I just looked at the source code of a few classes, haha!)
Here I use an infinite loop to read the file. But when I was testing, I found a problem that was difficult to solve: When to end the loop. I initially used client shutdown as the exit condition, but found that it didn't work. Later it was discovered that for network streams, if -1 is read, it means that the opposite input stream has been closed, so this is used as a sign to exit the loop. If this code is deleted, the program will not terminate automatically, and the last read file will always be generated. However, since the data cannot be read, the files are all 0-byte files. (This thing generates files very quickly. It will generate thousands of files in about a few seconds. If you are interested, you can try it, but it is best to terminate the program quickly, haha! )
if (exit == -1) { System.out.println("文件上传结束!"); break; }
Test Class
Just test one connection here. This is just an example.
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"); } } }
Test results
Client
##Server
Source file directory This contains the five files I tested. Pay attention to comparing the size information of the file. For IO testing, I like to use picture and video testing, because they are very special files. If there is a slight error (less or more bytes), the file will basically be damaged, and the performance The picture is not displayed properly and the video cannot be played normally.
Destination file directory
The above is the detailed content of How to send multiple files using a single TCP connection in Java?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











In Go, range is used to iterate over data types and return corresponding values: 1. For slices and arrays, range returns index and element copy; 2. Unwanted indexes or values can be ignored using _; 3. For maps, range returns keys and values, but the iteration order is not fixed; 4. For strings, range returns rune index and characters (rune type), supporting Unicode; 5. For channels, range continues to read values until the channel is closed, and only a single element is returned. Using range can avoid manually managing indexes, making iteratives simpler and safer.

Use meaningful naming: variables such as intdaysSinceModification; methods such as getUserRolesByUsername() to make the code intention clear; 2. Functions should be small and do only one thing: for example, createUser() is split into single responsibility methods such as validateRequest() and mapToUser(); 3. Reduce comments and write self-interpretation code: Use userHasPrivilegedAccess() instead of redundant comments; 4. Handle errors elegantly: do not ignore exceptions, use try-with-resources to automatically manage resources; 5. Follow the "Boy Scout Rules": optimize variables every time you modify

There is an essential difference between JavaScript's WebWorkers and JavaThreads in concurrent processing. 1. JavaScript adopts a single-thread model. WebWorkers is an independent thread provided by the browser. It is suitable for performing time-consuming tasks that do not block the UI, but cannot operate the DOM; 2. Java supports real multithreading from the language level, created through the Thread class, suitable for complex concurrent logic and server-side processing; 3. WebWorkers use postMessage() to communicate with the main thread, which is highly secure and isolated; Java threads can share memory, so synchronization issues need to be paid attention to; 4. WebWorkers are more suitable for front-end parallel computing, such as image processing, and

Use subprocess.run() to safely execute shell commands and capture output. It is recommended to pass parameters in lists to avoid injection risks; 2. When shell characteristics are required, you can set shell=True, but beware of command injection; 3. Use subprocess.Popen to realize real-time output processing; 4. Set check=True to throw exceptions when the command fails; 5. You can directly call chains to obtain output in a simple scenario; you should give priority to subprocess.run() in daily life to avoid using os.system() or deprecated modules. The above methods override the core usage of executing shell commands in Python.

The single responsibility principle (SRP) requires a class to be responsible for only one function, such as separating the saving and mail sending in order processing; 2. The opening and closing principle (OCP) requires opening and closing for extensions and closing for modifications, such as adding new graphics without modifying the calculator; 3. The Richter replacement principle (LSP) requires that subclasses can replace the parent class without destroying the program, such as using independent classes to avoid behavior abnormalities caused by square inheritance rectangles; 4. The interface isolation principle (ISP) requires that clients should not rely on unwanted interfaces, such as splitting the multi-function device interface to independent printing, scanning, and fax interfaces; 5. The dependency inversion principle (DIP) requires that high-level modules do not rely on low-level modules, and both rely on abstraction, such as OrderService depends on Data

GraphQL can be easily integrated in SpringBoot through official support. 1. Use spring-boot-starter-graphql to add dependencies; 2. Define the schema.graphqls file under resources to declare Query and Mutation; 3. Use @Controller to cooperate with @QueryMapping and @MutationMapping to achieve data acquisition; 4. Enable GraphiQL interface testing API; 5. Follow best practices such as input verification, N 1 query prevention, security control, etc., and ultimately implement a flexible and efficient client-driven API.

ProjectLoomintroducesvirtualthreadstosolveJava’sconcurrencylimitationsbyenablinglightweight,scalablethreading.1.VirtualthreadsareJVM-managed,low-footprintthreadsthatallowmillionsofconcurrentthreadswithminimalOSresources.2.Theysimplifyhigh-concurrency

Resilience4j improves the flexibility of Java microservices through circuit breakers, current limiting, retry and other mechanisms. 1. Use circuit breakers to prevent cascade failures and prevent requests from being sent when services fail frequently; 2. Use current limit control to control concurrent access to avoid sudden traffic overwhelming downstream services; 3. Respond to temporary errors through retry mechanisms, but avoid invalid retry and resource waste; 4. Multiple strategies can be used in combination to enhance the overall resilience of the system, but attention should be paid to the mutual influence between policies. Properly configuring these functions can significantly improve the stability and fault tolerance of distributed systems.
