一、开发环境

server端:centos 7  python-3.6.2

客户端:Windows 7 python-3.6.2 pycharm-2018

程序目的:1、学习使用socketserver实现并发处理多个客户端。

             2、了解使用struct解决TCP粘包。

二、程序设计

(本人菜鸟一枚,对于开发规范,接口设计完全不懂,完全是随心所欲,自娱自乐。写博客主要是记录自己学习的点点滴滴,如有不足之处还请见谅。)

1、server端

1.1 目录结构如下:

 1.2 目录简介:

FTP_SERVER:程序主目录

app:程序主逻辑目录,目录下有四个模块:

FTPserver.py:FTP  Server端启动入口。

login.py:认证注册模块,用于处理用户注册,登录认证。

dataAnalysis.py:命令解析模块,负责解析,执行客户端命令。

FileOpertion.py:负责文件读,写。数据发送,数据接收。

db:存放user_pwd.db文件,用于存放用户信息(用户名,密码,FTP目录总空间,已使用空间等)

lib:存放公共数据。

1.3 模块中类的继承关系

1.4 执行流程

1.4.1 程序启动文件FTPserver.py,程序启动后进入监听状态。核心代码如下:

class MyFtpServer(socketserver.BaseRequestHandler):

    def handle(self):  # 重写handle方法,处理socket请求
print(f"连接来自{self.client_address}的客户端")
commom_obj = Commom()
data_analy = DataAnalysis()
login_obj = Login()
while 1:
# 执行用户选项:1、登陆系统 2、注册账号。并返回一个结果
status_id = login_obj.run_client_choice(self.request, commom_obj)
if status_id == "": # 登陆成功
if not self.run_ftp_server(data_analy,commom_obj): # 执行ftpserver主功能
break
elif int(status_id) == -1: # client断开连接了
break
print(f"客户端{self.client_address}断开了连接") def run_ftp_server(self,data_analy,commom_obj):
""""
登陆成功后,接收客户端发来的命令,并进行处理
:param data_analy:负责解析,执行客户端命令的对象
:param commom_obj:程序执行时所需的数据对象
:return 返回false代表客户端断开连接了
"""
while True:
try:
cmd_len_pack = self.request.recv(4)
cmd_len = struct.unpack('i',cmd_len_pack)[0] # 获取命令长度,防止粘包
except Exception:
break
recv_data = self.request.recv(cmd_len).decode('utf-8') # 接收客户端数据
if recv_data.upper() == "Q": # 客户端提出断开连接了
break
# 解析,处理客户端的命令
data_analy.syntax_analysis(recv_data, self.request, commom_obj)
return False if __name__ == '__main__':
print('运行FTP服务')
ip_port = ('192.168.10.10',9000)
# 创建并发服务端对象
server = socketserver.ThreadingTCPServer(ip_port, MyFtpServer)
# 开启服务
server.serve_forever()

1.4.2 服务端进入监听状态后,客户端发起连接请求,服务端接收连接请求后会等待客户单发来状态码,1表示请求登录FTP服务器,2表示客户端要注册用户,注册用户需要服务端手动反馈状态码1才可注册。处理用户登录,注册模块login.py核心代码如下:

class Login(FileOperation):
"""
登陆注册类。主要负责用户的登陆认证,和用户注册。
"""
def run_client_choice(self,socket_obj,commom):
"""
获取客户端的请求,1是登陆,2是注册用户
:param socket_obj: socket对象
:param commom: ftpserver运行时所需要的数据对象
:return:
"""
recv_choice = socket_obj.recv(1).decode("utf-8") # 获取用户选项:1是登陆,2是注册用户
if recv_choice == "": # client请求登陆
return self.login_authen(socket_obj,commom)
elif recv_choice == "": # client请求注册账号
return self.register_user(socket_obj,commom)
else:
return -1 # client断开连接了 # 用户登陆认证
def login_authen(self,socket_obj,commom):
"""
客户端登陆认证
:param socket_obj: socket对象
:param commom: ftpserver运行时需要的数据对象
:return:返回1代表登陆成功
"""
# 接收client发来的用户名,密码
recv_userPwd = self.recv_data(socket_obj).decode("utf-8").split("|")
# 效验用户名密码
check_ret = self.check_user_pwd(recv_userPwd, socket_obj,commom)
if check_ret: # 用户名密码正确
self.check_user_home_dir(commom,recv_userPwd[0]) # 检测用户家目录
return commom.status_info["login_success"]
else:
return commom.status_info["login_fail"] ... # 注册用户
def register_user(self,socket_obj,commom):
"""
:param socket_obj:
:param commom:
:return: 返回是否允许注册的结果,1允许客户端注册,2拒绝客户端注册
"""
while True:
choice_id = input("请输入回应码:1是允许注册,2是不允许注册:")
if choice_id.isdigit() and 3 > int(choice_id) > 0:
socket_obj.send(choice_id.encode("utf-8")) # 发通知告知客户端,处理结果
if choice_id == "": # 注册用户
return self.client_register(socket_obj, commom)
return choice_id
else:
print("您输入的信息有误,请重新输入。") ...

