基于Python的ftp文件服务器(附代码)

朱通
2023-12-01

设计流程:  

 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 

 类似资料: