Blog.csdn.net/zhaoxy_thu/…
After implementing the HTTP server, I intend to implement an FTP server. FTP protocol and HTTP are both in the application layer, so the implementation principle is similar. Here the realization of the principle and source code to share with you.
FTP involves command ports and data ports. That is, each client sends commands (such as switching directories and deleting files) to the server through the command port, and receives data (such as directory list and downloading and uploading files) from the server through the data port. This requires that two ports must be maintained for each connection, which would be much more complicated if you used multipath IO like the one in the previous article, so this article uses an Apache-like multi-process mechanism, which creates a separate process for each connection to manage.
The message sent by the FTP server to the client consists of two parts. The first part is the status code (similar to HTTP), and the second part is the specific content (which can be empty). The two parts are separated by Spaces. For example, “220 TS FTP Server Ready” indicates that the client is connected to the Server. The command sent from the client to the server is also composed of two parts. The first part is the command string, and the second part is the specific content (which can be empty). The two parts are separated by Spaces. Take the process of logging in to the FTP server and obtaining the directory list as an example:
[plain] view plain copy
- 220 TS FTP Server ready…
- USER anonymous
- 331 Password required for anonymous
- PASS [email protected]
- 530 Not logged in,password error.
- QUIT
- 221 Goodbye
- USER zhaoxy
- 331 Password required for zhaoxy
- PASS 123
- 230 User zhaoxy logged in
- SYST
- 215 UNIX Type: L8
- PWD
- 257 “/” is current directory.
- TYPE I
- 200 Type set to I
- PASV
- 227 Entering Passive Mode (125,0,0, 121,54)
- SIZE /
- 550 File not found
- PASV
- 227 Entering Passive Mode (127,0,0,1,212,56)
- CWD /
- 250 CWD command successful. “/” is current directory.
- LIST -l
- 150 Opening data channel for directory list.
- 16877 8 501 20 272 4 8 114.
- 16877 29 501 20 986 4 8 114..
- 33188 1 501 20 6148 3 28 114 .DS_Store
- 16877 4 501 20 136 2 27 114 css
- 33279 1 501 20 129639543 6 14 113 haha.pdf
- 16877 11 501 20 374 2 27 114 images
- 33261 1 501 20 11930 3 9 114 index.html
- 16877 6 501 20 204 2 28 114 js
- 226 Transfer ok.
- QUIT
- 221 Goodbye
When a client connects to the server, the server first sends the welcome message 220 to the client, which sends the user name and password to the server. After verification, 530 is returned on failure and 230 is returned on success. Generally, all clients attempt to log in to the server as an anonymous user for the first time. When the login fails, the client asks the user for the user name and password. Next, the client verifies the type of file system with the server, queries the current directory, and formats the data to be transferred.
There are two main FTP formats, binary and ASCII. The main difference between the two formats is the newline. The binary format does not do any processing on the data, while the ASCII format converts the carriage return newline into the local carriage return character, such as \n in Unix, \r\n in Windows, and \ R in Mac. Ordinary images and execution files must be in binary format, and CGI scripts and ordinary HTML files must be in ASCII format.
After determining the transport format, the client sets the transport mode, Passive or Active. In passive mode, the server creates another socket and binds it to a free port and starts listening, sending the local IP and port number (H1, H2, H3, H4, P1, P2, where P1 *256+ P2 equals the port number) to the client. When data needs to be transmitted later, the server notifies the client through the 150 status code. After receiving the data, the client connects to the specified port and waits for data. After the transfer is complete, the server sends a 226 status code to tell the client that the transfer is successful. If the client does not need to maintain the long connection, you can run the QUIT command to disconnect the connection from the server. In active mode, the process is similar to passive mode, except that the socket is created and listened to by the client, and the server connects to the client’s port for data transfer.
Here is the code in the main function:
[cpp] view plain copy
- #include <iostream>
- #include “define.h”
- #include “CFtpHandler.h”
- #include <sys/types.h>
- #include <sys/socket.h>
- int main(int argc, const char * argv[])
- {
- int port = 2100;
- int listenFd = startup(port);
- //ignore SIGCHLD signal, which created by child process when exit, to avoid zombie process
- signal(SIGCHLD,SIG_IGN);
- while (1) {
- int newFd = accept(listenFd, (struct sockaddr *)NULL, NULL);
- if (newFd == -1) {
- //when child process exit, it’ll generate a signal which will cause the parent process accept failed.
- //If happens, continue.
- if (errno == EINTR) continue;
- printf(“accept error: %s(errno: %d)\n”,strerror(errno),errno);
- }
- //timeout of recv
- Struct timeval timeout = {3,0};
- setsockopt(newFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,sizeof(struct timeval));
- int pid = fork();
- //fork error
- if (pid < 0) {
- printf(“fork error: %s(errno: %d)\n”,strerror(errno),errno);
- }
- //child process
- else if (pid == 0) {
- //close useless socket
- close(listenFd);
- send(newFd, TS_FTP_STATUS_READY, strlen(TS_FTP_STATUS_READY), 0);
- CFtpHandler handler(newFd);
- int freeTime = 0;
- while (1) {
- char buff[256];
- int len = (int)recv(newFd, buff, sizeof(buff), 0);
- //connection interruption
- if (len == 0) break;
- //recv timeout return -1
- if (len < 0) {
- freeTime += 3;
- //max waiting time exceed
- if (freeTime >= 30) {
- break;
- }else {
- continue;
- }
- }
- buff[len] = ‘\0’;
- //reset free time
- freeTime = 0;
- if (handler.handleRequest(buff)) {
- break;
- }
- }
- close(newFd);
- std::cout<<“exit”<<std::endl;
- exit(0);
- }
- //parent process
- else {
- //close useless socket
- close(newFd);
- }
- }
- close(listenFd);
- return 0;
- }
The socket is created and bound to the specified port, and then the loop starts listening for the port. A child process is forked each time a new connection is listened on. The child process sends a welcome message to the client and enters a loop to process the command sent by the client until it receives the QUIT command or the connection times out. If the parent process does not process the SIGCHLD signal, the child process will become a zombie process. In this article, the child process will be ignored. Second, the accept function will return directly when the parent process receives the signal, so it needs to determine if the return is due to the signal, continue the loop, do not fork, otherwise the child process will be created indefinitely; The parent process needs to close the new connection socket, and the child process needs to close the listening socket, so that the socket cannot be completely closed.
Finally, the handleRequest method in the CFtpHandler class is used to process the client command, with the following code:
[cpp] view plain copy
- //handle client request
- bool CFtpHandler::handleRequest(char *buff) {
- stringstream recvStream;
- recvStream<<buff;
- cout<<buff;
- string command;
- recvStream>>command;
- bool isClose = false;
- string msg;
- //username
- if (command == COMMAND_USER) {
- recvStream>>username;
- msg = TS_FTP_STATUS_PWD_REQ(username);
- }
- //password
- else if (command == COMMAND_PASS) {
- recvStream>>password;
- if (username == “zhaoxy” && password == “123”) {
- msg = TS_FTP_STATUS_LOG_IN(username);
- }else {
- msg = TS_FTP_STATUS_PWD_ERROR;
- }
- }
- //quit
- else if (command == COMMAND_QUIT) {
- msg = TS_FTP_STATUS_BYE;
- isClose = true;
- }
- //system type
- else if (command == COMMAND_SYST) {
- msg = TS_FTP_STATUS_SYSTEM_TYPE;
- }
- //current directory
- else if (command == COMMAND_PWD) {
- msg = TS_FTP_STATUS_CUR_DIR(currentPath);
- }
- //transmit type
- else if (command == COMMAND_TYPE) {
- recvStream>>type;
- msg = TS_FTP_STATUS_TRAN_TYPE(type);
- }
- //passive mode
- else if (command == COMMAND_PASSIVE) {
- int port = 0;
- if (m_dataFd) {
- close(m_dataFd);
- }
- m_dataFd = startup(port);
- stringstream stream;
- stream<<TS_FTP_STATUS_PASV<<port/256<<“,”<<port%256<<“)”;
- msg = stream.str();
- //active passive mode
- m_isPassive = true;
- }
- //active mode
- else if (command == COMMAND_PORT) {
- string ipStr;
- recvStream>>ipStr;
- char ipC[32];
- strcpy(ipC, ipStr.c_str());
- char *ext = strtok(ipC, “,”);
- m_clientPort = 0; m_clientIp = 0;
- m_clientIp = atoi(ext);
- int count = 0;
- //convert string to ip address and port number
- //be careful, the ip should be network endianness
- while (1) {
- if ((ext = strtok(NULL, “,”))==NULL) {
- break;
- }
- switch (++count) {
- case 1:
- case 2:
- case 3:
- m_clientIp |= atoi(ext)<<(count*8);
- break;
- case 4:
- m_clientPort += atoi(ext)*256;
- break;
- case 5:
- m_clientPort += atoi(ext);
- break;
- default:
- break;
- }
- }
- msg = TS_FTP_STATUS_PORT_SUCCESS;
- }
- //file size
- else if (command == COMMAND_SIZE) {
- recvStream>>fileName;
- string filePath = ROOT_PATH+currentPath+fileName;
- long fileSize = filesize(filePath.c_str());
- if (fileSize) {
- stringstream stream;
- stream<<TS_FTP_STATUS_FILE_SIZE<<fileSize;
- msg = stream.str();
- }else {
- msg = TS_FTP_STATUS_FILE_NOT_FOUND;
- }
- }
- //change directory
- else if (command == COMMAND_CWD) {
- string tmpPath;
- recvStream>>tmpPath;
- string dirPath = ROOT_PATH+tmpPath;
- if (isDirectory(dirPath.c_str())) {
- currentPath = tmpPath;
- msg = TS_FTP_STATUS_CWD_SUCCESS(currentPath);
- }else {
- msg = TS_FTP_STATUS_CWD_FAILED(currentPath);
- }
- }
- //show file list
- else if (command == COMMAND_LIST || command == COMMAND_MLSD) {
- string param;
- recvStream>>param;
- msg = TS_FTP_STATUS_OPEN_DATA_CHANNEL;
- sendResponse(m_connFd, msg);
- int newFd = getDataSocket();
- //get files in directory
- string dirPath = ROOT_PATH+currentPath;
- DIR *dir = opendir(dirPath.c_str());
- struct dirent *ent;
- struct stat s;
- stringstream stream;
- while ((ent = readdir(dir))! =NULL) {
- string filePath = dirPath + ent->d_name;
- stat(filePath.c_str(), &s);
- struct tm tm = *gmtime(&s.st_mtime);
- //list with -l param
- if (param == “-l”) {
- stream<<s.st_mode<<” “<<s.st_nlink<<” “<<s.st_uid<<” “<<s.st_gid<<” “<<setw(10)<<s.st_size<<” “<<tm.tm_mon<<” “<<tm.tm_mday<<” “<<tm.tm_year<<” “<<ent->d_name<<endl;
- }else {
- stream<<ent->d_name<<endl;
- }
- }
- closedir(dir);
- //send file info
- string fileInfo = stream.str();
- cout<<fileInfo;
- send(newFd, fileInfo.c_str(), fileInfo.size(), 0);
- //close client
- close(newFd);
- //send transfer ok
- msg = TS_FTP_STATUS_TRANSFER_OK;
- }
- //send file
- else if (command == COMMAND_RETRIEVE) {
- recvStream>>fileName;
- msg = TS_FTP_STATUS_TRANSFER_START(fileName);
- sendResponse(m_connFd, msg);
- int newFd = getDataSocket();
- //send file
- std::ifstream file(ROOT_PATH+currentPath+fileName);
- file.seekg(0, std::ifstream::beg);
- while(file.tellg() != -1)
- {
- char *p = new char[1024];
- bzero(p, 1024);
- file.read(p, 1024);
- int n = (int)send(newFd, p, 1024, 0);
- if (n < 0) {
- cout<<“ERROR writing to socket”<<endl;
- break;
- }
- delete p;
- }
- file.close();
- //close client
- close(newFd);
- //send transfer ok
- msg = TS_FTP_STATUS_FILE_SENT;
- }
- //receive file
- else if (command == COMMAND_STORE) {
- recvStream>>fileName;
- msg = TS_FTP_STATUS_UPLOAD_START;
- sendResponse(m_connFd, msg);
- int newFd = getDataSocket();
- //receive file
- ofstream file;
- file.open(ROOT_PATH+currentPath+fileName, ios::out | ios::binary);
- char buff[1024];
- while (1) {
- int n = (int)recv(newFd, buff, sizeof(buff), 0);
- if (n<=0) break;
- file.write(buff, n);
- }
- file.close();
- //close client
- close(newFd);
- //send transfer ok
- msg = TS_FTP_STATUS_FILE_RECEIVE;
- }
- //get support command
- else if (command == COMMAND_FEAT) {
- stringstream stream;
- stream<<“211-Extension supported”<<endl;
- stream<<COMMAND_SIZE<<endl;
- stream<<“211 End”<<endl;;
- msg = stream.str();
- }
- //get parent directory
- else if (command == COMMAND_CDUP) {
- if (currentPath ! {= “/”)
- char path[256];
- strcpy(path, currentPath.c_str());
- char *ext = strtok(path, “/”);
- char *lastExt = ext;
- while (ext! =NULL) {
- ext = strtok(NULL, “/”);
- if (ext) lastExt = ext;
- }
- currentPath = currentPath.substr(0, currentPath.length()-strlen(lastExt)-1);
- }
- msg = TS_FTP_STATUS_CDUP(currentPath);
- }
- //delete file
- else if (command == COMMAND_DELETE) {
- recvStream>>fileName;
- //delete file
- if (remove((ROOT_PATH+currentPath+fileName).c_str()) == 0) {
- msg = TS_FTP_STATUS_DELETE;
- }else {
- printf(“delete error: %s(errno: %d)\n”,strerror(errno),errno);
- msg = TS_FTP_STATUS_DELETE_FAILED;
- }
- }
- //other
- else if (command == COMMAND_NOOP || command == COMMAND_OPTS){
- msg = TS_FTP_STATUS_OK;
- }
- sendResponse(m_connFd, msg);
- return isClose;
- }
The code above treats each command differently and will not be detailed here. It should be noted that the if-else method used in this paper is very inefficient in determining commands, and the time complexity is O(n) (n is the total number of commands). There are two methods for optimization. One is that the FTP commands are composed of four letters, and the ASCII codes of four letters can be spliced into an integer and the switch can be used for judgment. The time complexity is O(1); Second, it is similar to the method in Http server. Each command and the corresponding processing function are stored in hashmap. When receiving a command, the corresponding function can be directly called through hash, and the time complexity is also O(1).
Note the difference between the local byte order and the network byte order. For example, 127.0.0.1 should be converted into an integer in reverse order and stored in the network byte order in s_ADDR variable.
The above source code has been uploaded to GitHub, interested friends can go to download.
\
If you feel helpful to yourself, I hope you can help, thank you 🙂
Personal blog: blog.csdn.net/zhaoxy2850
Address: blog.csdn.net/zhaoxy_thu/…
Please indicate the source of reprint, thank you!
\