使用TCP协议设计一个文件下载服务协议,客户端发送要下载的文件路径给服务器,服务器将对应的文件内容送给客户端,客户端将文件存储到本地磁盘。注意,当文件不存在时给出提示。要求服务的实现分别采用以下三种方法实现:
(1)单线程,迭代服务器(依次服务每一个客户端)
(2)多线程,并发服务器
(3)异步方式(select模型或poll模型)
(4)asyncio库
实现了单线程、多线程、select模型异步
Base
from socket import *
from threading import Thread
import os
import struct
import time
import select
class BaseMessageHead:
"所有消息头的基类"
formatStr = ""
headLength = 0
class MessageHeadServiceProvider(BaseMessageHead):
"默认消息头的实现"
formatStr = "HHL" # 定义各字段打包格式
headLength = struct.calcsize(formatStr)
def __init__(self):
self.tupleField = (0, 0, 0) # 按顺序存储以下字段值
# 消息头字段
self.type = 0 # 消息类型 0代表通知,1文件,2代表请求文件
self.mode = 0 # 保留字段
self.msgLength = 0 # 消息大小 小于(2^32-1)B,约4GB
def UpdateField(self) -> None:
"利用tupleField更新消息头字段"
self.type = self.tupleField[0]
self.mode = self.tupleField[1]
self.msgLength = self.tupleField[2]
def ExportBytes(self) -> bytes:
"将消息头导出字节数组"
result = struct.pack(MessageHeadServiceProvider.formatStr,self.tupleField[0], self.tupleField[1], self.tupleField[2])
return result
def ImportBytes(self, head: bytes) -> None:
"将字节数组导入消息头"
self.tupleField = struct.unpack(MessageHeadServiceProvider.formatStr, head)
self.UpdateField()
def SetField(self, tupleField: tuple) -> None:
"设置消息头字段"
self.tupleField = tupleField
self.UpdateField()
def GetField(self) -> tuple:
"获取消息头字段"
return self.tupleField
def Parse(self, dataSocket,fileName=None) -> None:
"利用dataSocket解析者,解析消息头"
# type=0 消息体为字符串
if self.type == 0:
noticeBytes = dataSocket.recv(self.msgLength)
notice = noticeBytes.decode()
print(f"Received notice: {notice}")
# type=1 消息体为文件
elif self.type == 1:
recvedSize=0
f = open(time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())+" "+fileName, "wb")
while recvedSize < self.msgLength:
if self.msgLength - recvedSize > FileData.oneReadSize:
currentData = dataSocket.recv(FileData.oneReadSize)
f.write(currentData)
recvedSize += len(currentData)
else:
currentData = dataSocket.recv(self.msgLength - recvedSize)
recvedSize += len(currentData)
f.write(currentData)
f.close()
# type=2文件请求消息
elif self.type == 2:
filePathBytes = dataSocket.recv(self.msgLength)
filePath = filePathBytes.decode()
fd = FileData()
# 文件不存在 处理
if not fd.InitFileData(filePath):
# 服务端处理
print(f"{filePath} doesn't exist")
# 客户端处理
sd = StringData()
sd.InitStringData(f"{filePath}File doesn't exist")
self.SetField((0, 0, sd.strSize))
dataSocket.send(self.ExportBytes())
dataSocket.send(sd.strContent.encode())
# 流程处理
return
# 发送头部与文件
self.SetField((1, 0, fd.fileSize))
dataSocket.send(self.ExportBytes())
with open(fd.filePath, "rb") as temp:
while True:
data = temp.read(FileData.oneReadSize)
if not data:
break
dataSocket.send(data)
else:
# 接收到非法信息
print(f"{dataSocket.getpeername()[0]}:{dataSocket.getpeername()[1]} is refused")
print(f"{dataSocket.getpeername()[0]}:{dataSocket.getpeername()[1]} disconnected")
dataSocket.close()
return
class FileData:
"保存已读入文件元数据"
# 在InitFileData成功后,存入FileData对象
fileList = []
oneReadSize = 2048
def __init__(self):
self.fileSize = 0
self.filePath = ""
def InitFileData(self, filePath):
"返回布尔值"
if not os.path.exists(filePath):
return False
self.fileSize = os.path.getsize(filePath)
self.filePath = filePath
FileData.fileList.append(self)
return True
class StringData:
"保存已读入字符串元数据"
# 在InitStringData成功后,存入StringData对象
strList = []
def __init__(self):
self.strSize = 0
self.strContent = ""
def InitStringData(self, str):
if not str:
return False
self.strSize = len(str.encode("UTF-8"))
self.strContent = str
StringData.strList.append(self)
return True
class BaseMode:
"所有服务器实现的基类"
def __init__(self, ip, port) -> None:
self.IP = ip
self.Port = port
class SingleThreadMode(BaseMode):
"单线程"
MAXCONNECTED = 8 # 最大连接数
def __init__(self, ip, port):
BaseMode.__init__(self, ip, port)
self.waitList = [] # dataSocket等待队列
self.currentDataSocket = None # 当前服务的客户端
def Start(self):
# 主线程,监听连接
listenSocket = socket(AF_INET, SOCK_STREAM)
listenSocket.bind((self.IP, self.Port))
listenSocket.listen(SingleThreadMode.MAXCONNECTED)
print(f"Server started,port {self.Port} is open...")
# 创建子线程
th = Thread(target=self.DefaultClientHandler)
th.start()
while True:
dataSocket, addr = listenSocket.accept()
print(f"{addr[0]}:{addr[1]} connected")
self.waitList.append(dataSocket)
def DefaultClientHandler(self):
"此线程用于处理waitList中的socket对象,只被创建一次"
while True:
# 等待列表为空时,每次暂停运行一秒,减少资源开销
if self.waitList == []:
time.sleep(1)
continue
self.currentDataSocket = self.waitList.pop(0)
while True:
# 读入消息头
# 客户端断开链接处理
try:
head = self.currentDataSocket.recv(
MessageHeadServiceProvider.headLength)
if not head:
print(
f"{self.currentDataSocket.getpeername()[0]}:{self.currentDataSocket.getpeername()[1]} disconnected")
self.currentDataSocket = None
break
except:
print(
f"{self.currentDataSocket.getpeername()[0]}:{self.currentDataSocket.getpeername()[1]} disconnected")
self.currentDataSocket = None
break
msgHead = MessageHeadServiceProvider()
msgHead.ImportBytes(head)
msgHead.Parse(self.currentDataSocket)
class MultipleThreadMode(BaseMode):
"多线程"
MAXCONNECTED = 8 # 最大连接数
def __init__(self, ip, port):
BaseMode.__init__(self, ip, port)
def Start(self):
# 主线程,监听连接
listenSocket = socket(AF_INET, SOCK_STREAM)
listenSocket.bind((self.IP, self.Port))
listenSocket.listen(MultipleThreadMode.MAXCONNECTED)
print(f"Server started,port {self.Port} is open...")
while True:
dataSocket, addr = listenSocket.accept()
print(f"{addr[0]}:{addr[1]} connected")
# 创建子线程
th = Thread(target=self.DefaultClientHandler,
args=(dataSocket, addr))
th.start()
def DefaultClientHandler(self, dataSocket, addr):
"此线程用于单独地处理客户端对象,每连进一个客户端就创建一次"
while True:
# 读入消息头
# 客户端断开链接处理
try:
head = dataSocket.recv(MessageHeadServiceProvider.headLength)
if not head:
print(
f"{dataSocket.getpeername()[0]}:{dataSocket.getpeername()[1]} disconnected")
dataSocket = None
break
except:
print(
f"{dataSocket.getpeername()[0]}:{dataSocket.getpeername()[1]} disconnected")
dataSocket = None
break
msgHead = MessageHeadServiceProvider()
msgHead.ImportBytes(head)
msgHead.Parse(dataSocket)
class AsynchronousMode(BaseMode):
"异步"
MAXCONNECTED = 8 # 最大连接数
def __init__(self, ip, port):
BaseMode.__init__(self, ip, port)
self.rList = []
def Start(self):
# 主线程,利用select监听 读等待队列
listenSocket = socket(AF_INET, SOCK_STREAM)
listenSocket.bind((self.IP, self.Port))
listenSocket.listen(AsynchronousMode.MAXCONNECTED)
print(f"Server started,port {self.Port} is open...")
# 第一个是listenSocket,后面每一个都是dataSocket
self.rList = [listenSocket, ]
while True:
rl, wl, el = select.select(self.rList, [], [], 10)
for eachSocket in rl:
# listenSocket处理
if eachSocket == listenSocket:
dataSocket, addr = eachSocket.accept()
print(f"{addr[0]}:{addr[1]} connected")
# 将新建立的socket添加到rList列表
self.rList.append(dataSocket)
# dataSocket处理
else:
try:
head = eachSocket.recv(MessageHeadServiceProvider.headLength)
if not head:
print(
f"{eachSocket.getpeername()[0]}:{eachSocket.getpeername()[1]} disconnected")
self.rList.remove(eachSocket)
continue
except:
print(
f"{eachSocket.getpeername()[0]}:{eachSocket.getpeername()[1]} disconnected")
self.rList.remove(eachSocket)
continue
msgHead = MessageHeadServiceProvider()
msgHead.ImportBytes(head)
msgHead.Parse(eachSocket)
class ServerInstallation:
"通用服务器,初始化时需传入启动模式"
def __init__(self, mode) -> None:
self.mode = mode
def Start(self):
self.mode.Start()
Server
from socket import *
from threading import Thread
from Base import *
IP = ''
PORT = 60666
mode1=SingleThreadMode(IP,PORT)
mode2=MultipleThreadMode(IP,PORT)
mode3=AsynchronousMode(IP,PORT)
modeList=[]
modeList.append(mode1)
modeList.append(mode2)
modeList.append(mode3)
print("======================================")
print(" ")
print(" choose mode ")
print(" ")
print("======================================")
while True:
print("SingleThread(0)")
print("MultipleThread(1)")
print("Asynchronous(2)")
tempMode=input()
if tempMode=="exit":
exit(0)
try:
mode=modeList[int(tempMode)]
break
except:
print("Not a legal value")
server=ServerInstallation(mode)
server.Start()
Client
from socket import *
from Base import *
# socket初始化
IP = "127.0.0.1"
PORT = 60666
dataSocket = socket(AF_INET, SOCK_STREAM)
dataSocket.connect((IP, PORT))
# 生成对象
msgHead = MessageHeadServiceProvider()
str = StringData()
while True:
optionNum=-1
print(f"Notice(input n)\nGetFile(input f)")
# 输入选项
while True:
option = input(">>> ")
if option == "n":
optionNum=0
print("Please input notice")
break
elif option == "f":
optionNum=2
print("Please input file path")
break
elif option=="exit":
dataSocket.close()
exit()
else:
print("Not a legal value")
#输入通知或文件路径
while True:
temp = input(">>> ")
if str.InitStringData(temp):
break
else:
print("Not a legal value")
#构建消息头
msgHead.SetField((optionNum, 0, str.strSize))
#发送消息头
dataSocket.send(msgHead.ExportBytes())
#发送消息体
dataSocket.send(str.strContent.encode())
if optionNum==2:
fileName=str.strContent.split("\\")[-1]
head=dataSocket.recv(MessageHeadServiceProvider.headLength)
msgHead=MessageHeadServiceProvider()
msgHead.ImportBytes(head)
msgHead.Parse(dataSocket,fileName)