1.4.3 客户端登录成功后,服务端会等待接收客户端发来的命令,命令的解析,执行由dataAnalysis.py模块执行,核心代码如下:

class DataAnalysis(FileOperation):
"""
数据分析处理类,主要负责解析client发送过来的指令。
"""
def syntax_analysis(self,recv_data, socket_obj, commom):
"""
负责解析客户端传来的数据。
:param recv_data:接收到的客户端用户数据
:param socket_obj:socket对象
:param commom:数据对象
:return:
"""
clientData = recv_data.split(" ")
if hasattr(self,clientData[0]): # 判断对象方法是否存在
get_fun = getattr(self,clientData[0])#获取对象方法
get_fun(clientData,socket_obj,commom) # 运行对象方法
else:
pass
...

执行客户端命令后,继续等待接收客户端发来的命令,如此循环...。

2、客户端

2.1 目录结构如下:

2.2 目录简介:

client:程序主目录。

bin:程序入口,程序启动文件main.py用于建立socket连接,然后调用FTPclient.py模块下的run_ftp_client方法运行程序。

app:程序主逻辑,目录下有四个模块如下:

FTPclient.py:FTP客户端,根据用户选项,执行用户指令。

login.py:认证注册模块,用于处理用户注册,登录认证。

dataAnalysis.py:命令解析模块,解析用户输入的命令,发给服务端获取结果。

FileOpertion.py:负责文件读,写。

lib:存放公共数据,有两个文件:

commom.py:主要存放的是公共变量。

help.txt:存放的是帮助文档,当用户执行help命令时会调用该文件。

2.3 模块中类的继承关系

2.4 执行流程

2.4.1 程序入口main.py,启动后会与FTP服务端建立连接,与服务端连接成功后会调用FTPclient.py模块下的run_ftp_client方法,执行用户功能。核心代码如下:

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) # 接收用户输入的选项,执行对应的功能

2.4.2 FTPclient.py模块下的run_ftp_client方法会打印菜单,并等待用户输入选项,执行相应功能,核心代码如下:

class Client(Login,DataAnalysis):

    def run_ftp_client(self,socket_obj):
"""
运行用户输入的选项:1、是登陆 2、是注册账号
:return:
"""
while True:
self.login_menu() # 打印系统菜单
choice_id = self.get_user_choice() # 获取用户输入的选项
if choice_id:
if self.run_user_choice(choice_id,socket_obj):
break
else:
print("您输入的有误")
def get_user_choice(self):
"""
获取用户输入的选项
:return:
"""
choice_id = input("请输入选项:")
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 == "": # 登陆系统
socket_obj.send(choice_id.encode("utf-8")) # 发通知告知服务器准备登陆
if self.run_login(socket_obj) == True: # 执行登陆
return True
elif choice_id == "": # 注册用户
socket_obj.send(choice_id.encode("utf-8")) # 请求服务器,注册用户
self.register_user(socket_obj) # 执行注册
elif choice_id.upper() == "Q": # 退出程序
socket_obj.send(choice_id.encode("utf-8")) # 通知服务器,准备退出程序
socket_obj.close()
print("程序正常退出")
return True def run_login(self,socket_obj,):
"""
运行登陆认证模块,如果登陆成功执行程序主逻辑,否则重新登陆。
:param socket_obj:
:return:
"""
if self.login_authention(socket_obj):
while True:
send_data = input(">>>").strip(" ") # 获取发送数据(用户执行的命令)
if send_data.upper() == "Q": # 正常退出程序
socket_obj.send(send_data.encode("utf-8")) # 通知服务区断开连接
socket_obj.close()
print("程序正常退出")
return True
if self.syntax_analysis(send_data, socket_obj): # 解析用户数据并处理数据
print("异常退出")
return True
return False def login_menu(self):
print("-"*41)
print(" 欢迎登陆迷你FTPv1.0")
print("-"*41)
print("1、登陆系统")
print("2、用户注册")
print("Q、退出程序")

