设计流程:
1、设计:使用多线程并发的TCP传输模式,实现客户端获取文件列表、上传文件、下载文件。
2、计划实施:今天完成项目
3、文档确认
使用方法:命令行方法获取文件目录、文件上传、文档下载
代码架构:
创建并发的网络连接 + 退出处理 + 测试
实现查看文件列表的功能
@ 客户端请求
@ 服务端确认请求
@ 遍历文件夹下文件,把文件名发给客户端
@ 客户端接收并打印
实现get_file()方法
@ 客户端发起请求,请求传输文件名
@ 服务器检查文件存在,发送文件头(文件名,文件总大小,分包大小,总发送次数)
@ 服务端打开文件读,开始发送文件(1MB/次)
@ 客户端接收文件写
实现put_file()方法
@ 客户端发起请求上传文件(文件名,文件大小,分包大小,分包数)
@ 服务器检查是否存在同名文件,返回许可
@ 客户端打开文件读,发送文件
@ 服务端打开文件写,接收文件
@ 服务端接受结束,返回成功信息
4、编写代码 + 测试
*****************************************************************************************************************************************
有几个问题需要注意:
1、Python的进程创建和调度非常浪费时间,个人电脑CPU一般为4核或8核,如果进程创建多了,反而会浪费资源
2、由于全局解释器锁GIL的存在,Python的多线程是伪多线程,但对于IO密集型程序,能够大幅提升效率
3、TCP传输会发生粘包问题,发送的命令数据包有可能和文件数据包粘包,需要处理,一般如下处理
1、将消息格式化
2、发送一个消息同时将一个消息的长度标识
3、让消息的发送延时,等待对方接收完
这里使用了数据包响应来处理粘包问题,一方发送命令包之后,接收方回复消息确认收到之后才进行下一步传输
文件数据传输时不会有粘包问题,因为即使粘在了一起也没关系,因为文件数据本身就是连续的
****************************************************************************************************************************************
模块使用说明:
该FTP服务器代码基于Python3.6标准库实现,使用TCP协议 和 多线程并发
在Ubuntu 16.04下调试通过
使用方法:
1、先运行服务器程序,服务器程序将文件存放在/home/xie/FTP_Folder/目录下,可在程序相应位置更改
2、在任意目录下运行客户端,弹出命令输入提示:
FTP_Client >>>
即可输入命令
命令介绍:
getlist:获取当前服务器上存放的文件目录
示例:
/********************************************
FTP_Client >>> getlist
List on the FTP server
+------------------------------+--------------------+
| file name | file size(bits) |
+------------------------------+--------------------+
| a.txt | 33 |
| server.py | 564 |
| socketserve.py | 24666 |
+------------------------------+--------------------+
*********************************************/
putfile /path/filename:将文件上传到ftp服务器,第二个/path/filename可以是相对路径,也可以是绝对路径(含文件名)
文件会直接上传到服务器相应目录下
getfile filename:将ftp服务器上的文件下载到本地,默认下载到当前工作目录
********************************************************************************************************************************************
代码如下:
服务器端:
import os, sys, threading, socket,math
import enum
class OptType(enum.Enum):
ERROR = 1
LIST = 2
SENDFILE = 3
RECVFILE = 4
HOST = "192.168.31.129"
PORT = 8888
ADDR = (HOST, PORT)
BUFFER_SIZE = 1024
def send_data(conn, desc, opttype):
SIZE_EACH_TIME = 1024
TIMES_TO_SEND = math.ceil(len(desc)/SIZE_EACH_TIME)
if opttype == OptType.LIST:
head_data = ("LIST %d" % len(desc)).encode()
conn.send(head_data)
feedback1 = conn.recv(BUFFER_SIZE).decode()
if feedback1 == 'READY TO RECEIVE LIST':
for i in range(TIMES_TO_SEND):
data_to_send = desc[i * SIZE_EACH_TIME:(i+1) * SIZE_EACH_TIME]
conn.send(data_to_send.encode())
else:
print("Send failed")
elif opttype == OptType.ERROR:
head_data = ("ERROR %d" % len(desc)).encode()
conn.send(head_data)
feedback1 = conn.recv(BUFFER_SIZE).decode()
if feedback1 == 'READY TO RECEIVE ERROR':
conn.send(desc.encode())
elif opttype == OptType.SENDFILE:
head_data = ("GETFILE %d" % os.path.getsize(desc)).encode()
conn.send(head_data)
feedback1 = conn.recv(BUFFER_SIZE).decode()
if feedback1 == 'READY TO RECEIVE FILENAME':
print(os.path.basename(desc))
conn.send(os.path.basename(desc).encode())
feedback2 = conn.recv(BUFFER_SIZE).decode()
if feedback2 == 'READY TO RECEIVE FILE':
fd = open(desc, 'rb')
while True:
file_data = fd.read(1024)
if not file_data:
break
conn.send(file_data)
fd.close()
def ftp_request_handler(conn, addr):
while True:
data = conn.recv(BUFFER_SIZE)
if not data:
break
order = data.decode().strip()
l = list()
print(order.lower())
order = order.split()
if order[0] == "getlist":
t = os.listdir("/home/xie/FTP_Folder/")
for f in t:
path = "/home/xie/FTP_Folder/" + f
if os.path.isfile(path) == True:
l.append((f, os.path.getsize(path)))
send_data(conn, l.__repr__(), OptType.LIST)
if order[0] == "getfile":
if len(order) == 1:
send_data(conn, "Input Error: 'getfile' should be followed by filename", OptType.ERROR)
for i in order[1:]:
print("i is", i)
path = "/home/xie/FTP_Folder/" + i
if os.path.exists(path) == False:
send_data(conn, "File Error: %s: no such file" % i, OptType.ERROR)
continue
send_data(conn, path, OptType.SENDFILE)
if order[0] == "putfile":
conn.send('READY TO RECEIVE FILE'.encode())
recv_size = 0
fd = open("/home/xie/FTP_Folder/" + order[1], 'wb')
while recv_size < int(order[2]):
tmp_data = conn.recv(1024)
fd.write(tmp_data)
recv_size += len(tmp_data)
fd.close()
conn.close()
sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sockfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockfd.bind(ADDR)
sockfd.listen()
while True:
try:
conn, addr = sockfd.accept()
except KeyboardInterrupt:
sockfd.close()
print("FTP server shut down")
os._exit(0)
except Exception as e:
print(e)
continue
thread = threading.Thread(target = ftp_request_handler, args = (conn, addr))
thread.daemon = True
thread.start()
客户端:
import socket,os
SERVER_IP = "192.168.31.129"
SERVER_PORT = 8888
SERVER_ADDR = (SERVER_IP, SERVER_PORT)
BUFFER_SIZE = 1024
connfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connfd.connect(SERVER_ADDR)
while True:
msg = input("FTP_Client >>> ")
if not msg:
break
order = msg.split()
if order[0] == "putfile":
for i in order[1:]:
if os.path.exists(i) == False:
print("File Error: %s: no such file exists" % i)
continue
filename = os.path.basename(i)
print(i, filename)
connfd.send(("putfile %s %d" % (filename, os.path.getsize(i))).encode())
if connfd.recv(1024).decode() == 'READY TO RECEIVE FILE':
fd = open(i, 'rb')
while True:
file_data = fd.read(1024)
if not file_data:
break
connfd.send(file_data)
fd.close()
continue
connfd.send(msg.encode())
data = connfd.recv(1024)
msg_type, SIZE = data.decode().split()
SIZE = int(SIZE)
if msg_type == 'LIST':
connfd.send('READY TO RECEIVE LIST'.encode())
filelist = b''
recv_size = 0
while recv_size < SIZE:
tmp_data = connfd.recv(1024)
recv_size += len(tmp_data)
filelist += tmp_data
filelist = filelist.decode()
l = eval("%s" % filelist)
print("\nList on the FTP server")
print("+", "".center(30,'-'), '+', ''.center(20, '-'), '+', sep = '')
print("|", "file name".center(30), '|', 'file size(bits)'.center(20), '|', sep = '')
print("+", "".center(30,'-'), '+', ''.center(20, '-'), '+', sep = '')
for i in l:
print('|', i[0].center(30), '|', str(i[1]).center(20), '|', sep = '')
print("+", "".center(30,'-'), '+', ''.center(20, '-'), '+', sep = '')
elif msg_type == 'ERROR':
connfd.send('READY TO RECEIVE ERROR'.encode())
data = connfd.recv(1024)
print(data.decode())
elif msg_type == 'GETFILE':
connfd.send('READY TO RECEIVE FILENAME'.encode())
recv_size = 0
tmp_data = connfd.recv(1024).decode()
print("Tmp_data is", tmp_data)
connfd.send('READY TO RECEIVE FILE'.encode())
fd = open(tmp_data, 'wb')
while recv_size < SIZE:
tmp_data = connfd.recv(1024)
fd.write(tmp_data)
recv_size += len(tmp_data)
fd.close()
connfd.close()
代码及说明上传到了百度网盘:https://pan.baidu.com/s/1J4trYGpNqy7u976juoxW_g
提取码:0vlo