这一部分其实就是实现客户端与服务端的一个大文件的传输,因为题目要求是只需要自定义应用层协议,所以本项目是基于TCP(传输层)协议下实现的。采用的是python的struct模块对传输信息进行打包以便应对不同类型的传输文件,同时可以转为用于传输的byte类型。
针对大文件的传输开始是想直接用动态分配内存给文件传输分配一个大容量的堆作为缓冲区,但感觉这样就违背了本题的意思而且对于文件传输的效率不高,于是采用小缓冲区分段对文件进行传输。
实现的功能为:
服务端接收来自客户端的需求请求后
服务端实现三个功能:
1.客户端请求下载服务端的文件,服务端返回给客户端可下载的文件列表,服务端根据列表选项完成下载
2.客户端请求上传文件,选择一个本地的文件进行上传
3.客户端请求断开连接同时关闭服务器
待完善功能:
实现多线程连接 (本题来说必要性不大感觉)
利用python的gui模块做一个整洁好看的界面 (尝试过被出现的奇奇怪怪的bug打败)
客户端:
#-*- coding:utf-8 -*-
'''
接收来自客户端的需求请求后
服务端实现三个功能:
1.客户端请求下载服务端的文件,服务端返回给客户端可下载的文件列表,服务端根据列表选项完成下载
2.客户端请求上传文件,选择一个本地的文件进行上传
3.客户端请求断开连接同时关闭服务器
'''
'''
CS之间的传输的应用层协议自定义为:
基于TCP传输层协议下:
使用python的struct模块,对发送的数据进行pack打包
将包内数据转为可传输的byte类型,接收端接收后拆包为str或int等类型进行应用
根据包头信息对包类型以及大小的定义可以完成不同类型的数据的网络传输
'''
from socket import *
import sys
import os
import struct
filepath = '.' + '/' #服务器用于和客户端传输文件的文件库
HOST = ''
PORT = 8686
BUF_SIZE = 1024 #缓冲区大小
ADDR = (HOST, PORT) #socket
SerSock = socket(AF_INET, SOCK_STREAM)
SerSock.bind(ADDR)
#开始监听
SerSock.listen(5)
print ('Waiting for connection...')
CliSock, addr = SerSock.accept()
print ('...connected from: ', addr)
CliSock.send('connected successfully'.encode())
while True:
#接收客户端请求的功能需求
data = CliSock.recv(BUF_SIZE)
data = data.decode()
if data == '1':
#显示给客户端可以下载的文件列表
CliSock.send('the list of files you can download: '.encode())
dirs = os.listdir(filepath)
count = 0
#统计列表中文件的数量发送给客户端
for dir in dirs:
count = count + 1
CliSock.send(struct.pack('i', count)) #i表示传输的包为int型
#将列表中的文件名逐一发送给客户端
for dir in dirs:
#256si:s表示char类型, i表示int类型, 256表示包可传送的字节大小
CliSock.send(struct.pack('256si', bytes(dir, 'utf-8'), os.stat(filepath+dir).st_size))
file = CliSock.recv(struct.calcsize('128si'))
if file:
#解包,接收客户端下载需求
fname, fisize= struct.unpack('128si', file)
fs = fname.decode().strip('\0')
if fs in dirs:
#将包的具体信息发送给客户端
fhead = struct.pack('256si',os.path.basename(fs).encode(), os.stat(filepath+fs).st_size)
CliSock.send(fhead)
#读取文件并将文件按照缓冲区(1KB)上限拆分发送
fs = filepath+fs
fp = open(fs, 'rb')
while True:
fdata = fp.read(BUF_SIZE)
if not fdata:
print('send successfully')
break
CliSock.send(fdata)
fp.close()
#CliSock.close()
#break
elif data == '2':
#接收来自服务端发送的文件的名称及大小信息
file = CliSock.recv(struct.calcsize('256si'))
if file:
fname, fsize = struct.unpack('256si', file)
fs = fname.decode().strip('\0')
newfile = os.path.join('new_' + fs)
#将接收的新文件放在服务端用于接收客户端文件的目录下
newfile = filepath + newfile
fp = open(newfile, 'wb')
#获取每次服务端发送来的文件内容,并逐一写入新文件中
#判断不足1024时要只写入剩下的字节,否则会产生错码
resize = 0
while not resize == fsize:
if fsize - resize > BUF_SIZE:
data = CliSock.recv(BUF_SIZE)
resize = resize + len(data)
else:
data = CliSock.recv(fsize - resize)
resize = fsize
fp.write(data)
fp.close()
print('receive successfully')
#CliSock.close()
#break
#退出
elif data == 'exit':
CliSock.close()
print('shut down the connection with client successfully! ')
break
#代表客户端的输入无法识别,不做处理
else:
continue
#CliSock.close()
SerSock.close()
print('exit server successfully! ')
客户端:
#-*- coding:utf-8 -*-
from socket import *
import sys
import os
import struct
HOST = 'localhost'
PORT = 8686
BUF_SIZE = 1024
ADDR = (HOST, PORT)
CliSock = socket(AF_INET, SOCK_STREAM)
CliSock.connect(ADDR)
print(str(CliSock.recv(BUF_SIZE), 'utf-8'))
#功能UI
print('\n'+'------- 1 express download a file -------'+'\n'+'-------- 2 express upload a file --------'+'\n'+'--- exit express shut down server and client ---'+'\n')
while True:
#选择功能需求发送给服务端
type = input('What type of service do you need? '+'\n')
CliSock.send(type.encode())
if type == '1':
print(str(CliSock.recv(BUF_SIZE),'utf-8') + '\n')
#获取文件总数
filesize = CliSock.recv(struct.calcsize('i'))
filesize, = struct.unpack('i', filesize)
#filesize = int(filesize.decode())
fdict = {} #记录文件名为索引的文件大小
filenum = filesize
#获得服务端返回的可下载文件列表并一一显示
while filesize:
filepath = CliSock.recv(struct.calcsize('256si'))
file_name, ffsize= struct.unpack('256si', filepath)
file_name = file_name.decode('utf-8').strip('\0')
print(file_name+' ------ ' +str(ffsize//1024+1)+'KB')
fdict[file_name] = ffsize
filesize = filesize - 1
print ('\n'+'the sum of the file is %d' %filenum + '\n')
file = input('Select the file you want to download: '+'\n')
#将想下载的文件名传输给服务端以便获得服务端发送的该文件具体信息
fhead = struct.pack('128si', file.encode(), fdict[file])
CliSock.send(fhead)
#接收服务端发送来的文件详细信息
files = CliSock.recv(struct.calcsize('256si'))
if files:
fname, fsize = struct.unpack('256si', files)
fs = fname.decode().strip('\0')
#新文件的存储名
newfile = os.path.join('new_' + fs)
fp = open(newfile, 'wb')
print('Its receiving..., wait a minute...'+'\n')
resize = 0
#获取每次服务端发送来的文件内容,并逐一写入新文件中
#判断不足1024时要只写入剩下的字节,否则会产生错码
while not resize == fsize:
if fsize - resize > BUF_SIZE:
data = CliSock.recv(BUF_SIZE)
resize = resize + len(data)
else:
data = CliSock.recv(fsize - resize)
resize = fsize
fp.write(data)
fp.close()
print('receive successfully')
#CliSock.close()
#break
elif type == '2':
data = input('input the dir of the file you want to upload: '+'\n')
#选择要上传的文件名, 若文件名错误或不存在则重新输入
while not os.path.isfile(data):
data = input('Wrong! Please input again: '+'\n')
fhead = struct.pack('256si',os.path.basename(data).encode(), os.stat(data).st_size)
CliSock.send(fhead)
fp = open(data, 'rb')
while True:
fdata = fp.read(BUF_SIZE)
if not fdata:
print('send successfully')
break
CliSock.send(fdata)
fp.close()
#CliSock.close()
#break
#退出客户端与服务端的连接
elif type == 'exit':
CliSock.close()
print('shut down the connection with server successfully! ')
break
else:
type = input('wrong input. Please input again: '+'\n')