python implements simple FTP

Posted by rushdot on Thu, 26 Dec 2019 23:40:34 +0100

1. Development environment

server side: centos 7 python-3.6.2

Client: Windows 7 python-3.6.2 pycharm-2018

Program purpose: 1. Learn to use socketserver to achieve concurrent processing of multiple clients.

2. Learn to use struct to solve TCP sticky packages.

2. Programming

(I am a novice bird, for development specifications, interface design is completely unknown, is completely at will, entertainment.Blogging is mainly about recording your own learning. If there are any deficiencies, please forgive me.)

1. server side

1.1 The directory structure is as follows:

Introduction to 1.2 Catalog:

FTP_SERVER: Program Home Directory

app: The main logical directory of the program, which has four modules:

FTPserver.py:FTP Server Start Entry.

login.py: Authentication registration module for user registration, login authentication.

dataAnalysis.py: A command parsing module responsible for parsing and executing client-side commands.

FileOpertion.py: Read and write files.Data sending, data receiving.

Db: Stores the user_pwd.db file for user information (username, password, total FTP directory space, used space, etc.)

lib: store public data.

Inheritance of Classes in 1.3 Modules

1.4 Execution Process

1.4.1 Program startup file FTPserver.py, program startup into a listening state.The core code is as follows:

class MyFtpServer(socketserver.BaseRequestHandler):
    
    def handle(self):  # Rewrite handle Method,Handle socket request
        print(f"Connection from{self.client_address}Client")
        commom_obj = Commom()
        data_analy = DataAnalysis()
        login_obj = Login()
        while 1:
            # Execute user options: 1, Log on to the system 2, Register an account.And returns a result
            status_id = login_obj.run_client_choice(self.request, commom_obj)
            if status_id == "01":  # Landing Success
                if not self.run_ftp_server(data_analy,commom_obj):  # implement ftpserver Main function
                    break
            elif int(status_id) == -1: # client Disconnected
                break
        print(f"Client{self.client_address}Disconnected")

    def run_ftp_server(self,data_analy,commom_obj):
        """"
        //After successful login, receive commands from clients and process them
        :param data_analy:Object responsible for parsing, executing client commands
        :param commom_obj:Data objects required for program execution
        :return Return false Disconnected on behalf of client
        """
        while True:
            try:
                cmd_len_pack = self.request.recv(4)  
                cmd_len = struct.unpack('i',cmd_len_pack)[0] # Get command length to prevent glue  
            except Exception:
                break
            recv_data = self.request.recv(cmd_len).decode('utf-8')  # Receive client data
            if recv_data.upper() == "Q":  # The client offered to disconnect
                break
            # Resolve, process client commands
            data_analy.syntax_analysis(recv_data, self.request, commom_obj)
        return False

if __name__ == '__main__':
    print('Function FTP service')
    ip_port = ('192.168.10.10',9000)
    # Creating concurrent server-side objects
    server = socketserver.ThreadingTCPServer(ip_port, MyFtpServer)
    # Open Service
    server.serve_forever()

1.4.2 When the server enters the listening state, the client initiates a connection request. After the server receives the connection request, it waits for the client to send a single status code. 1 indicates the request to log on to the FTP server, 2 indicates the client wants to register the user. The registered user needs a manual feedback status code 1 from the server to register.Processing user login, the core code of login.py registration module is as follows:

