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

  1. 220 TS FTP Server ready…  
  2. USER anonymous  
  3. 331 Password required for anonymous  
  4. PASS [email protected]  
  5. 530 Not logged in,password error.  
  6. QUIT  
  7. 221 Goodbye  
  8. USER zhaoxy  
  9. 331 Password required for zhaoxy  
  10. PASS 123  
  11. 230 User zhaoxy logged in  
  12. SYST  
  13. 215 UNIX Type: L8  
  14. PWD  
  15. 257 “/” is current directory.  
  16. TYPE I  
  17. 200 Type set to I  
  18. PASV  
  19. 227 Entering Passive Mode (125,0,0, 121,54)
  20. SIZE /  
  21. 550 File not found  
  22. PASV  
  23. 227 Entering Passive Mode (127,0,0,1,212,56)  
  24. CWD /  
  25. 250 CWD command successful. “/” is current directory.  
  26. LIST -l  
  27. 150 Opening data channel for directory list.  
  28. 16877 8 501 20 272 4 8 114.
  29. 16877 29 501 20 986 4 8 114..
  30. 33188 1 501 20       6148 3 28 114 .DS_Store  
  31. 16877 4 501 20        136 2 27 114 css  
  32. 33279 1 501 20  129639543 6 14 113 haha.pdf  
  33. 16877 11 501 20        374 2 27 114 images  
  34. 33261 1 501 20      11930 3 9 114 index.html  
  35. 16877 6 501 20        204 2 28 114 js  
  36. 226 Transfer ok.  
  37. QUIT  
  38. 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

  1. #include <iostream>  
  2. #include “define.h”  
  3. #include “CFtpHandler.h”  
  4. #include <sys/types.h>  
  5. #include <sys/socket.h>  
  6.   
  7. int main(int argc, const char * argv[])  
  8. {  
  9.     int port = 2100;  
  10.     int listenFd = startup(port);  
  11.     //ignore SIGCHLD signal, which created by child process when exit, to avoid zombie process  
  12.     signal(SIGCHLD,SIG_IGN);  
  13.     while (1) {  
  14.         int newFd = accept(listenFd, (struct sockaddr *)NULL, NULL);  
  15.         if (newFd == -1) {  
  16.             //when child process exit, it’ll generate a signal which will cause the parent process accept failed.  
  17.             //If happens, continue.  
  18.             if (errno == EINTR) continue;  
  19.             printf(“accept error: %s(errno: %d)\n”,strerror(errno),errno);  
  20.         }  
  21.         //timeout of recv  
  22. Struct timeval timeout = {3,0};
  23.         setsockopt(newFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,sizeof(struct timeval));  
  24.         int pid = fork();  
  25.         //fork error  
  26.         if (pid < 0) {  
  27.             printf(“fork error: %s(errno: %d)\n”,strerror(errno),errno);  
  28.         }  
  29.         //child process  
  30.         else if (pid == 0) {  
  31.             //close useless socket  
  32.             close(listenFd);  
  33.             send(newFd, TS_FTP_STATUS_READY, strlen(TS_FTP_STATUS_READY), 0);  
  34.             CFtpHandler handler(newFd);  
  35.             int freeTime = 0;  
  36.             while (1) {  
  37.                 char buff[256];  
  38.                 int len = (int)recv(newFd, buff, sizeof(buff), 0);  
  39.                 //connection interruption  
  40.                 if (len == 0) break;  
  41.                 //recv timeout return -1  
  42.                 if (len < 0) {  
  43.                     freeTime += 3;  
  44.                     //max waiting time exceed  
  45.                     if (freeTime >= 30) {  
  46.                         break;  
  47.                     }else {  
  48.                         continue;  
  49.                     }  
  50.                 }  
  51.                 buff[len] = ‘\0’;  
  52.                 //reset free time  
  53.                 freeTime = 0;  
  54.                 if (handler.handleRequest(buff)) {  
  55.                     break;  
  56.                 }  
  57.             }  
  58.             close(newFd);  
  59.             std::cout<<“exit”<<std::endl;  
  60.             exit(0);  
  61.         }  
  62.         //parent process  
  63.         else {  
  64.             //close useless socket  
  65.             close(newFd);  
  66.         }  
  67.     }  
  68.     close(listenFd);  
  69.     return 0;  
  70. }  

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

  1. //handle client request  
  2. bool CFtpHandler::handleRequest(char *buff) {  
  3.     stringstream recvStream;  
  4.     recvStream<<buff;  
  5.       
  6.     cout<<buff;  
  7.     string command;  
  8.     recvStream>>command;  
  9.       
  10.     bool isClose = false;  
  11.     string msg;  
  12.     //username  
  13.     if (command == COMMAND_USER) {  
  14.         recvStream>>username;  
  15.         msg = TS_FTP_STATUS_PWD_REQ(username);  
  16.     }  
  17.     //password  
  18.     else if (command == COMMAND_PASS) {  
  19.         recvStream>>password;  
  20.         if (username == “zhaoxy” && password == “123”) {  
  21.             msg = TS_FTP_STATUS_LOG_IN(username);  
  22.         }else {  
  23.             msg = TS_FTP_STATUS_PWD_ERROR;  
  24.         }  
  25.     }  
  26.     //quit  
  27.     else if (command == COMMAND_QUIT) {  
  28.         msg = TS_FTP_STATUS_BYE;  
  29.         isClose = true;  
  30.     }  
  31.     //system type  
  32.     else if (command == COMMAND_SYST) {  
  33.         msg = TS_FTP_STATUS_SYSTEM_TYPE;  
  34.     }  
  35.     //current directory  
  36.     else if (command == COMMAND_PWD) {  
  37.         msg = TS_FTP_STATUS_CUR_DIR(currentPath);  
  38.     }  
  39.     //transmit type  
  40.     else if (command == COMMAND_TYPE) {  
  41.         recvStream>>type;  
  42.         msg = TS_FTP_STATUS_TRAN_TYPE(type);  
  43.     }  
  44.     //passive mode  
  45.     else if (command == COMMAND_PASSIVE) {  
  46.         int port = 0;  
  47.         if (m_dataFd) {  
  48.             close(m_dataFd);  
  49.         }  
  50.         m_dataFd = startup(port);  
  51.           
  52.         stringstream stream;  
  53.         stream<<TS_FTP_STATUS_PASV<<port/256<<“,”<<port%256<<“)”;  
  54.         msg = stream.str();  
  55.           
  56.         //active passive mode  
  57.         m_isPassive = true;  
  58.     }  
  59.     //active mode  
  60.     else if (command == COMMAND_PORT) {  
  61.         string ipStr;  
  62.         recvStream>>ipStr;  
  63.           
  64.         char ipC[32];  
  65.         strcpy(ipC, ipStr.c_str());  
  66.         char *ext = strtok(ipC, “,”);  
  67.         m_clientPort = 0; m_clientIp = 0;  
  68.         m_clientIp = atoi(ext);  
  69.         int count = 0;  
  70.         //convert string to ip address and port number  
  71.         //be careful, the ip should be network endianness  
  72.         while (1) {  
  73.             if ((ext = strtok(NULL, “,”))==NULL) {  
  74.                 break;  
  75.             }  
  76.             switch (++count) {  
  77.                 case 1:  
  78.                 case 2:  
  79.                 case 3:  
  80.                     m_clientIp |= atoi(ext)<<(count*8);  
  81.                     break;  
  82.                 case 4:  
  83.                     m_clientPort += atoi(ext)*256;  
  84.                     break;  
  85.                 case 5:  
  86.                     m_clientPort += atoi(ext);  
  87.                     break;  
  88.                 default:  
  89.                     break;  
  90.             }  
  91.         }  
  92.         msg = TS_FTP_STATUS_PORT_SUCCESS;  
  93.     }  
  94.     //file size  
  95.     else if (command == COMMAND_SIZE) {  
  96.         recvStream>>fileName;  
  97.         string filePath = ROOT_PATH+currentPath+fileName;  
  98.         long fileSize = filesize(filePath.c_str());  
  99.         if (fileSize) {  
  100.             stringstream stream;  
  101.             stream<<TS_FTP_STATUS_FILE_SIZE<<fileSize;  
  102.             msg = stream.str();  
  103.         }else {  
  104.             msg = TS_FTP_STATUS_FILE_NOT_FOUND;  
  105.         }  
  106.     }  
  107.     //change directory  
  108.     else if (command == COMMAND_CWD) {  
  109.         string tmpPath;  
  110.         recvStream>>tmpPath;  
  111.         string dirPath = ROOT_PATH+tmpPath;  
  112.         if (isDirectory(dirPath.c_str())) {  
  113.             currentPath = tmpPath;  
  114.             msg = TS_FTP_STATUS_CWD_SUCCESS(currentPath);  
  115.         }else {  
  116.             msg = TS_FTP_STATUS_CWD_FAILED(currentPath);  
  117.         }  
  118.     }  
  119.     //show file list  
  120.     else if (command == COMMAND_LIST || command == COMMAND_MLSD) {  
  121.         string param;  
  122.         recvStream>>param;  
  123.           
  124.         msg = TS_FTP_STATUS_OPEN_DATA_CHANNEL;  
  125.         sendResponse(m_connFd, msg);  
  126.         int newFd = getDataSocket();  
  127.         //get files in directory  
  128.         string dirPath = ROOT_PATH+currentPath;  
  129.         DIR *dir = opendir(dirPath.c_str());  
  130.         struct dirent *ent;  
  131.         struct stat s;  
  132.         stringstream stream;  
  133. while ((ent = readdir(dir))! =NULL) {
  134.             string filePath = dirPath + ent->d_name;  
  135.             stat(filePath.c_str(), &s);  
  136.             struct tm tm = *gmtime(&s.st_mtime);  
  137.             //list with -l param  
  138.             if (param == “-l”) {  
  139.                 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;  
  140.             }else {  
  141.                 stream<<ent->d_name<<endl;  
  142.             }  
  143.         }  
  144.         closedir(dir);  
  145.         //send file info  
  146.         string fileInfo = stream.str();  
  147.         cout<<fileInfo;  
  148.         send(newFd, fileInfo.c_str(), fileInfo.size(), 0);  
  149.         //close client  
  150.         close(newFd);  
  151.         //send transfer ok  
  152.         msg = TS_FTP_STATUS_TRANSFER_OK;  
  153.     }  
  154.     //send file  
  155.     else if (command == COMMAND_RETRIEVE) {  
  156.         recvStream>>fileName;  
  157.         msg = TS_FTP_STATUS_TRANSFER_START(fileName);  
  158.         sendResponse(m_connFd, msg);  
  159.         int newFd = getDataSocket();  
  160.         //send file  
  161.         std::ifstream file(ROOT_PATH+currentPath+fileName);  
  162.         file.seekg(0, std::ifstream::beg);  
  163.         while(file.tellg() != -1)  
  164.         {  
  165.             char *p = new char[1024];  
  166.             bzero(p, 1024);  
  167.             file.read(p, 1024);  
  168.             int n = (int)send(newFd, p, 1024, 0);  
  169.             if (n < 0) {  
  170.                 cout<<“ERROR writing to socket”<<endl;  
  171.                 break;  
  172.             }  
  173.             delete p;  
  174.         }  
  175.         file.close();  
  176.         //close client  
  177.         close(newFd);  
  178.         //send transfer ok  
  179.         msg = TS_FTP_STATUS_FILE_SENT;  
  180.     }  
  181.     //receive file  
  182.     else if (command == COMMAND_STORE) {  
  183.         recvStream>>fileName;  
  184.         msg = TS_FTP_STATUS_UPLOAD_START;  
  185.         sendResponse(m_connFd, msg);  
  186.         int newFd = getDataSocket();  
  187.         //receive file  
  188.         ofstream file;  
  189.         file.open(ROOT_PATH+currentPath+fileName, ios::out | ios::binary);  
  190.         char buff[1024];  
  191.         while (1) {  
  192.             int n = (int)recv(newFd, buff, sizeof(buff), 0);  
  193.             if (n<=0) break;  
  194.             file.write(buff, n);  
  195.         }  
  196.         file.close();  
  197.         //close client  
  198.         close(newFd);  
  199.         //send transfer ok  
  200.         msg = TS_FTP_STATUS_FILE_RECEIVE;  
  201.     }  
  202.     //get support command  
  203.     else if (command == COMMAND_FEAT) {  
  204.         stringstream stream;  
  205.         stream<<“211-Extension supported”<<endl;  
  206.         stream<<COMMAND_SIZE<<endl;  
  207.         stream<<“211 End”<<endl;;  
  208.         msg = stream.str();  
  209.     }  
  210.     //get parent directory  
  211.     else if (command == COMMAND_CDUP) {  
  212. if (currentPath ! {= “/”)
  213.             char path[256];  
  214.             strcpy(path, currentPath.c_str());  
  215.             char *ext = strtok(path, “/”);  
  216.             char *lastExt = ext;  
  217. while (ext! =NULL) {
  218.                 ext = strtok(NULL, “/”);  
  219.                 if (ext) lastExt = ext;  
  220.             }  
  221.             currentPath = currentPath.substr(0, currentPath.length()-strlen(lastExt)-1);  
  222.         }  
  223.         msg = TS_FTP_STATUS_CDUP(currentPath);  
  224.     }  
  225.     //delete file  
  226.     else if (command == COMMAND_DELETE) {  
  227.         recvStream>>fileName;  
  228.         //delete file  
  229.         if (remove((ROOT_PATH+currentPath+fileName).c_str()) == 0) {  
  230.             msg = TS_FTP_STATUS_DELETE;  
  231.         }else {  
  232.             printf(“delete error: %s(errno: %d)\n”,strerror(errno),errno);  
  233.             msg = TS_FTP_STATUS_DELETE_FAILED;  
  234.         }  
  235.     }  
  236.     //other  
  237.     else if (command == COMMAND_NOOP || command == COMMAND_OPTS){  
  238.         msg = TS_FTP_STATUS_OK;  
  239.     }  
  240.       
  241.     sendResponse(m_connFd, msg);  
  242.     return isClose;  
  243. }  

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!

\