2.4.3 login.py模块主要用于处理注册和登录的功能,核心代码如下:

class Login(Commom):
def login_authention(self,socket_obj):
"""
登陆认证
:param socket_obj:socket 对象
:return:
"""
user_pwd = self.get_user_pwd() # 获取用户名密码
self.send_data(socket_obj,user_pwd) # 将用户名和密码发给服务器
recv_status = socket_obj.recv(2).decode("utf-8") # 等待接收状态码
print(self.status_info[recv_status]) # 打印状态码对应的结果
if self.status_info[recv_status] == '登录成功':
return True
return False
... def register_user(self,socket_obj):
"""
等待服务端反馈是否允许注册用户。
:param socket_obj:
:return:
"""
print("请等待服务端回应.....")
recv_status = socket_obj.recv(1).decode("utf-8")
if recv_status == "": # 服务端同意申请账号
user_pwd = self.get_regist_user_pwd() # 获取注册用户名和密码
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("用户名密码有误")
else: # 客户端拒绝申请账号的请求
print("服务端拒绝了您申请账号的请求,请与管理员取得联系。")
return False
...

2.4.4 用户登录成功后,会等待接收用户输入命令,由dataAnalysis.py模块负责解析用户输入的命令,并将命令发给FTP服务器,然后接收服务器的反馈。核心代码如下:

class DataAnalysis(FileOperation):

    def syntax_analysis(self,cmd,socket_obj):
"""
解析用户输入的命令。
:param cmd:用户执行的命令,如:put 上传的文件
:param socket_obj:socket对象发送和接收数据
:return:
"""
cmd_split = cmd.split(" ") # 将字符串命令分割成列表,用于验证命令是否存在
if hasattr(self,cmd_split[0]):
run_fun = getattr(self,cmd_split[0])
run_fun(cmd_split,socket_obj)
else:
print("无效的命令")
...

三、功能演示

测试环境:FTP服务器:虚拟机 centos 7  192.168.10.10

FTP客户端:本机 Windows 7 192.168.1.103

FTP客户端:虚拟机 Windows 7  192.168.10.102

1、注册登录

2、使用help查看帮助

3、文件夹操作(mkdir,rmdir,cd,list)

用户被锁定在FTP家目录(/home/FTP_HOME/用户名)中,无法查看其它用户的文件。

4、上传文件put

文件传输是经过MD5加密的,传输完成后客户端和服务端需要对MD5进行效验。传输完成后可以使用free查看用户空间。

4.1 上传文件(断点续传)

5、文件下载get

5.1 文件下载(断点续传)

将上面下载好的2.exe更名为3.exe。一会和断点续传的2.exe对比大小,两个文件大小一致才对。

6、rmfile 删除文件

7、执行远程命令