class Login(FileOperation):
    """
    //Log on to the registration class.Primarily responsible for user login authentication and user registration.
    """
    def run_client_choice(self,socket_obj,commom):
        """
        //Get requests from clients, 1 for logon and 2 for registered users
        :param socket_obj: socket object
        :param commom: ftpserver Data Objects Required at Runtime
        :return:
        """
        recv_choice = socket_obj.recv(1).decode("utf-8")  # Get user options: 1 is login, 2 is registered user
        if recv_choice == "1":  # client Request landing
            return self.login_authen(socket_obj,commom)
        elif recv_choice == "2":  # client Request Account Registration
            return self.register_user(socket_obj,commom)
        else:
            return -1  # client Disconnected

    # User Logon Authentication
    def login_authen(self,socket_obj,commom):
        """
        //Client Logon Authentication
        :param socket_obj:  socket object
        :param commom:  ftpserver Data Objects Required at Runtime
        :return:Return 1 for successful landing
        """
        # Receive client User name, password from
        recv_userPwd = self.recv_data(socket_obj).decode("utf-8").split("|")  
        # Verify username password
        check_ret = self.check_user_pwd(recv_userPwd, socket_obj,commom)
        if check_ret:  # User name password is correct
            self.check_user_home_dir(commom,recv_userPwd[0]) # Detect user home directories
            return commom.status_info["login_success"]
        else:
            return commom.status_info["login_fail"]

   ...

    # Registered User
    def register_user(self,socket_obj,commom):
        """
        :param socket_obj:
        :param commom:
        :return: Returns the result of whether registration is allowed, 1 allows client registration, 2 rejects client registration
        """
        while True:
            choice_id = input("Please enter a response code: 1 is to allow registration, 2 is not to allow registration:")
            if choice_id.isdigit() and 3 > int(choice_id) > 0:
                socket_obj.send(choice_id.encode("utf-8"))  # Notify the client and process the results
                if choice_id == "1":  # Registered User
                    return self.client_register(socket_obj, commom)
                return choice_id
            else:
                print("The information you entered is incorrect. Please re-enter it.")

   ...

After the 1.4.3 client successfully logs in, the server waits for the command from the client to be received. The command is parsed and executed by the dataAnalysis.py module with the following core code:

class DataAnalysis(FileOperation):
    """
    //Data analysis processing class, mainly responsible for resolving instructions sent by client.
    """
    def syntax_analysis(self,recv_data, socket_obj, commom):
        """
        //Responsible for parsing data from clients.
        :param recv_data:Received client user data
        :param socket_obj:socket object
        :param commom:data object
        :return:
        """
        clientData = recv_data.split(" ")
        if hasattr(self,clientData[0]):  # Determine whether the object method exists
            get_fun = getattr(self,clientData[0])#Get Object Method
            get_fun(clientData,socket_obj,commom)  # Run Object Method
        else:
            pass
    ...

After executing the client command, continue to wait for the command from the client to be received, such a cycle...

2. Client

2.1 The directory structure is as follows:

2.2 Catalog Introduction:

client: the program home directory.

bin: Program entry, the program startup file main.py is used to establish socket connection, then run_ftp_client method under FTPclient.py module is called to run the program.

app: the main logic of the program, there are four modules in the directory as follows:

FTPclient.py: FTP client, executes user instructions according to user options.

login.py: Authentication registration module for user registration, login authentication.

dataAnalysis.py: A command parsing module that parses user-entered commands and sends them to the server for results.

FileOpertion.py: Read and write files.

lib: store public data with two files:

commom.py: The main storage is public variables.

help.txt: The help document is stored and called when the user executes the help command.

Inheritance of Classes in 2.3 Module

2.4 Execution Process

2.4.1 program entry main.py, after starting, will establish a connection with the FTP service side, and after successful connection with the service side, will call the run_ftp_client method under the FTP client.py module to perform user functions.The core code is as follows:

socket_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket_obj.connect(("192.168.10.10",9000))

client_obj = Client()
client_obj.run_ftp_client(socket_obj)  # Receive user input options to perform corresponding functions

The run_ftp_client method in the 2.4.2 FTPclient.py module prints the menu and waits for the user to enter options to perform the appropriate functions. The core code is as follows:

class Client(Login,DataAnalysis):

    def run_ftp_client(self,socket_obj):
        """
        //Run user input options: 1, logon 2, registered account
        :return:
        """
        while True:
            self.login_menu()  # Printing System Menu
            choice_id = self.get_user_choice()  # Options for getting user input
            if choice_id:
                if self.run_user_choice(choice_id,socket_obj):
                    break
            else:
                print("You entered incorrectly")
    def get_user_choice(self):
        """
        //Options for getting user input
        :return:
        """
        choice_id = input("Please enter options:")
        if choice_id.isdigit() and 4 > int(choice_id) > 0 or choice_id.upper() == "Q":
            return choice_id
        return False
    def run_user_choice(self,choice_id,socket_obj):
        if choice_id == "1":  # Landing System
            socket_obj.send(choice_id.encode("utf-8"))  # Notify the server that it is ready to log on
            if self.run_login(socket_obj) == True:  # Execute landing
                return True
        elif choice_id == "2":  # Registered User
            socket_obj.send(choice_id.encode("utf-8"))  # Request Server, Register User
            self.register_user(socket_obj)  # Perform registration
        elif choice_id.upper() == "Q":  # Exit the program
            socket_obj.send(choice_id.encode("utf-8"))  # Notify the server to quit the program
            socket_obj.close()
            print("Program exited normally")
            return True

    def run_login(self,socket_obj,):
        """
        //Run the login authentication module, if the login successfully executes the main logic of the program, otherwise login again.
        :param socket_obj:
        :return:
        """
        if self.login_authention(socket_obj):
            while True:
                send_data = input(">>>").strip(" ")  # Get send data (commands executed by the user)
                if send_data.upper() == "Q":  # Exit the program normally
                    socket_obj.send(send_data.encode("utf-8"))  # Notify service area to disconnect
                    socket_obj.close()
                    print("Program exited normally")
                    return True
                if self.syntax_analysis(send_data, socket_obj):  # Parse user data and process data
                    print("Abnormal Exit")
                    return True
        return False

    def login_menu(self):
        print("-"*41)
        print("          Welcome to Mini FTPv1.0")
        print("-"*41)
        print("1,Landing System")
        print("2,User registration")
        print("Q,Exit the program")

The 2.4.3 login.py module is mainly used to handle the functions of registration and login, and the core code is as follows:

class Login(Commom):
    def login_authention(self,socket_obj):
        """
        //Logon Authentication
        :param socket_obj:socket object
        :return:
        """
        user_pwd = self.get_user_pwd()  # Get User Name Password
        self.send_data(socket_obj,user_pwd)  # Send username and password to server
        recv_status = socket_obj.recv(2).decode("utf-8")  # Waiting to receive status code
        print(self.status_info[recv_status])  # Print the result corresponding to the status code
        if self.status_info[recv_status] == 'Login Successful':
            return True
        return False
    ...

    def register_user(self,socket_obj):
        """
        //Wait for feedback from the server on whether to allow registered users.
        :param socket_obj:
        :return:
        """
        print("Please wait for the server to respond.....")
        recv_status = socket_obj.recv(1).decode("utf-8")
        if recv_status == "1":  # Server agrees to apply for account
            user_pwd = self.get_regist_user_pwd()  # Get registered user name and password
            if user_pwd:
                self.send_data(socket_obj,user_pwd)
                result = socket_obj.recv(2).decode("utf-8")
                print(self.status_info[result])
            else:
                print("Error in username password")
        else:  # Client rejects request for account
            print("The server rejected your request for an account. Please contact your administrator.")
        return False
    ...

2.4.4 After a successful login, the user will wait for a command to be received. The dataAnalysis.py module is responsible for resolving the command entered by the user, sending it to the FTP server, and then receiving feedback from the server.The core code is as follows:

class DataAnalysis(FileOperation):

    def syntax_analysis(self,cmd,socket_obj):
        """
        //Resolve commands entered by the user.
        :param cmd:Commands executed by the user, such as: put Uploaded Files
        :param socket_obj:socket Object sends and receives data
        :return:
        """
        cmd_split = cmd.split(" ")  # Split a string command into a list to verify that the command exists
        if hasattr(self,cmd_split[0]):
            run_fun = getattr(self,cmd_split[0])
            run_fun(cmd_split,socket_obj)
        else:
            print("Invalid command")
    ...

3. Functional Demonstration

Topics: Python socket ftp CentOS