“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”
Under normal circumstances, who would use TCP to transfer a file?
At the beginning of writing I feel this function is special chicken ribs, TCP transfer files, there is no way to need such a function, as long as the scalp, if it is not this demand, I will swear, really not redundant!
Okay, so let’s get straight to how file sending is used in TCP communication.
Required features: client –>> server sends files
Development environment: VS2017 + QT5.14.2
Development language: C++
What are the major challenges to implementing this feature?
-
1: What if the file is too large?
-
2: How to solve the transmission interruption?
-
3. How to deal with interface stuck during transmission?
-
4: The file is sent. How does the server handle it?
These are some of the problems I encountered when implementing the send file function, and I will address them in a future article. If there are other questions you don’t understand, please leave a message and tell me. We can discuss and solve them together.
1: Defines communication protocols
In TCP communication, the bridge between the server and the client is the communication protocol. Defining a simple and straightforward communication protocol is also more efficient for us to implement functions with less effort.
Speaking of sending files, we can assume that the communication command word for sending files is A1.
First, we need to send the name and size of the file to the server before sending its contents.
We can define a JSON structure to send
PNG ", "fileSize":100}Copy the code
In the above structure, fileName stores the name of the file to be sent, which is represented by a string. FileSize stores the size of files that need to be sent.
Second, the actual file content with the command word A1 is then sent directly. For fast delivery, assume that up to 40960 real content can be sent at one time.
Above, we will send the file communication instructions established. So let’s start using it directly
2: sends file information to the server
The first step is to read the size of the file to be sent.
There are many ways to read files, such as: QFile, ifstream, or FILE.
I don’t know what the specification is when we write programs, as far as I am concerned, for some communication classes, the vast majority will adopt pure C++ standard way, so that no matter using MFC framework and QT framework, or even WIN32 programs can be compatible.
FILE* readFile = fopen(spath.c_str(), "rb");
fseek(readFile, 0, SEEK_END);
int nFileSize = ftell(readFile);
Copy the code
The second step is to form the valid data into a JSON structure string data
Json data serialization and deserialization functions, you should be familiar with, here I also write out.
Before using json functionality, remember to add #include “json.h”
It’s a very simple way to write it, so I don’t have to say too much about it.
However, it is important to note that the number in UTF8 format must be used during communication. You can either use the C++ method or you can use the QT method, which is a little bit faster. After all, I am using VS+QT development environment here.
Step three, send a message to the server
STD: : string sJson = m_pDataParsing. AnalysisFileInforToJson (" file name ", "file size"); int nSendJsonSize = sJson.length(); int nTotalSize = 27 + nSendJsonSize + 1; char* chSend = new char[nTotalSize]; memset(chSend, 0, nTotalSize); this->InitHeaderData(chSend); chSend[21] = 0x00; ChSend [22] = 0x00; chSend[23] = 0xA1; ChSend [24] = nSendJsonSize /256/256; chSend[24] = nSendJsonSize /256/256; chSend[25] = nSendJsonSize /256; chSend[26] = nSendJsonSize %256; // Copy the json data string to the char* array memcpy(chSend+27, sjson.c_str (), nSendJsonSize); chSend[nTotalSize-1] = 0xEF; chSend[nTotalSize] = '\0'; Delete [] chSend; // delete[] chSend; chSend = nullptr;Copy the code
The above code converts JSON data into CHAR data for sending.
InitHeaderData() : This store is reserved 20 bytes, is in the open process to prevent subsequent addition, according to personal requirements.
24, 25, and 26 bytes are the size of files to store in order to prevent larger files from being sent.
The difficulty for my understanding is how to assign JSON string data to a CHAR array; the rest is pretty straightforward.
3: sends file content to the server
This step is a bit trickier to understand and involves how the page looks.
When we send a small file, the message is sent quickly, with little page lag. But this problem can occur when sending a larger file.
After the phase 2 implementation is complete, we need to do the following to send the valid file contents to the server.
As a first step, we need to restore the file pointer to the end
rewind(readFile); // Return the pointer to the end
Cause: The file pointer position is at the end of the file when reading the entire file size. The actual content needs to be sent from the start. \
Step 2: Define the variables needed to send the file
What do we need to know when we send a file?
int nTotalSize = nFileSize; char chFileBuffer[g_nMaxSendByte]; int nMoveSize = 0; Bool bOK = true;Copy the code
The total size of the file, nTotalSize, chFileBuffer, which records the contents of the file each time it is read, the position of the file pointer movement, and the success flag help us break out of the loop.
Phase 1 showed that we take up to 40960 valid contents per read, where g_nMaxSendByte = 40960;
Step 3: Read the file and send it
It’s a good habit to get in the habit of emptying the chFileBuffer of old data before reading files.
memset(chFileBuffer, 0, g_nMaxSendByte];
Gets the actual size of the current send read.
This is a ternary operator operation, mainly because I am too lazy to write if and else statements, haha.
This means:
When the total size of the file is less than 40960 lengths, the file content is the total size of the file, otherwise 40960 lengths
int nSendSize = nTotalSize <= g_nMaxSendByte ? nTotalSize : g_nMaxSendByte;
Records the size of the file pointer to be moved
nMoveSize += nSendSize;
Read what was just calculated from the file
fread(chFileBuffer, sizeof(char), nSendSize, readFile);
Sends the read file contents (chFileBuffer) to the server.
The format of sending data is similar to that of sending JSON data, with the main difference being that char data is sent. So how do I assign a char array to a char star
memcpy(data + 27, senddata, nsendSize); // Send the JSON string
After successfully sending the file, remember to subtract the sent size from the total file size. If the file size is negative, the file has been sent
nTotalSize -= nSendSize; if (nTotalSize <= 0) { break; // out of the loop}
Offsets the pointer to a file
fseek(readFile, nMoveSize, SEEK_SET); // Move the pointer away from the head file by nSendSize
Under normal circumstances, that’s all the while loop sends.
Why is it normal? What if, during file transfer, the server is disconnected? If the socket == INVALID_SOCKET, we need to check whether the socket has valid data
Next, show the entire looping file content
while (true) { Sleep(100); memset(chFileBuffer, 0, g_nMaxSendByte); Int nSendSize = nTotalSize <= g_nMaxSendByte? nTotalSize : g_nMaxSendByte; nMoveSize += nSendSize; fread(chFileBuffer, sizeof(char), nSendSize, readFile); int nTotalSize = 27 + nSendSize + 1; char* chSend = new char[nTotalSize]; / * this is assembled to send data is similar to send the file information, don't do detailed * / if (m_pTcpPanel - > m_ClientThreadInfo. ServerSocket! = INVALID_SOCKET) { int nRet = send(serverSocket, chSendData, nsocketSize, 0); If (nRet == SOCKET_ERROR) {if (nRet == SOCKET_ERROR) {if (nRet == SOCKET_ERROR) {if (nRet == SOCKET_ERROR) { chSendData = nullptr; bOK = false; break; } } else { bOK = false; Delete [] chSendData; chSendData = nullptr; break; Delete [] chSendData; delete[] chSendData; chSendData = nullptr; nTotalSize -= nSendSize; if (nTotalSize <= 0) { break; } fseek(readFile, nMoveSize, SEEK_SET); } fclose(readFile); // After sending, you need to close the fileCopy the code
4: The interface is stuck
When sending a large document in this way, the interface is bound to freeze. This is also the most troublesome part of C++ program processing.
This was the first solution THAT came to my mind when I first wrote this feature.
However, IT occurred to me that if I had written a program that was slow in loading data, it might not be the transfer of files, but something else. Do I still have threads flying around?
Just thinking about it gives me a headache. Later I used the “QtConcurrent::run” method, which is QT’s way of setting up a thread quickly, but with this method the function must return True, otherwise it would be stuck in an infinite loop.
When sending a file, load the waiting page again and send the file content again, using a process process, much easier than starting a thread.
5: The server receives file information from the client
The first step is to define the receiving data structure
First, we need to record the file information, that is, the file name, the file size, and the size of the received file. There is a large amount of data, and the structure used here is used for storage.
In a many-to-one relationship, the STD ::vector m_vetInfo member is used to record client data on the server side. You can define other containers, such as map and list, based on actual usage
The second step is to record the file information sent by the client
First, the client that is currently sending the message is queried from a number of clients
When we query the matching client, if the total file size recorded in the structure is ==0, it indicates that the basic situation of the file is stored for the first time, the data is parsed and stored
If (itFind->nTotalFileSize == 0) {int nTotalFilesSize = 0; std::string sFileName = ""; ItFind ->sFileName = sFileName; itFind->nTotalFileSize = nTotalFilesSize; itFind->nReceivedSize = 0; STD ::string sSavePath = "This is the path of the current file and is already created "; itFind->writeFile = fopen(sSavePath, "wb"); itFind->sSavePath = sSavePath; }Copy the code
Third, record the actual file information
When the total file size in the structure is not zero, actual valid data is being received
Gets the actual received file size
int nJsonSize = (unsigned char)data[24] * 256 * 256 + (unsigned char)data[25] * 256 + (unsigned char)data[26];
Stores the received content in a file pointer for storage
fwrite(data + 27, sizeof(char), nJsonSize, itFind->writeFile);
Records the actual length that has been received after logging to the file pointer
itFind->nReceivedSize += nJsonSize;
If the size of the file recorded by the server is less than or equal to the receiving size, the file is received successfully
if(itFind->nTotalFileSize <= itFind->nReceivedSize) { fclose(itFind->writeFile); ItFind ->nTotalFileSize = 0; itFind->nReceivedSize = -1; itFind->sSavePath = ""; itFind->sFileName = ""; }Copy the code
This allows you to receive the file name and size for the first time and the actual file content for the next time.
This is how the server receives in action.
Today’s TCP file transfer operation has been explained.
Although the function is very weak, consider yourself to improve the level of communication.