#!/usr/bin/env python#__author__:ZhaoHong#-*- coding: utf-8 -*-
importsocketserverimporttime,os,sys,re,hashlibfrom subprocess importPIPE,Popenfrom multiSocketFTP.multiFTPServer.set importsetting
_FILE_SLIM= (100*1024*1024) #100MB
classFTPServerHandle(socketserver.BaseRequestHandler):'''It's a class for handle FTPServer.
socketserver is a multthreading base class,'''
defhandle(self):whileTrue:########## -------------------------------------------------------------------------------------------------beginning recive
self.data = self.receiveFromClient(1024) #----------------------------------------------------------------receive
print ("from client : [{}] \nthe server has got data is : [{}]"\
.format(self.client_address[0],self.data))#print gotted message
########## -------------------------------------------------------------------------------------------------have not data
if not self.data:#if data is not sented,we will break loop.
print ("client {} stop to send data.".format(self.client_address[0]))break
########## -------------------------------------------------------------------------------------------------receive data handle here
## cmdHead : judge "handle branch"
##################################
#we will handle the data of recieve,follow the agreement below:
user_input = self.data.split() #use split fuction to make a list for data
cmdHead = user_input[0] #get head about input command from client
if cmdHead == 'welcome': #if head is welcome then show welcome message. it's show at begining
print("welcome to use FTP SERVER...")
self.sendToClient('Welcome to use FTP\n=================') ## ------------------------------------welcome 1
#self.request.sendall(bytes('Welcome to use FTP\n=================','utf-8'))
if cmdHead == 'welcome_show_ok':continue
#---------------------------------------------------------------------------------------------------------- login --
#fisrt we must handle login process,otherwish we dont handle other command from client.
#(user_input list length must be 3)
elif cmdHead == 'login' and len(user_input) == 3:
cmdUserName= user_input[1] #get user name from client
cmdUserPassword = user_input[2] #get user password from client
#****** admin login begin *********
(isLogin,username,directory,quota) = self.userlogin(cmdUserName,cmdUserPassword,setting.userInfo)##----- Login Infomation---
if isLogin:##------------------------------------------------------------------------------------------ enter Login ###
print("{}:login FTP success!".format(cmdUserName))
self.sendToClient('loginSuccess')##-------------------------------------------------------------- login sucess 2
if setting.sysStr == 'Windows':
os.chdir("%s\\%s"%(setting.homeDIR,cmdUserName)) #cd to acount home drectory(windows)
os.system("cd")else:
os.chdir("%s/%s"%(setting.homeDIR,cmdUserName)) #cd to acount home drectory(linux)
os.system("pwd")while True:#--------------------------------------------------------------------------------------{loop begin} FTP cmd handle
#命令格式:cd 目录
#get filename
#put filename
ret = self.receiveFromClient(1024) #------------------------------------------------------------wait [recv] 1
ftpInput = ret.split() #split string to list (separator is spaces)
if ret == '':continue
print("command:%s"%ret)if re.search(r"^cd",ret) and len(ftpInput) >= 2:#----------------------------------------------cd DIR
print(user_input[1])if setting.sysStr == "Windows":if ftpInput[1] == 'home':
Directory= "%s%s"%(setting.homeDIR,username)else:
Directory= "%s%s\\%s"%(setting.homeDIR,username,ftpInput[1])else:if ftpInput[1] == 'home':
Directory= "%s%s"%(setting.homeDIR,username)else:
Directory= "%s%s/%s"%(setting.homeDIR,username,ftpInput[1])#cmd = "cd %s"%Directory
try:
os.chdir(Directory)
self.sendToClient(Directory)except:
self.sendToClient("directory is not found")print("directory is not found")#-----------------------------------------------------------------------------------------------ls,dir
elif (re.search(r"^ls",ret) and len(ftpInput) >= 1) or\
re.search(r"^dir",ret) :
retDIR=os.popen(ret).read()
self.sendToClient(retDIR)#if setting.sysStr == "Windows":
#self.sendToClient(bytes(retDIR,'gbk'))
#else:
#self.sendToClient(bytes(retDIR,'gbk'))
#------------------------------------------------------------------------------------------------get srcFilename drcFilename srcDIR drcDIR
elif re.search(r"^get",ret):#开始 get
print ("starting to get file ...")if setting.sysStr == "Windows":#获取文件名
pathFile = "%s%s\\%s"%(setting.homeDIR,ftpInput[3],ftpInput[1])
saveSizeFile= "%s%s\\%s"%(setting.homeDIR,ftpInput[3],"srcFileSize.dat")
saveBreakFile= "%s%s\\%s"%(setting.homeDIR,ftpInput[3],"breakAt.dat")else:
pathFile= "%s%s/%s"%(setting.homeDIR,ftpInput[3],ftpInput[1])#ftpInput[1]: path,ftpInput[2]:src filename ftpInput[2]: drc filename
saveSizeFile = "%s%s\\%s"%(setting.homeDIR,ftpInput[3],"srcFileSize.dat")
saveBreakFile= "%s%s\\%s"%(setting.homeDIR,ftpInput[3],"breakAt.dat")
self.request.sendall(b"get")#---------------------------------------------------------------send get
msg = self.request.recv(500) #---------------------------------------------------------------recv (获取文件客户端是否已经下载这个文件)
msg = str(msg,'utf-8')
msg1= msg.split('|')if msg1[0] == 'notHaveFile':#如果没有下载,就从头开始下载
md5Value = self.file_md5(pathFile)#获取源文件的MD5值
fileSize = self.writeFileSize(pathFile,saveSizeFile)#获取源文件的总计数
self.getFile(pathFile,saveBreakFile,fileSize,md5Value) #----------------------------------------------------------function getfile
elif msg1[0] == 'MD5':#否则,开始断点续传
fromClientFileMD5 = msg1[1]#获取客户端的MD5值
md5Value = self.file_md5(pathFile)#获取源文件的MD5值
fileSize= self.writeFileSize(pathFile,saveSizeFile)#获取源文件的总计数
if md5Value == fromClientFileMD5:#对比服务端源文件和客户端目标文件的MD5值,相等
print("文件已经成功下载")
self.request.sendall(b'fileIsdownload')#------------------------------------------send 文件已经下载 fileIsdownload
else:#否则,进入断点续传
breakat = self.getBreakAT(pathFile)#获取断点位置数字
self.getBreakFile(pathFile,saveBreakFile,breakat,fileSize,md5Value)#-----------------进入断点续传 function
#-----------------------------------------------------------------------------------------------put srcFilename drcFilename srcDIR drcDIR
elif re.search(r"^put",ret):if setting.sysStr == "Windows":
fileName= "{}{}\\{}".format(setting.homeDIR,ftpInput[4],ftpInput[2])else:
fileName= "{}{}/{}".format(setting.homeDIR,ftpInput[4],ftpInput[2])#fileName = "{}/{}".format(setting.homeDIR,head[1])
print(fileName)
t=os.path.isfile(fileName)ift:print(fileName)
fileMD5=self.file_md5(fileName)
s= "MD5|{}".format(fileMD5)
self.request.recv(200)#--------------------------------------------------------------------------------recv b'get'
self.request.sendall(bytes(s,'utf-8'))#---------------------------------------------------------------send MD5
lineSize =0
self.putFile(self.request,fileName)else:
self.request.recv(200)#--------------------------------------------------------------------------------recv b'get'---(1)
self.request.sendall(b'notHaveFile|a')#--------------------------------------------------------send (notHaveFile)
self.putFile(self.request,fileName)elif re.search(r"^quitFTP",ret):#--------------------------------------------------------------quit
self.request.sendall(b'OK')else:#------------------------------------------------------------------------------------------other handle
retForCmd = Popen(ret,shell=True,stdout=PIPE).stdout.read()#self.request.sendall(retForCmd)print(retForCmd)else:
self.sendToClient('')elif cmdHead == "ssh":
self.sshHandle()else:pass
defreceiveFromClient(self,length):
self.data= self.request.recv(length).strip()#waiting for receiv message,it's can get 1024 character one time
self.data = str(self.data,'utf-8')returnself.datadefsendToClient(self,str):
self.request.sendall(bytes(str,'utf-8'))defsshHandle(self):whileTrue:
self.sendToClient("\nssh ready to receive..")#time.sleep(1)
cmd = self.receiveFromClient(1024)if len(cmd) == 0:continue
if cmd == 'q':print("quit ssh...")breakretForCmd= Popen(cmd,shell=True,stdout=PIPE).stdout.read()#retForCmd = self.PopenHandle(cmd)
#print("cmd ret:{}".format(retForCmd))
if len(retForCmd) ==0:
retForCmd= b"no data to return!"len1=str(len(retForCmd))
lenghSend="lineSize:%s"%(len1)
self.sendToClient(lenghSend)
time.sleep(1)print("send data size is : {}".format(lenghSend))
clientRet= self.receiveFromClient(100)if clientRet == "readyToGo":#retForCmd = "%s"%retForCmd
#retForCmd = retForCmd.decode()
self.request.sendall(retForCmd)#.requestsendall(retForCmd)
print(retForCmd)defPopenHandle(self,str):
retResult= Popen(str,shell=True,stdout=PIPE).stdout.read()returnretResultdefuserlogin(self,username,password,msgDict):'''用户登陆
:param username:账户名
:param password: 口令
:param msgDict: 账户字典 {username:[口令,磁盘目录名,磁盘配额(MB)]}
:return: isLogin,username,directory,quota'''
#print(AdminMsgList[0][0],AdminMsgList[1][0],AdminMsgList)
isLogin =Falsefor k,v inmsgDict.items():if k == username and msgDict[k][0] ==password:
isLogin=True
directory= msgDict[k][1]
quota= msgDict[k][2]returnisLogin,username,directory,quotaelse:return isLogin,'','',''
defcmd(self,cmdStr):'''接收一个ssh命令
:param cmdStr: 命令
:return: 命令之后的结果'''ret= Popen(cmdStr,shell=True,stdout =PIPE).stdout.read()print(ret)returnretdefputFile(self,obj,fileName):'''写文件:从客户端传来的文件
:return:'''srcfileMD5= ''drcfileMD5= ''
whileTrue:
f= open(fileName,'ab')#--------------------------以累加的形式ab设置文件句柄
#print("1")
long = obj.recv(100)#-------------------------------------------------------------------------recv 所传的长度,用于写入
print(long)
slong= str(long,'utf8')if slong == 'alldone':#---------------------------------传输完毕的处理
drcfileMD5 = self.file_md5(fileName)#上传完成的文件MD5值
if srcfileMD5 == drcfileMD5:#与客户端文件的MD5比较
print("file download is done !")#相等,则下载成功
else:print('file download is failure !')#不等,下载失败
breakilong=int(slong)
obj.sendall(b'ok')#------------------------------------------------------------------------send 回应所传来的信息长度
data = obj.recv(ilong) #--------------------------------------------------------------------recv 以传来的长度设置接收长度,开始接收数据
f.write(data)#写传来的数据到文件
f.close()#关闭文件,之所以在循环里面打开,关闭文件是为了保证每次数据都能存下,便于以后断点续传。
obj.sendall(b"getline")#------------------------------------------------------------------send 回应获取了一行数据
#data1 = str(data,'utf-8')
msg = obj.recv(500)#-----------------------------------------------------------------------recv 接收一个信息,包含:所传文件的总大小,断点位置和MD5值
#print (slong,msg)
msg1= str(msg,'utf8')
isMsg= msg1.split('|')#print(type(data))
if isMsg[0] == 'msg':#获取所传文件的总大小,断点位置和MD5值
filesize = int(isMsg[1])
fileBreak= int(isMsg[2])
srcfileMD5= isMsg[3]
self.progressBar(fileBreak,filesize,"finish :")#---------------------进度条
#lineSize += len(data)
obj.sendall(b"getMsg")#-----------------------------------------------------------------send 回应接收到msg
#---------------------------------------------------------------------------------------------------------------继续循环
#done = obj.recv(100)
defgetFile(self,srcFileName,sizeBreakpointFile,srcFileSizeCount,MD5):'''读文件:获取一个需要下载的文件 get file to download
:param srcFileName:准备要下载的源文件:
:param sizeBreakpointFile:存放断点位置的文件:
:param srcFileSizeCount:源文件的readline总计数
:param:MD5:源文件的MD5值
:return:'''
#print(srcFileName)
breakpointAt = 0 #初始化断点位置
#开始传输文件
t = os.path.isfile(sizeBreakpointFile)#如果存放断点的文件存在,先把他删除,以便从下一个断点往下记
ift:
os.remove(sizeBreakpointFile)
isFile= os.path.isfile(srcFileName)#查看需要下载的文件是否存在
#如果需要下载的源文件存在,就开始get的动作
ifisFile:
srcRf= open(srcFileName,'rb')#获取源文件句柄
while breakpointAt < srcFileSizeCount:#不断记录断点位置,并对比源文件总计数。以防止出现异常中断
#yield
line = srcRf.readline() #---------------源文件读一行
l =len(line)
sl=str(l)
self.request.sendall(bytes(sl,'utf8'))#----------------------------------------------------------------send 发送一行信息的长度
for i in range(200):pass
#print(sl)
self.request.recv(100)#---------------------------------------------------------------------------------recv 等待回应,不做处理
#time.sleep(0.1)
self.request.sendall(line)#发给客户端一行--------------------------------------------------------------send 发送一行信息
self.request.recv(200)#接收一个 get返回信息-------------------------------------------------------------recv 等待回应,不做处理
breakAtf = open(sizeBreakpointFile,'a')#打开记录断点的文件句柄
breakpointAt += 1#断点计数
s =str(breakpointAt)
s= "%s\n"%s
breakAtf.write(s)#写断点位置计数--------------------------写一个断点
breakAtf.close()#关闭句柄
msg= "msg|{}|{}|{}".format(srcFileSizeCount,breakpointAt,MD5)#----------msg|源文件总数|断点位置|MD5
self.request.sendall(bytes(msg,'utf8'))#----------------------------------------------------------------send 发送 msg|源文件总数|断点位置|MD5
self.request.recv(200)#----------------------------------------------------------------------------------rece 等待回应,不做处理
#word = " [正在传输 %s]"%srcFileName
self.progressBar(s,srcFileSizeCount,"finish:") #--------------------进度条
else:
srcRf.close()#-------------------------------------------------------------------------------------------传输完成
self.request.sendall(bytes('alldone','utf8'))#---------------------------------------------------------send ‘alldone’传输完成标志
defgetBreakFile(self,srcFileName,sizeBreakpointFile,breakpointAt,srcFileSizeCount,MD5):'''源文件断点续传
:param srcFileName:
:param sizeBreakpointFile:
:param breakAt:
:param countAll:
:return:'''t= os.path.isfile(sizeBreakpointFile)#如果存放断点的文件存在,先把他删除,以便从下一个断点往下记
ift:
os.remove(sizeBreakpointFile)
isFile= os.path.isfile(srcFileName)#查看需要下载的文件是否存在
i =0#如果需要下载的源文件存在,就开始get的动作
ifisFile:
srcRf= open(srcFileName,'rb')#获取源文件句柄
while i < srcFileSizeCount:#不断记录断点位置,并对比源文件总计数。以防止出现异常中断
i += 1#断点计数
if i > breakpointAt:#进入断点续传条件
line = srcRf.readline() #源文件读一行
self.request.sendall(line)#发给客户端一行---------------------------------------------------------------send
self.request.recv(200)#接收一个 get返回信息--------------------------------------------------------------recv
breakAtf = open(sizeBreakpointFile,'a')#打开记录断点的文件句柄
#drcWf = open(drcFileName,'ab')
#drcWf.write(line)#写目标文件
s =str(breakpointAt)
s= "%s\n"%s#print(breakpointAt)
breakAtf.write(s)#写断点位置计数
breakAtf.close()#drcWf.close()
msg = "msg|{}|{}".format(srcFileSizeCount,breakpointAt)#发送 msg|源文件总数|断点位置
self.request.sendall(bytes(msg,'utf8'))#----------------------------------------------------------------send
self.request.recv(200)#----------------------------------------------------------------------------------rece get
word = "[正在传输 %s]"%srcFileName
self.progressBar(s,srcFileSizeCount,word)#进度条
srcRf.close()#-----------------------------------------------------------------------------------------------传输完成
lastMsg = "alldone|{}".format(MD5)#------------------------------------------------------------------------send 最后发送源文件的MD5值
self.request.sendall(bytes(lastMsg,'utf8'))deffile_md5(self,filename):#calltimes = 0
hmd5 =hashlib.md5()
fp= open(filename,"rb")
f_size=os.stat(filename).st_sizeif f_size>_FILE_SLIM:while(f_size>_FILE_SLIM):
hmd5.update(fp.read(_FILE_SLIM))
f_size/=_FILE_SLIM#calltimes += 1 #delete
if(f_size>0) and (f_size<=_FILE_SLIM):
hmd5.update(fp.read())else:
hmd5.update(fp.read())returnhmd5.hexdigest()defwriteFileSize(self,fileName,sizeFile):
sizeWf= open(sizeFile,'w+')#文件总大小文件开启 ,准备读
count =0
with open(fileName,'rb') as srcRf:#循环以获取文件总数
for line insrcRf:
count+= 1s= str(count)#存储总数到文件 1.txt
s = "%s\n"%s
sizeWf.write(s)
sizeWf.close()returncountdefgetBreakAT(self,fileName):
i=0
with open(fileName,'r') as srcRf:#循环以获取文件总数
for line insrcRf:
lastRead=line#print(type(lastRead))
i=int(lastRead.strip())returnidef progressBar(self,num=1, sum=100,bar_word=":"):
rate= float(num) /float(sum)
rate_num= int(rate * 100)
temp= '\r%d %% %s' %(rate_num,bar_word)
sys.stdout.write(temp)
sys.stdout.flush()##################################
classclsSocketServer(object):def __init__(self,host,port):
self.host=host
self.port=portdefstartServer(self,obj):
server=socketserver.ThreadingTCPServer((self.host,self.port),obj)
server.serve_forever()