socket实现简单的FTP的更多相关文章

  1. SELECTORS模块实现并发简单版FTP

    环境:windows, python 3.5功能:使用SELECTORS模块实现并发简单版FTP允许多用户并发上传下载文件 结构:ftp_client ---| bin ---| start_clie ...

  2. socket.io简单入门(一.实现简单的图表推送)

    引子:随着nodejs蓬勃发展,虽然主要业务系统因为架构健壮性不会选择nodejs座位应用服务器.但是大量的内部系统却可以使用nodejs试水,大量的前端开发人员转入全堆开发也是一个因素. 研究本例主 ...

  3. 从零开始学Python08作业思路:开发简单的FTP

    一,作业要求 开发简单的FTP 1,用户登录 2,上传/下载文件 3,不同用户家目录不同 4,查看当前目录下文件 5,充分使用面向对象 二,程序文件清单 Folder目录:用户上传文件家目录 db目录 ...

  4. socket.io简单说明及在线抽奖demo

    socket.io简单说明及在线抽奖demo socket.io 简介 Socket.IO可以实现实时双向的基于事件的通信. 它适用于各种平台,浏览器或设备,也同样注重可靠性和速度. socket.i ...

  5. 运用socket实现简单的服务器客户端交互

    Socket解释: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是“孔”或“插座”.作为BSD UNIX的进程通信机制,取后一种意 ...

  6. java Socket实现简单在线聊天(二)

    接<java Socket实现简单在线聊天(一)>,在单客户端连接的基础上,这里第二步需要实现多客户端的连接,也就需要使用到线程.每当有一个新的客户端连接上来,服务端便需要新启动一个线程进 ...

  7. java Socket实现简单在线聊天(一)

    最近的项目有一个在线网页交流的需求,由于很久以前做过的demo已经忘记的差不多了,因此便重新学习一下. 我计划的大致实现步骤分这样几大步: 1.使用awt组件和socket实现简单的单客户端向服务端持 ...

  8. 转:【专题十二】实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  9. socket实现两台FTP服务器指定目录下的文件转移(不依赖第三方jar包)

    通过socket实现两台FTP服务器指定目录下的文件转移,其中包含了基础了ftp文件列表显示.上传和下载.这里仅供学习用,需掌握的点有socket.ftp命令.文件流读取转换等 完整代码如下: Ftp ...

随机推荐

  1. sqlserver数据库导入大批量数据

    参考: https://www.cnblogs.com/zhangliangzlee/p/3585862.html

  2. VS“无法查找或打开PDB文件”是怎么回事?如何解决

    有时候,我们使用 VS(Visual Studio)编译程序时会出现“无法查找或打开PDB文件”的提示,并且此时程序会生成失败,无法运行,如下图所示: 大家不要惊慌,出现这种提示并不是代码写错了,而是 ...

  3. 洛谷 P1659 [国家集训队]拉拉队排练(Manacher)

    题目链接:https://www.luogu.com.cn/problem/P1659 思路: 首先跑一遍Manacher,用$cnt_i$记录长为$i$的回文串有多少个. 所记录的$cnt$并不是最 ...

  4. xshell 链接虚拟机

    1.在虚拟机上添加网络适配器,选择仅主机模式 2. 2.启用本机的网络连接 3.在虚拟机上开启ssh服务 首先看下22端口有没开放,如果没有的话需要开启 service start sshd 4.在虚 ...

  5. 普及C组第一题(8.9)

    2297. [noip普及组2(放到第一题)]棋盘 (好像重名了)(File IO): input:chess.in output:chess.out 题目描述 众所周知,国际象棋的棋盘是一个网格.国 ...

  6. SqlDataReader阅读器关闭时尝试调用 HasRows 无效

    SqlDataReader阅读器关闭时尝试调用 HasRows 无效 原创长白山上放羊娃 发布于2018-07-25 00:29:27 阅读数 538  收藏 展开 在SqlHelper中封装好的Sq ...

  7. Educational Codeforces Round 76 (Rated for Div. 2) A. Two Rival Students

    You are the gym teacher in the school. There are nn students in the row. And there are two rivalling ...

  8. iframe子页面之间值传递

    <div style="width:100%;height: 100%;"> <div style="width:74%;height: 70%;flo ...

  9. linux shell let, expr 命令详解

    linux命令之let.exprshell程序中的操作默认都是字符串操作,在运行数学运算符的时候可能得到意想不到的答案: var=1var=$var+1echo $var 将输出 1+1从这个例子中可 ...

  10. P2P头部平台退出后,普通人如何避开投资理财的“雷区”?

    编辑 | 于斌 出品 | 于见(mpyujian) 近期,P2P市场上不断传来不利消息,引起市场轩然大波,也打乱了投资者投资计划,是继续坚持自己的选择还是另择它路? 18日,陆金所作为千亿头部平台,宣 ...