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") ...