代码流程:
目录结构 - FtpServer:
|-- ftpserver
| |-- conf
| | |-- ftpserver.conf
| | `-- ftpusers.conf
| |-- data
| | |-- anonymous
| | |-- pub
| | | `-- test.txt
| | `-- root
| | `-- XX00
| | |-- limanman
| | `-- xx
| |-- __init__.py
| |-- __init__.pyc
| |-- logs
| `-- modules
| |-- FtpServer.py
| |-- FtpServer.pyc
| |-- __init__.py
| `-- __init__.pyc
|-- server.py
目录结构 - FtpClient:
|-- client.py
|-- ftpclient
| |-- data
| | |-- xx
| | |-- xx.0
| | |-- xx.0.0
| | |-- xx.1
| | |-- xx.2
| | |-- xx.3
| | |-- xx.4
| | |-- xx.5
| | |-- xx.6
| | |-- xx.7
| | `-- xx.8
| |-- __init__.py
| |-- __init__.pyc
| `-- modules
| |-- FtpClient.py
| |-- FtpClient.pyc
| |-- __init__.py
| `-- __init__.pyc
相关配置 - FtpServer:
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/ftpserver/conf/ftpserver.conf
# basic server setting
ftp_listen_port = 21
ftp_login_banner = 欢迎访问 XmDevOps FTPserver!
ftp_logout_banner = 再见!
ftp_access_dir =
# basic private setting
ftp_download_file = True
ftp_upload_file = True
ftp_rename_file = True
ftp_delete_file = True
ftp_create_dir = True
ftp_anonymous_access = True
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/ftpserver/conf/ftpusers.conf
root = root
代码实现 - FtpServer:
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/server.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
import os
import pprint
from ftpserver.modules.FtpServer import (FtpServer, FtpHandler)
def read_conf(conf):
"""Read conf file.
Args:
conf: ftp server conf
Returns:
dict
"""
# 配置文件错误检测略
conf_dict = {}
with open(conf, 'r+b') as rhandler:
for cur_line in rhandler:
cur_line = cur_line.strip()
if cur_line.startswith('#') or ('=' not in cur_line):
continue
key = cur_line.split('=')[0].strip()
val = cur_line.split('=')[1].strip()
conf_dict.update({key: val})
return conf_dict
def get_dirs(*args):
"""Get dirs.
Args:
args: dir list
Returns:
dict
"""
ftp_dirs = {}
for cur_dir in args:
ftp_dirs.update({
cur_dir: os.path.join(os.getcwd(),'ftpserver',cur_dir)
})
return ftp_dirs
def main():
"""Main function.
"""
host_info = ('', int(ftp_conf['ftp_listen_port']))
setattr(FtpHandler, 'ftp_dirs', ftp_dirs)
setattr(FtpHandler, 'ftp_conf', ftp_conf)
setattr(FtpHandler, 'ftp_user', ftp_user)
server = FtpServer(host_info, FtpHandler)
server.serve_forever()
if __name__ == "__main__":
# 读取当前的目录和配置
ftp_dirs = get_dirs('conf', 'logs', 'data', 'modules')
ftp_conf = read_conf(os.path.join(ftp_dirs['conf'], 'ftpserver.conf'))
ftp_user = read_conf(os.path.join(ftp_dirs['conf'], 'ftpusers.conf'))
# 更改默认程序运行目录
if ftp_conf.has_key('ftp_access_dir'):
if not ftp_conf['ftp_access_dir'].strip():
ftp_conf['ftp_access_dir'] = os.path.join(ftp_dirs['data'], 'pub')
# 运行
main()
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/ftpserver/modules/FtpServer.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
import os
import time
import glob
import shutil
from SocketServer import (StreamRequestHandler, ThreadingMixIn, TCPServer)
# 装饰 - 判断是否已登陆
def ftp_is_login(func):
"""Common login verification decorate.
Args:
func: ftp action.
Returns:
func
"""
def decorate(self, *args):
# 登陆验证
if not self.login_usr:
self.wfile.write('errors: login with username'
'and user pass first.%s' % (os.linesep))
return False
# 参数验证
if args[0][0] != 'ftp_disconnect':
if not args[0][1].strip():
self.wfile.write('errors: detected parameter '
'is empty with error.%s' % (os.linesep))
return False
func_res = func(self, *args)
return func_res
return decorate
# 多线程处理多路IO
class FtpServer(ThreadingMixIn, TCPServer):
"""Multiplex threading io class.
"""
pass
# 多路IO处理类
class FtpHandler(StreamRequestHandler):
"""Multiplex threading io request handler class.
Attrinutes:
exit_flag: is exit?
login_usr: curent login user.
directory: current directory
"""
login_usr = None
directory = None
exit_flag = False
@staticmethod
def add_ends(command):
"""Add linesep for command.
Args:
command: command string
Returns:
string
"""
return '%s%s' % (command, os.linesep)
# 主要处理方法
def handle(self):
while not self.exit_flag:
# 启动界面
if not self.login_usr:
self.wfile.write('%s%s' % (self.ftp_conf['ftp_login_banner'],
os.linesep))
self.wfile.write('User (%s:(none)): ' % self.client_address[0])
user_name = self.rfile.readline().strip()
self.wfile.write('Please specify the password.%s' % os.linesep)
self.wfile.write('Password: ')
user_pass = self.rfile.readline().strip()
self.ftp_authentication(['ftp_authentication',
user_name,user_pass])
# 命令执行
self.wfile.write('ftp> ')
cur_buffer = self.rfile.readline().strip()
parameters = cur_buffer.split('|')
cur_action = parameters[0]
if hasattr(self, cur_action):
func = getattr(self, cur_action)
# parameters返回给客户端时会用到
func_res = func(parameters)
# 防止指令参数为空导致异常友好提示
if not func_res:
continue
self.wfile.write(func_res)
else:
self.wfile.write('errors: get wrong'
'message type (%s)%s' % (cur_action,
os.linesep))
# FTP授权访问方法
def ftp_authentication(self, parameters):
"""Ftp authentication.
Args:
parameters: parameters list
Returns:
ftp_authentication::flag::describe
msgformat:
ftp_authentication|uname|upass
"""
# 登陆验证,格式已定
action_res = False
action, uname, upass = parameters
authentication_res = '%s::0::ftp authentication fail.' % (action)
if uname == 'anonymous':
action_res = True
if self.ftp_user.has_key(uname) and self.ftp_user[uname] == upass:
action_res = True
if action_res:
authentication_res = '%s::1::ftp authentication succ.' % (action)
self.directory = os.path.join(self.ftp_dirs['data'], uname)
if not os.path.exists(self.directory):
os.makedirs(self.directory)
# 代表已登陆
self.login_usr = uname
self.wfile.write(self.add_ends(authentication_res))
return action_res
# FTP显示当前路径
@ftp_is_login
def ftp_showpwd(self, parameters):
"""Ftp change dir
Args:
parameters: ['.']
Returns:
ftp_showpwd::dstdir::flag::describe
msgformat:
ftp_showpwd|.
"""
action, dstdir = parameters
showpwd_res = '%s::%s::1::%s' % (action, dstdir, self.directory)
return self.add_ends(showpwd_res)
# FTP切换目录方法
@ftp_is_login
def ftp_changedir(self, parameters):
"""Ftp change dir
Args:
parameters: parameters list
Returns:
ftp_changedir::dstdir::flag::describe
msgformat:
ftp_changedir|dstdir
"""
# 登陆验证,格式已定
action_res = False
action, dstdir = parameters
changedir_res = '%s::%s::0::ftp changedir fail.' % (action, dstdir)
cur_dir = os.path.join(self.directory, dstdir)
cur_dir = os.path.abspath(cur_dir)
# 限制用户在家目录
if os.path.exists(cur_dir) and os.path.isdir(cur_dir):
if cur_dir in self.ftp_dirs['data']:
cur_dir = self.directory
action_res = True
if action_res:
changedir_res = '%s::%s::0::ftp changedir succ.' % (action, dstdir)
os.chdir(cur_dir)
self.directory = cur_dir
return self.add_ends(changedir_res)
@staticmethod
def show_fileattr(cur_file):
"""Show file attribute.
Args:
cur_file: file with abs path.
Returns:
str
"""
mode_dict = {
'1': '--x',
'2': '-w-',
'4': 'r--',
'5': 'r-x',
'6': 'rw-',
'7': 'rwx',
}
file_type = 'f'
file_mode = ''
file_stat = os.stat(cur_file)
file_moct = oct(file_stat.st_mode)[-3:]
file_link = file_stat.st_nlink
file_size = file_stat.st_size
file_name = os.path.basename(cur_file)
file_time = time.ctime(file_stat.st_mtime)
if os.path.isdir(cur_file):
file_type = 'd'
for cur_bit in file_moct:
file_mode += mode_dict[cur_bit]
return '%s%s %s user group %-10s %-10s %s' % (file_type, file_mode,
file_link, file_size,
file_time, file_name)
# FTP列出文件方法
@ftp_is_login
def ftp_listfile(self, parameters):
"""Ftp list files.
Args:
parameters: parameters list
Returns:
ftp_listfile::dstdir::flag::describe
msgformat:
ftp_listfile|dstdir
"""
# 登陆验证,格式已定
attrs_list = []
action_flag = False
action, dstdir = parameters
cur_dir = os.path.join(self.directory, dstdir)
cur_dir = os.path.abspath(cur_dir)
listfile_res = 'ftp_listfile::%s:0::ftp listfile fail.' % (dstdir)
if not os.path.isdir(cur_dir):
return self.add_ends(listfile_res)
if cur_dir in self.ftp_dirs['data']:
cur_dir = self.directory
for cur_file in os.listdir(cur_dir):
abs_path = os.path.join(cur_dir, cur_file)
cur_line = self.show_fileattr(abs_path)
attrs_list.append(cur_line)
return self.add_ends(os.linesep.join(attrs_list))
# FTP下载文件方法
def ftp_download(self, parameters):
"""Ftp download.
Args:
parameters: [action, fname]
Returns:
ftp_download::fname::flag::describe
msgformat:
ftp_download|fname|fsize
"""
action, fname = parameters
freal_path = os.path.join(self.directory, fname)
download_res = ('ftp_download::%s::%s::0::ftp '
'download fail.' % (fname, 0))
if os.path.exists(freal_path) and os.path.isfile(freal_path):
fsize = os.path.getsize(freal_path)
# 告诉客户端文件名和文件大小
download_res = ('ftp_download::%s::%s::1::ftp '
'download ready' % (fname, fsize))
self.wfile.write(self.add_ends(download_res))
rhandler = open(freal_path, 'r+b')
cur_send = 0
while cur_send != int(fsize):
splus = fsize - cur_send
if splus <= 1024:
cur_buffer = rhandler.read(splus)
cur_send += splus
else:
cur_buffer = rhandler.read(1024)
cur_send += 1024
self.wfile.write(cur_buffer)
if not rhandler.closed:
rhandler.close()
download_res = ('ftp_download::%s::%s::1::ftp '
'download succ.' % (fname, fsize))
return self.add_ends(download_res)
@staticmethod
def file_suffix(fname, fpath):
"""Got file suffix.
Args:
fname: file name
fpath: file dir path
Returns:
str
"""
suffix_res = fname
real_path = os.path.join(fpath, fname)
glob_path = os.path.join(fpath, '%s.*' % (fname))
# 如果文件存在获取编号
if os.path.exists(real_path):
glob_file = glob.glob(glob_path)
if not glob_file:
suffix_res = '%s.0' % (fname)
else:
glob_file.sort(key=lambda s:int(s[-1]), reverse=True)
suffix_res = '%s.%s' % (fname, int(glob_file[0][-1])+1)
return os.path.join(fpath, suffix_res)
# FTP上传文件方法
@ftp_is_login
def ftp_upload(self, parameters):
"""Ftp upload.
Args:
parameters: [action, fname, fsize]
Returns:
ftp_upload::fname::flag::describe
msgformat:
ftp_upload|fname|fsize
"""
# 登陆验证,格式已定
action, fname, fsize = parameters
upload_res = 'ftp_upload::%s::0::ftp upload fail.' % (fname)
file_path = os.path.join(self.directory, fname)
file_back = self.file_suffix(fname, self.directory)
ahandler = open(file_back, 'a+b')
cur_recv = 0
while cur_recv != int(fsize):
cur_buffer = self.request.recv(1024)
cur_recv += len(cur_buffer)
ahandler.write(cur_buffer)
if not ahandler.closed:
ahandler.close()
upload_res = 'ftp_upload::%s::1::ftp upload succ.' % (fname)
return self.add_ends(upload_res)
# FTP文件更名方法
@ftp_is_login
def ftp_rename(self, parameters):
"""Ftp rename.
Args:
parameters: [sfname, dsname]
Returns:
ftp_rename::sfname::dfname::flag::describe
msgformat:
ftp_delete|sfname|dfname
"""
action, sfname, dfname = parameters
rename_res = 'ftp_rename::%s::%s::0::ftp rename fail.' % (sfname,
dfname)
sreal_path = os.path.join(self.directory, sfname)
dreal_path = os.path.join(self.directory, dfname)
if os.path.exists(sreal_path):
rename_res = 'ftp_rename::%s::%s::1::ftp rename succ.' % (sfname,
dfname)
shutil.move(sreal_path, dreal_path)
return self.add_ends(rename_res)
# FTP删除文件方法
@ftp_is_login
def ftp_delete(self, parameters):
"""Frp delete
Args:
parameters: [fname]
Returns:
ftp_delete::fname::flag::describe
msgformat:
ftp_delete|fname
"""
action, fname = parameters
rpath = os.path.join(self.directory, fname)
delete_res = 'ftp_delete::%s::0::ftp delete fail.' % (fname)
if os.path.exists(rpath):
if os.path.isfile(rpath):
os.remove(rpath)
else:
shutil.rmtree(rpath)
delete_res = 'ftp_delete::%s::1::ftp delete succ.' % (fname)
return self.add_ends(delete_res)
# FTP创建目录方法
@ftp_is_login
def ftp_createdir(self, parameters):
"""Ftp createdir.
Args:
parameters: [dname]
Returns:
ftp_createdir::dname::flag::describe
msgformat:
ftp_createdir|dname
"""
action, dname = parameters
createdir_res = 'ftp_createdir::%s::0::ftp createdir fail.' % (dname)
real_path = os.path.join(self.directory, dname)
os.makedirs(real_path)
createdir_res = 'ftp_createdir::%s::1::ftp createdir succ.' % (dname)
return self.add_ends(createdir_res)
# FTP关闭连接方法
@ftp_is_login
def ftp_disconnect(self, parameters):
"""Ftp disconnect.
Args:
parameters: default empty
Returns:
ftp_disconnect::flag::describe
msgformat:
ftp_disconnect
"""
action = parameters[0]
disconnect_res = '%s::0::ftp disconnect fail.' % (action)
self.exit_flag = True
if self.exit_flag:
disconnect_res = '%s::1::ftp disconnect succ.' % (action)
# 发送执行结果
return self.add_ends(disconnect_res)
代码实现 - FtpClient:
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/client.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
import os
import sys
import pprint
import socket
from ftpclient.modules.FtpClient import FtpClient
def main():
"""Main function.
"""
curdata_dir = os.path.join(os.getcwd(), 'ftpclient', 'data')
others_dict = {
'data' : curdata_dir,
}
action_dict = {
'dir' : 'ftp_listfile',
'cd' : 'ftp_changedir',
'get' : 'ftp_download',
'put' : 'ftp_upload',
'mv' : 'ftp_rename',
'pwd' : 'ftp_showpwd',
'del' : 'ftp_delete',
'mkdir': 'ftp_createdir',
'bye' : 'ftp_disconnect',
'quit' : 'ftp_disconnect'
}
FtpClient(action_dict, others_dict, '127.0.0.1', 21)
if __name__ == '__main__':
main()
/xm-workspace/xm-pyss/auto_python/xmdevops_limanman/ftpclient/modules/FtpClient.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
#
# Authors: limanman
# OsChina: http://my.oschina.net/pydevops/
# Purpose:
#
"""
import os
import sys
import glob
import socket
class FtpClient(object):
"""Ftp client class.
Attributes:
conn_host : ftp server host
conn_port : ftp server port
action_dict: ftp client action dict
"""
actionres_dict = {}
@staticmethod
def add_ends(command):
return '%s%s' % (command, os.linesep)
def __init__(self, action_dict, others_dict,
conn_host='127.0.0.1', conn_port=21):
self.conn_host = conn_host
self.conn_port = conn_port
self.others_dict = others_dict
self.action_dict = action_dict
self.cursocket = self.ftp_connect(self.conn_host,
self.conn_port)
# 切换到进程目录
os.chdir(self.others_dict['data'])
while True:
# 没数据会阻塞在这里
cur_buffer = self.cursocket.recv(1024)
action_res = cur_buffer.split('::')
if action_res[0] == 'ftp_disconnect':
break
# 结果保存后续再处理
if 'User' in cur_buffer or 'Password' in cur_buffer:
user_info = raw_input(cur_buffer)
user_info = self.add_ends(user_info)
self.cursocket.sendall(user_info)
elif 'ftp>' in cur_buffer:
comm_strs = raw_input(cur_buffer)
comm_strs = self.add_ends(comm_strs)
comm_list = comm_strs.split()
# 防止空输入的异常
if not comm_list:
self.cursocket.sendall(os.linesep)
continue
# 获取当前调用方法
curaction = comm_list[0]
curparses = comm_list[1:]
if not self.action_dict.has_key(curaction):
self.cursocket.sendall(comm_strs)
else:
func = getattr(self, self.action_dict[curaction])
func(curparses)
continue
else:
# 处理返回值
if '::' in cur_buffer:
self.actionres_dict.update({action_res[0]: action_res[1:]})
sys.stdout.write(cur_buffer)
sys.stdout.flush
def ftp_connect(self, conn_host, conn_port):
"""Ftp connect
Args:
conn_host: ftp server host
conn_port: ftp client port
Returns:
socket
"""
try:
cur_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 端口复用和长连接
cur_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# cur_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
except socket.error, e:
sys.exit('errors: creating socket with error %s' % (e))
try:
cur_socket.connect((conn_host, conn_port))
except socket.gaierror, e:
sys.exit('errors: resolve host with error %s' % (e))
except socket.error, e:
sys.exit('errors: connection host with error %s' % (e))
# 返回socket句柄
return cur_socket
# FTP显示当前路径
def ftp_showpwd(self, parameters):
"""Ftp showpwd.
msgformat:
ftp_showpwd|.
"""
res_parameters = self.add_ends('.')
self.cursocket.sendall('ftp_showpwd|%s' % (res_parameters))
# FTP列出文件方法
def ftp_listfile(self, parameters):
"""Ftp listfile.
msgformat:
ftp_listfile|dstdir
"""
res_parameters = self.add_ends(''.join(parameters))
self.cursocket.sendall('ftp_listfile|%s' % (res_parameters))
# FTP改变目录方法
def ftp_changedir(self, parameters):
"""Ftp change directory.
msgformat:
ftp_changedir|dstdir
"""
res_parameters = self.add_ends(''.join(parameters))
self.cursocket.sendall('ftp_changedir|%s' % (res_parameters))
@staticmethod
def file_suffix(fname, fpath):
"""Got file suffix.
Args:
fname: file name
fpath: file dir path
Returns:
str
"""
suffix_res = fname
real_path = os.path.join(fpath, fname)
glob_path = os.path.join(fpath, '%s.*' % (fname))
# 如果文件存在获取编号
if os.path.exists(real_path):
glob_file = glob.glob(glob_path)
if not glob_file:
suffix_res = '%s.0' % (fname)
else:
glob_file.sort(key=lambda s:int(s[-1]), reverse=True)
suffix_res = '%s.%s' % (fname, int(glob_file[0][-1])+1)
return os.path.join(fpath, suffix_res)
# FTP下载文件方法
def ftp_download(self, parameters):
"""Ftp download.
msgformat:
ftp_download|fname
"""
fname = parameters[0]
res_download = self.add_ends('%s' % (fname))
self.cursocket.sendall('ftp_download|%s' % (res_download))
while True:
cur_buffer = self.cursocket.recv(1024)
if '::' in cur_buffer:
fsize = int(cur_buffer.split('::')[2])
break
suffixname = self.file_suffix(fname, self.others_dict['data'])
whanler = open(suffixname, 'a+b')
cur_recv = 0
while cur_recv != fsize:
cur_buffer = self.cursocket.recv(1024)
cur_recv += len(cur_buffer)
whanler.write(cur_buffer)
if not whanler.closed:
whanler.close()
# FTP上传文件方法
def ftp_upload(self, parameters):
"""Ftp upload.
msgformat:
ftp_upload|fname|fsize
"""
fname = parameters[0]
rpath = os.path.join(self.others_dict['data'], fname)
if os.path.exists(rpath) and os.path.isfile(rpath):
fsize = os.path.getsize(rpath)
res_parameters = self.add_ends('%s|%s' % (fname, fsize))
self.cursocket.sendall('ftp_upload|%s' % (res_parameters))
rhandler = open(rpath, 'r+b')
cur_send = 0
while cur_send != int(fsize):
splus = fsize - cur_send
if splus <= 1024:
cur_data = rhandler.read(splus)
cur_send += splus
else:
cur_data = rhandler.read(1024)
cur_send += 1024
self.cursocket.sendall(cur_data)
if not rhandler.closed:
rhandler.close()
else:
print 'errors: read file %s with errors' % (fname)
# FTP文件命名方法
def ftp_rename(self, parameters):
"""Ftp rename
msgformat:
ftp_rename|sfname|dfname
"""
res_parameters = self.add_ends('|'.join(parameters))
self.cursocket.sendall('ftp_rename|%s' % (res_parameters))
# FTP文件删除方法
def ftp_delete(self, parameters):
"""Frp delete
msgformat:
ftp_delete|fname
"""
res_parameters = self.add_ends(''.join(parameters))
self.cursocket.sendall('ftp_delete|%s' % (res_parameters))
# FTP创建目录方法
def ftp_createdir(self, parameters):
"""Ftp createdir.
msgformat:
ftp_createdir|dname
"""
res_parameters = self.add_ends(''.join(parameters))
self.cursocket.sendall('ftp_createdir|%s' % (res_parameters))
# FTP关闭连接方法
def ftp_disconnect(self, parameters):
"""Ftp disconnect.
msgformat:
ftp_disconnect
"""
action_res = False
res_parameters = self.add_ends('ftp_disconnect')
self.cursocket.sendall(res_parameters)
return action_res
有图有像: