FTP 文件服务器
需求分析:
(1)分为服务器和客户端,要求可以有多个客户端同时操作
(2)客户端可以查看服务器文件库中有什么文件
(3)客户端可以从文件库中下载文件到本地
(4)客户端可以上场一个本地文件到文件库
(5)使用print 在客户端打印命令输入提示,引导操作
技术点分析:
C / S 模式
并发模型 : 多线程
网络: TCP网络
文件传输: 边读边发 边收边写
功能模块划分和封装 : 函数 + 类
搭建整体结构框架
查看文件目录
下载文件
上传文件
通信协议:
请求类型 数据参量
获取文件列表 LIST
下载文件 RETR filename
上传文件 STOR filename
退出 EXIT
“”"
-----------------FTP文件服务 服务端
“”"
from socket import *
from threading import Thread
import os, time
# 全局变量
HOST = '0.0.0.0'
PORT = 8888
ADDR = (HOST, PORT) # 服务器地址
# 文件库
FTP = "/root/"
# 处理客户端请求
class FTPServer(Thread):
def __init__(self, connfd):
super().__init__()
self.connfd = connfd
def do_list(self):
# 判断文件库是否为空
file_list = os.listdir(FTP)
if not file_list:
self.connfd.send(b'FAIL') # 列表为空
return
else:
self.connfd.send(b'OK')
time.sleep(0.1)
data = "\n".join(file_list)
self.connfd.send(data.encode())
# 处理下载
def do_get(self, filename):
try:
f = open(FTP + filename, 'rb')
except:
# 文件不存在报异常
self.connfd.send(b"FAIL")
return
else:
# 文件打开成功
self.connfd.send(b"OK")
time.sleep(0.1)
# 发送文件
while True:
data = f.read(1024)
if not data:
time.sleep(0.1)
self.connfd.send(b"##") # 文件发送完毕
break
self.connfd.send(data)
f.close()
# 处理上传
def do_put(self, filename):
if os.path.exists(FTP + filename):
self.connfd.send(b"FAIL")
return
else:
self.connfd.send(b"OK")
# 接收文件
f = open(FTP + filename, 'wb')
while True:
data = self.connfd.recv(1024)
if data == b"##":
break
f.write(data)
f.close()
# 作为一个线程内容处理某一个客户端的请求
def run(self):
# 总分模式
while True:
# 某个客户端所有的请求
data = self.connfd.recv(1024).decode()
print("Request:", data) # 调试
# 更具不同的请求做不同处理
if not data or data == 'EXIT':
self.connfd.close()
return
elif data == 'LIST':
self.do_list()
elif data[:4] == 'RETR':
filename = data.split(' ')[-1]
self.do_get(filename)
elif data[:4] == 'STOR':
filename = data.split(' ')[-1]
self.do_put(filename)
def main():
# tcp套接字创建
sock = socket()
sock.bind(ADDR)
sock.listen(5)
print("Listen the port %s" % PORT)
# 循环连接客户端
while True:
try:
connfd, addr = sock.accept()
print("Connect from", addr)
except KeyboardInterrupt:
sock.close()
return
# 为连接进来的客户端创建单独的线程
t = FTPServer(connfd) # 使用自定义线程类创建线程
t.setDaemon(True) # 主线程退出,分之线程终止服务
t.start()
if __name__ == '__main__':
main()
---------------FTP 客户端
"""
ftp文件服务客户端
"""
from socket import *
import time
import sys
# 服务端地址
ADDR = ("127.0.0.1", 8888)
# 实现具体的请求功能
class FTPClient:
def __init__(self, sock):
self.sock = sock
def do_list(self):
self.sock.send(b"LIST") # 发送请求
result = self.sock.recv(128).decode() # 回复 字符串
# 根据回复分情况讨论
if result == 'OK':
# 接收文件列表
file = self.sock.recv(1024 * 1024).decode()
print(file)
else:
print("文件库为空")
# 下载文件
def do_get(self, filename):
data = "RETR " + filename
self.sock.send(data.encode()) # 发送请求
# 等回复
result = self.sock.recv(128).decode()
if result == 'OK':
# 接收文件
f = open(filename, 'wb')
while True:
data = self.sock.recv(1024)
if data == b"##":
break
f.write(data)
f.close()
else:
print("文件不存在")
# 上传文件
def do_put(self, filename):
# 本地判断,防止文件路径写错
try:
f = open(filename, 'rb')
except:
print("该文件不存在")
return
# 上传 put 后可能是路径/home/tarena/abc,提取真正的文件名
filename = filename.split('/')[-1]
data = "STOR " + filename
self.sock.send(data.encode()) # 发送请求
# 等回复
result = self.sock.recv(128).decode()
if result == 'OK':
# 发送文件
while True:
data = f.read(1024)
if not data:
time.sleep(0.1)
self.sock.send(b"##") # 文件发送完毕
break
self.sock.send(data)
f.close()
else:
print("文件已经存在")
# 退出
def do_exit(self):
self.sock.send(b"EXIT")
self.sock.close()
sys.exit("谢谢使用")
def main():
# 创建套接字
sock = socket()
sock.connect(ADDR)
# 实例化功能类对象
ftp = FTPClient(sock)
while True:
print("============ 命令选项==============")
print("*** list ***")
print("*** get file ***")
print("*** put file ***")
print("*** exit ***")
print("==================================")
cmd = input("请输入命令:")
if cmd == "list":
ftp.do_list()
elif cmd[:3] == "get":
filename = cmd.split(' ')[-1] # 提取文件名
ftp.do_get(filename)
elif cmd[:3] == "put":
filename = cmd.split(' ')[-1] # 提取文件名
ftp.do_put(filename)
elif cmd == "exit":
ftp.do_exit()
else:
print("请输入正确命令")
if __name__ == '__main__':
main()
I O 阻塞
"""
非阻塞IO
套接字对象 --》 非阻塞
"""
from socket import *
import time
# 打开日志文件
f = open("my.log",'a')
# 创建tcp套接字
sockfd = socket()
sockfd.bind(('0.0.0.0',8888))
sockfd.listen(5)
# 设置套接字的非阻塞
# sockfd.setblocking(False)
# 超时检测时间
sockfd.settimeout(3)
while True:
print("Waiting for connect")
try:
connfd,addr = sockfd.accept() # 阻塞等待
print("Connect from",addr)
except BlockingIOError as e:
# 干点别的事
msg = "%s : %s\n"%(time.ctime(),e)
f.write(msg)
time.sleep(2)
except timeout as e:
# 干点别的事
msg = "%s : %s\n"%(time.ctime(),e)
f.write(msg)
else:
# 正常有客户端连接
data = connfd.recv(1024)
print(data.decode())
IO 多路复用
定义 : 同时监控多个IO事件,当哪个IO事件准备就绪执行哪个IO事件,以此形成可以同时处理多个IO
的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率
具体方案:
select 方法 :windows linux unix
poll 方法 : linux unix
epoll 方法: linux
select 方法 : help(select.select) #可以查看帮助文档
rs,ws,xs = select(rlist,wlist,xlist[,timeout])
功能:监控IO事件,阻塞等待IO发生
参数: rlist 列表 读IO列表 添加等待发生的或者可读的IO事件
wlist 列表 写IO列表 存放要可以主动处理的或者可写的IO事件
xlist 列表 异常IO列表 存放出现异常要处理的IO事件
timeout 超时时间
返回值: rs 列表 rlist中准备就绪的IO
ws 列表 wlist中准备就绪的IO
xs 列表 xlist中准备就绪的IO