Basic FTP client
FTP download client implemented by QT C + +
Environmental description
FTP server: centos7 8 + vsftpd 3.0.2 installation settings see blog
Client: win10+QT 5.15.2
The implementation is not a fully functional FTP client, but the program has the need to download files from the FTP server. It mainly realizes the download function, including breakpoint continuation, and does not realize multi-threaded download. The implementation of multi-threaded download has something to do with breakpoint continuation. It is simple to realize multi-threaded download after understanding breakpoint continuation.
FTP protocol is based on TCP. Socket programming is used in the implementation. Messages are sent between the client and the server. See the last pictures of the previous blog post for the format of messages.
Simple FTP client implementation of C + + (I) basic knowledge of FTP
Sample code download:
FTP client implemented by QT C + +, with breakpoint continuation function
Establish Socket connection
WSADATA dat; int ret; //Initialization is very important if (::WSAStartup(MAKEWORD(2,2),&dat) != 0) //Windows Sockets Asynchronous startup { cout<<"Init Failed: "<<GetLastError()<<endl; emit emitInfo(network , "Init Failed!\n"); return -1; } //Create Socket controlSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(controlSocket==INVALID_SOCKET) { cout<<"Creating Control Socket Failed: "<<GetLastError()<<endl; emit emitInfo(network , "Creating Control Socket Failed.\n"); return -1; } //Build server access parameter structure serverAddr.sin_family=AF_INET; serverAddr.sin_addr.S_un.S_addr=inet_addr(ip_addr.c_str()); //address serverAddr.sin_port=htons(PORT); //port memset(serverAddr.sin_zero,0,sizeof(serverAddr.sin_zero)); //connect ret = ::connect(controlSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr)); if(ret==SOCKET_ERROR) { cout<<"Control Socket Connecting Failed: "<<GetLastError()<<endl; emit emitInfo(network , "Control Socket Connecting Failed\n"); return -1; }
User name password login:
//user name executeCmd("USER " + username); if(recvControl(331) != 0) { emit emitInfo(userpass, ""); } //password executeCmd("PASS " + password); if(recvControl(230) != 0) { emit emitInfo(userpass, "Wrong user name or password!"); return -1; }
change directory
executeCmd("CWD "+tardir); if(recvControl(250) != 0) { emit emitInfo(directory, "FTP Directory does not exist!"); return -1; }
Toggle Binary mode
memset(buf, 0, BUFLEN); executeCmd("TYPE I"); if(recvControl(200) != 0) { emit emitInfo(filename, "switch BINARY Mode failed!"); return -1; }
List all files in the current directory
int FtpClient::listPwd() { intoPasv(); executeCmd("LIST -al"); recvControl(150); memset(databuf, 0, DATABUFLEN); string fulllist; int ret = recv(dataSocket, databuf, DATABUFLEN-1, 0); while(ret>0) { databuf[ret] = '\0'; fulllist += databuf; ret = recv(dataSocket, databuf, DATABUFLEN-1, 0); } removeSpace(fulllist); int lastp, lastq, p, q; vector<string> eachrow; string rawrow; string item; filelist.clear(); p = fulllist.find("\r\n"); lastp = 0; while(p>=0) { eachrow.clear(); rawrow = fulllist.substr(lastp, p-lastp); q = rawrow.find(' '); lastq = 0; for(int i=0; i<8; i++) { item = rawrow.substr(lastq, q-lastq); eachrow.push_back(item); lastq = q + 1; q = rawrow.find(' ', lastq); } item = rawrow.substr(lastq); eachrow.push_back(item); filelist.push_back(eachrow); lastp = p + 2; p = fulllist.find("\r\n", lastp); } closesocket(dataSocket); recvControl(226); return 0; }
Switch to PASV mode
int dataPort, ret; //Switch to passive mode executeCmd("PASV"); recvControl(227); //The returned information format is -- h1,h2,h3,h4,p1,p2 //Where H1, H2, H3 and H4 are server addresses and p1*256+p2 are data ports dataPort = getPortNum(); //Client data transmission socket dataSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); serverAddr.sin_port = htons(dataPort); //Change the port value in the connection parameters ret = ::connect(dataSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr)); if(ret == SOCKET_ERROR) { cout<<"Data Socket connecting Failed: "<<GetLastError()<<endl; return -1; } cout<<"Data Socket connecting is success."<<endl; return 0;
Download File:
int cur = 0; ofstream ofile; string localFile = localDir + "/" + localName; QFileInfo fileinfo(QString::fromStdString(localFile)); int ss = fileinfo.size(); ofile.open(localFile, ios_base::binary); if(intoPasv() == -1) { ofile.close(); emit emitInfo(network, "get into pasv Mode failed!"); return -1; } executeCmd("RETR "+remoteName); if(recvControl(150) != 0) { ofile.close(); emit emitInfo(filename, "FTP File does not exist!"); return -1; } memset(databuf, 0, DATABUFLEN); int ret = recv(dataSocket, databuf, DATABUFLEN, 0); while(ret > 0) { cur += ret; //cout << cur << " : " << size; emit emitProcess(cur, size); ofile.write(databuf, ret); ofile.flush(); ret = recv(dataSocket, databuf, DATABUFLEN, 0); if(ret == -1) { cout << "sending file, socker error!" << endl; emit emitInfo(network, "Failed to transfer file, network disconnected!"); break; } } ofile.close();
Breakpoint continuation
To realize the continuous transmission of breakpoints, the REST command is mainly used to transfer files from a specific offset.
First obtain the local file size and compare it with the server file. If it is smaller than the server file size, start breakpoint continuous transmission. The local file is opened in append mode and written from the end of the file.
Before setting the offset REST local file size, you need to switch to Binary mode. Generally, the default mode of FTP server is Ascii mode, and Ascii mode cannot continue transmission at breakpoints.
Core code:
int cur = 0; ofstream ofile; string localFile = localDir + "/" + localName; QFileInfo fileinfo(QString::fromStdString(localFile)); int ss = fileinfo.size(); if(resume) { if(ss > 0) { if(ss >= size) { // The local file is larger than or equal to that on ftp. It is overwritten by default cout << "s >= size-----" << endl; //ofile.seekp(0, std::ios::beg); ofile.open(localFile, ios_base::binary); } else { if(setTypeI() == -1) { //Cout < < "failed to set BINARY mode, unable to resume transmission at breakpoint, start from scratch!"<< endl; //ofile.seekp(0, std::ios::beg); // Failed to set BINARY mode, unable to resume transmission at breakpoint, start from scratch //ofile.open(localFile, ios_base::binary); emit emitInfo(network, "set up BINARY Mode failed!"); return -1; } else { if(restFile(ss) == -1) { //ofile.open(localFile, ios_base::binary); //ofile.seekp(0, std::ios::beg); // Failed to set breakpoint to resume transmission. Start from scratch emit emitInfo(network, "Failed to set resume mode!"); return -1; } else { cout << "begin resume break-point!" << endl; ofile.open(localFile, ios_base::binary|ios_base::app); cur += ss; } } } } else { ofile.open(localFile, ios_base::binary); } } else { ofile.open(localFile, ios_base::binary); } if(intoPasv() == -1) { ofile.close(); emit emitInfo(network, "get into pasv Mode failed!"); return -1; } executeCmd("RETR "+remoteName); if(recvControl(150) != 0) { ofile.close(); emit emitInfo(filename, "FTP File does not exist!"); return -1; } memset(databuf, 0, DATABUFLEN); int ret = recv(dataSocket, databuf, DATABUFLEN, 0); while(ret > 0) { cur += ret; //cout << cur << " : " << size; emit emitProcess(cur, size); ofile.write(databuf, ret); ofile.flush(); ret = recv(dataSocket, databuf, DATABUFLEN, 0); if(ret == -1) { cout << "sending file, socker error!" << endl; emit emitInfo(network, "Failed to transfer file, network disconnected!"); break; } } ofile.close();
Class encapsulation
It mainly includes three classes:
class FtpClient : public QObject
class ClientThread : public QThread
class ClientManager : public QObject
ClientManager class is an external interface class, ftpclient class is an ftp client class that interacts with the server, and ClientThread is a thread execution class that encapsulates a series of calls of ftpclient class in a thread function.
The FtpClient class reports information to the ClientManager class through signal, such as whether the login is successful, file download progress, etc. After receiving the important information, the ClientManager class throws up the signal again as needed.
The upper layer program calls respond to signal first
connect(&m_Client, SIGNAL(emitProcess(int,int)), this, SLOT(on_emitDownloadSize(int,int)));
connect(&m_Client, SIGNAL(emitError(int,QString)), this, SLOT(on_emitError(int,QString)));
Then it calls two functions to download files
m_ClientManager.setDownloadInfo(ui->textEdit1->toPlainText(), ui->textEdit1_2->toPlainText(), ui->textEdit1_3->toPlainText(), ui->textEdit1_4->toPlainText(), ui->textEdit1_5->toPlainText(), ui->textEdit1_6->toPlainText(), ui->textEdit1_7->toPlainText());
m_ ClientManager.startDownload();
Print out the main information of interaction with FTP server during program operation as follows:
Multithreaded Download
There is no multi-threaded download in my program. If you want to do it, you also need to use the REST command. Calculate the download offset of each thread according to the number of threads. Each thread connects the data port to the server through pasv mode, then sets the translation amount, stops downloading after downloading bytes of a specific size, and finally splices several file segments locally.