杭电胡老师的实验四:
使用TCP协议设计一个文件下载服务协议,客户端发送要下载的文件路径给服务器,服务器将对应的文件内容送给客户端,客户端将文件存储到本地磁盘。注意,当文件不存在时给出提示。要求服务的实现分别采用以下三种方法实现:
(1)单线程,迭代服务器(依次服务每一个客户端)
(2)多线程,并发服务器
(3)异步方式(select模型或poll模型)
(4)asyncio库
我用了6种方式实现,随便选就行了:
1、单线程, 迭代服务器
2、多线程, 并发服务器
3、多进程, 并发服务器
4、gevent库, 协程服务器
5、非阻塞模式服务器
6、asyncio库, 协程服务器
服务器
"""
@author: Bre Athy
@contact: https://www.zhihu.com/people/you-yi-shi-de-hu-xi
@productware: PyCharm
@file: 文件服务器_re3.0.py
@time: 2019/12/6 17:25
"""
import socket, gevent, threading, multiprocessing, asyncio,sys
from gevent import monkey
def get_file_content(file_name):
try:
with open(file_name, 'rb')as f:
print(" 获取成功!")
content = f.read()
if len(content) > 10 ** 10:print("文件过大!")
else:return "{:010d}".format(len(content)).encode()+content
except:
print(" 文件不存在!")
def fuck_one(client_socket, client_addr):
try:file_name = client_socket.recv(1024).decode("utf-8")
except:return False
if not file_name: return True
print(client_addr, "请求文件", file_name, end="")
file_content = get_file_content(file_name)
if file_content: client_socket.send(file_content)
client_socket.close()
def fuck_two(client_socket, clientAddr):
threading.Thread(target=fuck_one, args=(client_socket, clientAddr)).start()
def fuck_three(client_socket, clientAddr):
multiprocessing.Process(target=fuck_one, args=(client_socket, clientAddr)).start()
client_socket.close()
def fuck_four(client_socket, clientAddr):
gevent.spawn(fuck_one, client_socket, clientAddr)
def fuck_five(server_socket):
server_socket.setblocking(False)
g_socket_list = []
while True:
try:new_socket = server_socket.accept()
except:pass
else:
new_socket[0].setblocking(False)
g_socket_list.append(new_socket)
for g_socket in g_socket_list:
try:result = fuck_one(g_socket[0], g_socket[1])
except:pass
else:
if result:g_socket_list.remove(g_socket)
def fuck_six(ADDRESS, PORT):
async def handle(reader, writer):
while True:
try:file_name = (await reader.read(1024)).decode("utf-8")
except:break
if not file_name: break
client_addr = writer.get_extra_info('peername')
print(client_addr, "请求文件", file_name, end="")
file_content = get_file_content(file_name)
if file_content: writer.write(file_content)
await writer.drain()
writer.close()
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle, ADDRESS, PORT, loop=loop)
loop.run_until_complete(coro)
loop.run_forever()
def main(choice, meta):
ADDRESS = "127.0.0.1"
PORT = 6666
print(f"******************** {meta[int(choice) - 1]}启动 ********************")
if choice == '5': monkey.patch_all()
if choice == '6': fuck_six(ADDRESS, PORT)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((ADDRESS, PORT))
server_socket.listen(128)
fuck_boss = None
if choice == '1':fuck_boss = fuck_one
elif choice == '2':fuck_boss = fuck_two
elif choice == '3':fuck_boss = fuck_three
elif choice == '4':fuck_five(server_socket)
elif choice == '5' :fuck_boss = fuck_four
else:
print("输入错误!")
sys.exit()
while True:
new_client_socket, new_client_socket_addr = server_socket.accept()
fuck_boss(new_client_socket, new_client_socket_addr)
if __name__ == "__main__":
meta = ["单线程, 迭代服务器", "多线程, 并发服务器", "多进程, 并发服务器", "非阻塞模式服务器", "gevent库, 协程服务器", "asyncio库, 协程服务器"]
choice = input(f"服务器正在启动,请选择启动模式:\n1、{meta[0]}\n2、{meta[1]}\n3、{meta[2]}\n4、{meta[3]}\n5、{meta[4]}\n6、{meta[5]}\n\n【输入序号】:")
main(choice, meta)
客户端
"""
@author: Bre Athy
@contact: https://www.zhihu.com/people/you-yi-shi-de-hu-xi
@productware: PyCharm
@file: 文件客户端.py
@time: 2019/11/28 13:34
"""
from socket import *
import re,os
def main():
server_ip = "127.0.0.1"
server_port = 6666
root_dir = ""
print("******************** 客户端启动,最大支持文件大小:1G ********************")
while True:
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
try:tcp_client_socket.connect((server_ip, server_port))
except:
print("服务器异常,请检查地址是否正确!")
break
file_path = input("请输入文件路径【直接回车退出程序】:")
if file_path == "": break
tcp_client_socket.send(file_path.encode())
recv_length = tcp_client_socket.recv(10)
if "\\" in file_path: file_path = re.match(r".*\\(.*$)", file_path).group(1)
if recv_length:
while os.path.exists(file_path):file_path = "[接收]" + file_path
file_path = root_dir +file_path
with open(file_path, "ab")as f:
length = int(recv_length.decode())
print(f"请求文件:{file_path}\n文件大小:{length} 字节 ")
while length>0:
recv_data = tcp_client_socket.recv(1024)
f.write(recv_data)
length -= 1024
print("接收完成,文件已保存在当前路径:", file_path)
else:print(f"服务器不存在该文件!请检查!")
tcp_client_socket.close()
print("******************** 客户端已退出 ********************")
if __name__ == "__main__":
main()
运行截图:
其中单线程模式是不支持并发运行客户端的,所以我又添加了单线程非阻塞模式,运行时先运行服务器,再运行客户端。
12.9 日更新:
修复了客户端被强制断开报错的bug,
修复了没有后缀的文件不能保存的bug,
封装了文件属性,允许接收1G以内的文件,
设置了文件保存路径,支持反复接收相同文件