python自动化开发学习----ftp开发
1.FTP Server 开发
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
2.FTP Server 智能读取参数
学习模块optparse
http://blog.csdn.net/marksinoberg/article/details/51842197
其实就是在客户端在登陆过程中需要输入IP地址和端口是对应的参数读取
import optparse class FTPClient(object): def __init__(self): parser = optparse.OptionParser() #为类添加参数 parser.add_option("-s","--server",dest="server",help="ftp server ip_address") parser.add_option("-P","--port",type="int",dest="port",help="ftp server port") parser.add_option("-u","--username",dest="username",help="username") parser.add_option("-p","--password",dest="password",help="password") self.options,self.args = parser.parse_args() #实例化 #dest:用于保存临时变量,其值可以作为options的属性进行访问。存储的内容就是如-f,-n 等紧挨着的那个参数内容 #type:指的是对应于参数,如-f,-n等的接下来的那个参数的数据类型,有string,int,float等等 #help:提供用户友好的帮助信息,一般可以用来解释本add_option方法的功能阐述。
3.用户远程验证
def authenticate(self): """用户验证""" if self.options.username: print(self.options.username,self.options.password) return self.get_auth_result(self.options.username, self.options.password) else: retry_count =0 while retry_count<3: username=input("username").strip() password=input("password").strip() self.get_auth_result(username,password) def get_auth_result(self,user,password):#与服务端的远程交互 data ={ "action":"auth", "username":user, "password":password } self.sock.send(json.dumps(data).encode()) self.get_response()
4.设计规范的状态码
STATUS_CODE = { 100:"Invalid cmd format", 200:"Invalid cmd" } def send_response(self,status_code,data=None): """向客户端返回数据""" response = {"status_code":status_code,"status_msg":STATUS_CODE[status_code]} if data: response.update(data) self.request.send(json.dumps(response).encode())
5.用户账户信息储存和解析
使用模块configparser
创建文件accounts.cfg文件储存用户登陆的账户信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def _auth(self,*args,**kwargs): 2 data = args[0] 3 if data.get("username") is None or data.get("password") is None: 4 self.send_response(300) 5 6 user = self.authenticate(data.get("username"),data.get("password")) 7 if user is None: 8 self.send_response(301) 9 else: 10 print("passed authentication",user) 11 12 def authenticate(self,username,password): 13 """验证用户的合法性,合法就返回用户数据""" 14 config = configparser.ConfigParser() 15 config.read(settings.ACCOUNT_FILE) 16 if username in config.sections(): 17 _password = config[username]["Password"] 18 if _password == password: 19 print("pass auth..",username) 20 return config[username]
6.文件的下载
client端
def _get(self,cmd_list): print("_get",cmd_list) if len(cmd_list) == 1: print("no filename folows...") return data_header = { "action":"get", "filename":cmd_list[1] } self.sock.send(json.dumps(data_header).encode()) response = self.get_response() print(response) if response["status_code"] == 257: base_filename = cmd_list[1].split("/")[-1] recevied_size = 0 file_obj = open(base_filename,"wb") while recevied_size<response["file_size"]: data = self.sock.recv(1024) recevied_size +=len(data) file_obj.write(data) else: print("----file recv done-----") file_obj.close()
server端
def _get(self,*args,**kwargs): data = args[0] if data.get("filename") is None: self.send_response(255) user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"]) file_abs_path = "%s/%s" %(user_home_dir,data.get("filename")) print(user_home_dir) print(file_abs_path) if os.path.isfile(file_abs_path): file_obj = open(file_abs_path,"rb") file_size = os.path.getsize(file_abs_path) self.send_response(257,data = {"file_size":file_size}) for line in file_obj: self.request.send(line) else: file_obj.close() print("---file send done---") else: self.send_response(256)
文件上传
client端
def _put(self,cmd_list): print("_put",cmd_list) if len(cmd_list) > 1: file_name = cmd_list[1] if os.path.isfile(file_name): file_size = os.path.getsize(cmd_list[1]) print(file_size) data_header = { "action":"put", "filename":file_name, "filesize":file_size } self.sock.send(json.dumps(data_header).encode()) self.get_response() f = open(file_name,"rb") for line in f: self.sock.send(line) else: print("---%s is send done---"%file_name) f.close() else: print("%s is not exits"%file_name)
server端
def _put(self,*args,**kwargs): data = args[0] file_name = data["filename"] file_size = data["filesize"] user_home_dir = "%s/%s" % (setting.USER_HOME, self.user["Username"]) file_abs_path = "%s/%s" % (user_home_dir, file_name) print(user_home_dir) print(file_abs_path) if os.path.isfile(file_abs_path): f= open(file_abs_path,"wb") else: f= open(file_abs_path,"wb") self.send_response(258) recevied_size = 0 while recevied_size<file_size: data = self.request.recv(1024) f.write(data) recevied_size+=len(data) else: print("---%s is recv done---"%file_name)
7.文件的一致性(MD5验证)
client端
def __md5_required(self,cmd_list): #检测命令是否需要md5验证 if "--md5" in cmd_list: return True def _get(self,cmd_list):#文件下载 print("_get",cmd_list) if len(cmd_list) == 1: print("no filename folows...") return data_header = { "action":"get", "filename":cmd_list[1] } if self.__md5_required(cmd_list): data_header["md5"] = True self.sock.send(json.dumps(data_header).encode()) response = self.get_response() print(response) if response["status_code"] == 257: base_filename = cmd_list[1].split("/")[-1] recevied_size = 0 file_obj = open(base_filename,"wb") if self.__md5_required(cmd_list): md5_obj = hashlib.md5() while recevied_size<response["file_size"]: data = self.sock.recv(1024) recevied_size +=len(data) file_obj.write(data) md5_obj.update(data) else: print("----file recv done-----") file_obj.close() md5_val = md5_obj.hexdigest() md5_server = self.get_response() if md5_server["status_code"] == 259: if md5_server["md5"] == md5_val: print("%s 文件一致性验证成功"%base_filename) print(md5_val,md5_server) else: while recevied_size<response["file_size"]: data = self.sock.recv(1024) recevied_size +=len(data) file_obj.write(data) else: print("----file recv done-----") file_obj.close()
server端
def _get(self,*args,**kwargs): #文件下载 data = args[0] if data.get("filename") is None: self.send_response(255) user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"]) file_abs_path = "%s/%s" %(user_home_dir,data.get("filename")) print(user_home_dir) print(file_abs_path) if os.path.isfile(file_abs_path): file_obj = open(file_abs_path,"rb") file_size = os.path.getsize(file_abs_path) self.send_response(257,data = {"file_size":file_size}) if data.get("md5"): md5_obj = hashlib.md5() for line in file_obj: self.request.send(line) md5_obj.update(line) else: file_obj.close() md5_val = md5_obj.hexdigest() self.send_response(259,{"md5":md5_val}) print("---file send done---") else: for line in file_obj: self.request.send(line) else: file_obj.close() print("---file send done---") else: self.send_response(256)
8.粘包问题
发送端可以是1k,1k的发送数据而接受端的应用程序可以2k,2k的提取数据,当然也有可能是3k或者多k提取数据,也就是说,应用程序是不可见的,因此TCP协议是面来那个流的协议,这也是容易出现粘包的原因而UDP是面向笑死的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任一字节的数据,这一点和TCP是很同的。怎样定义消息呢?认为对方一次性write/send的数据为一个消息,需要命的是当对方send一条信息的时候,无论鼎城怎么样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
例如基于TCP的套接字客户端往服务器端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看来更笨不知道文件的字节流从何初开始,在何处结束。
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多数据后才发上一个TCP段。如连续几次下需要send的数据都很少,通常TCP会根据优化算法把 这些数据合成一个TCP段后 一次发送出去,这样接收方就收到了粘包数据
以下两种情况会出现粘包的现象:
1.发送端需要等本机的缓冲区满了以后才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合在一个起,产生粘包)
2.接收端不及时接收缓冲区的包,造成多个包接受(客户端发送一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据 ,就产生粘包)
解决方法:
1.添加睡眠时间time.sleep(0.5)
2.在2次发送之间在进行一次接受
client端
def _get(self,cmd_list):#文件下载 print("_get",cmd_list) if len(cmd_list) == 1: print("no filename folows...") return data_header = { "action":"get", "filename":cmd_list[1] } if self.__md5_required(cmd_list): data_header["md5"] = True self.sock.send(json.dumps(data_header).encode()) response = self.get_response() print(response) if response["status_code"] == 257: self.sock.send(b"1") #给服务器端发送一次数据防止粘包 base_filename = cmd_list[1].split("/")[-1] recevied_size = 0 file_obj = open(base_filename,"wb") if self.__md5_required(cmd_list): md5_obj = hashlib.md5() while recevied_size<response["file_size"]: data = self.sock.recv(1024) recevied_size +=len(data) file_obj.write(data) md5_obj.update(data) else: print("----file recv done-----") file_obj.close() md5_val = md5_obj.hexdigest() md5_server = self.get_response() if md5_server["status_code"] == 259: if md5_server["md5"] == md5_val: print("%s 文件一致性验证成功"%base_filename) print(md5_val,md5_server) else: while recevied_size<response["file_size"]: data = self.sock.recv(1024) recevied_size +=len(data) file_obj.write(data) else: print("----file recv done-----") file_obj.close()
server端
def _get(self,*args,**kwargs): #文件下载 data = args[0] if data.get("filename") is None: self.send_response(255) user_home_dir = "%s/%s" %(setting.USER_HOME,self.user["Username"]) file_abs_path = "%s/%s" %(user_home_dir,data.get("filename")) print(user_home_dir) print(file_abs_path) if os.path.isfile(file_abs_path): file_obj = open(file_abs_path,"rb") file_size = os.path.getsize(file_abs_path) self.send_response(257,data = {"file_size":file_size}) self.request.recv(1)#防止粘包做一次接受 if data.get("md5"): md5_obj = hashlib.md5() for line in file_obj: self.request.send(line) md5_obj.update(line) else: file_obj.close() md5_val = md5_obj.hexdigest() self.send_response(259,{"md5":md5_val}) print("---file send done---") else: for line in file_obj: self.request.send(line) else: file_obj.close() print("---file send done---") else: self.send_response(256)
9.进度条
一个简单的进度条
import time for i in range(10): print("#",end="",flush=True) time.sleep(0.5)
*****************未完待续********************