1.FTP Server 开发
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
2.FTP Server 智能读取参数
学习模块optparse
其实就是在客户端在登陆过程中需要输入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文件储存用户登陆的账户信息
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]
View Code
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)
*****************未完待续********************