网络编程之文件传输实例
一、文件传输包括两部分,服务端收发文件和客户端(即用户)收发文件。
收发文件与远程执行命令的程序原理是一样的,比如客户端下载文件的过程:
1、客户端提交下载命令;
2、服务端接收命令,解析,执行下载文件的方法,即以读的方式打开文件,利用for循环读出一行行内容,
然后发送(send)给客户端。
3、客户端以写的方式打开文件,将接受的文件内容接收写入文件中。
二、文件传输的几种模式
1、基础版
服务端文件夹应包括共享文件夹share和server.py
server.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct
import json
import os
share_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输基础版\服务端\share'
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',3361))
server.listen(5)
while True:
conn,client_addres = server.accept()
while True:
try:
cmd = conn.recv(1024)
if not cmd:break # 适用于linux
# 执行客户端的命令,并返回执行后的信息
# 1、解析客户端的指令,提取文件名
data = cmd.decode('gbk').split()
file_name = data[1]
# 1、制作报头
header_dic = {
'file_name':file_name, # 客户端需要下载的文件名
'md5':'xxxxxxxx',
'total_size':os.path.getsize('%s\%s'%(share_dir,file_name)) # 服务端对应文件的大小
}
# 2、编辑并发送报头长度
header_json = json.dumps(header_dic) # 报头字典转字符串
header_bytes = header_json.encode('gbk') # 字符串转bytes
conn.send(struct.pack('i',len(header_bytes)))# 发送报头长度
# 3、发送报头信息
conn.send(header_bytes)
# 4、发送真实数据
with open('%s\%s'%(share_dir,file_name),'rb') as f:
for line in f:
conn.send(line)
except ConnectionResetError:
break
conn.close()
server.close()
客户端文件夹应包括下载或上传文件夹down和client.py
client.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct
import json
downland_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输基础版\客户端\dowland'
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',3361))
while True:
cmd = input('输入下载的文件及后缀名>>:').strip()
if not cmd:continue
client.send(cmd.encode('gbk'))
# 等待接收
# 1、获取报头长度
obj = client.recv(4)
header_size = struct.unpack('i',obj)[0]
# 2、获取报头内容
header_bytes = client.recv(header_size)
# 3、解析报头,获取真实数据长度
header_data = header_bytes.decode('gbk') # bytes解码成字符串
header_dic = json.loads(header_data) # 字符串反序列化为字典(报头)
total_size = header_dic['total_size'] # 得到真实数据长度
file_name = header_dic['file_name']
# 4、接收真实数据
with open('%s\%s'%(downland_dir,file_name),'wb') as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(1024)
f.write(line)
recv_size += len(line)
print('文件总大小:%s\t已下载大小:%s'%(total_size,recv_size))
client.close()
2、函数版
服务端文件夹包括recieve、share文件夹和server.py
server.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct
import json
import os
share_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_函数版\服务端\share'
recieve_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_函数版\服务端\recieve'
def get(cmd,conn):
# 执行客户端的命令,并返回执行后的信息
# 1、解析客户端的指令,提取文件名
data = cmd.decode('gbk').split()
file_name = data[1]
# 1、制作报头
header_dic = {
'file_name': file_name, # 客户端需要下载的文件名
'md5': 'xxxxxxxx',
'total_size': os.path.getsize('%s\%s' % (share_dir, file_name)) # 服务端对应文件的大小
}
# 2、编辑并发送报头长度
header_json = json.dumps(header_dic) # 报头字典转字符串
header_bytes = header_json.encode('gbk') # 字符串转bytes
conn.send(struct.pack('i', len(header_bytes))) # 发送报头长度
# 3、发送报头信息
conn.send(header_bytes)
# 4、发送真实数据
with open('%s\%s' % (share_dir, file_name), 'rb') as f:
for line in f:
conn.send(line)
def put(cmd,conn):
'''
获取用户上传的内容
:param cmd:
:param conn:
:return:
'''
# 1、接收报头的大小
obj = conn.recv(4)
header_size = struct.unpack('i', obj)[0]
# 2、接收报头的信息
head_bytes = conn.recv(header_size)
# 3、解析报头信息并获取真实数据的大小
head_data = head_bytes.decode('gbk') # bytes-->str
head_dic = json.loads(head_data) # str-->dict
file_name = head_dic['file_name']
total_size = head_dic['total_size']
# 4、接收真实数据
with open('%s\%s'%(recieve_dir,file_name),'wb') as f:
recv_size = 0
while recv_size < total_size:
line = conn.recv(1024)
f.write(line)
recv_size += len(line)
print('文件总大小为:%s\t已接收的文件大小为:%s'%(total_size,recv_size))
def run():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8889))
server.listen(5)
while True:
conn,client_addres = server.accept()
while True:
try:
cmd = conn.recv(1024)
data = cmd.decode('gbk').split()
if data[0] == 'get':
get(cmd,conn)
elif data[0] == 'put':
put(cmd,conn)
except ConnectionResetError:
break
conn.close()
server.close()
if __name__ == '__main__':
run()
客户端文件夹包括dowland、share文件夹和client.py
client.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct
import json
import os
downland_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_函数版\客户端\dowland'
share_dir = r'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_函数版\客户端\share'
def get(client,cmd):
'''
用户下载功能
:param client:
:param cmd:
:return:
'''
# 等待接收
# 1、获取报头长度
obj = client.recv(4)
header_size = struct.unpack('i',obj)[0]
# 2、获取报头内容
header_bytes = client.recv(header_size)
# 3、解析报头,获取真实数据长度
header_data = header_bytes.decode('gbk') # bytes解码成字符串
header_dic = json.loads(header_data) # 字符串反序列化为字典(报头)
total_size = header_dic['total_size'] # 得到真实数据长度
file_name = header_dic['file_name']
# 4、接收真实数据
with open('%s\%s' % (downland_dir, file_name), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(1024)
f.write(line)
recv_size += len(line)
print('文件总大小:%s\t已下载大小:%s' % (total_size, recv_size))
def put(client,cmd):
'''
用户上传功能
:param cmd:
:return:
'''
# 1、制作报头
file_name = cmd.split()[1]
file_dic = {
'file_name':file_name,
'md5':'xxxxxxxx',
'total_size':os.path.getsize('%s\%s'%(share_dir,file_name))
}
# 2、编辑并发送报头长度
header_json = json.dumps(file_dic)
head_bytes = header_json.encode('gbk')
client.send(struct.pack('i',len(head_bytes)))
# 3、发送报头信息
client.send(head_bytes)
# 4、发送真实数据信息
with open('%s\%s'%(share_dir,file_name),'rb') as f:
for line in f:
client.send(line)
# print('文件总大小为:%s\t已上传文件大小为:%s'%(file_dic['total_size'],))
def run():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8889))
while True:
cmd = input('输入下载(get)或上传(put)的文件及后缀名>>:').strip()
if not cmd:continue
client.send(cmd.encode('gbk'))
res = cmd.split()[0]
if res == 'get':
print('你正在进行下载操作...')
get(client,cmd)
elif res == 'put':
print('你正在进行上传操作...')
put(client,cmd)
client.close()
if __name__ == '__main__':
run()
3、面向对象版
服务端包括server_db文件夹(用于存放文件)和server.py
server.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct
import json
import os
class My_server:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_addres = False # 端口重用
max_size = 8192 # 最大单次接收字节数目
coding = 'utf-8' # 编码,windows系统,mac或linux使用utf-8
listen_queue = 5 # 监听数
#server_dir = 'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_面向对象\服务端\server_db' # 服务器文件目录
server_dir = 'server_db'
def __init__(self,server_addres,bind_and_active=True):
'''由构造函数调用来绑定套接字'''
self.server_addres = server_addres
self.socket = socket.socket(self.address_family,self.socket_type)
if bind_and_active:
try:
self.server_bind()
self.server_active()
except:
self.server_close()
raise
def server_bind(self):
'''服务器socket套接字绑定方法'''
if self.allow_reuse_addres:
self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 设置端口重用
self.socket.bind(self.server_addres)
self.server_addres = self.socket.getsockname() # 获取本地端点的地址
def server_active(self):
'''由构造函数调用以激活服务器 '''
self.socket.listen(self.listen_queue)
def get_request(self):
'''从套接字获取请求和客户端地址'''
return self.socket.accept()
def close_request(self,request):
'''调用以清理单个请求'''
request.close()
def server_close(self):
'''调用以清理服务器'''
self.socket.close()
def run(self):
'''入口程序'''
while True:
self.conn,self.client_addres = self.get_request()
print('from client:',self.client_addres)
while True:
try: # 异常处理
head_struct = self.conn.recv(4)#接收报头
#if not head_struct:break # linux适用
print(head_struct)
head_len = struct.unpack('i',head_struct)[0] # 获取报头字典长度
head_json = self.conn.recv(head_len).decode(self.coding) #接收字典并转成字符串
head_dic = json.loads(head_json) # 将字符串转为字典
print(head_dic)
cmd = head_dic['cmd']
if hasattr(self,cmd): # 反射,判断是否有字符串对应的方法
func = getattr(self,cmd) # 获取字符串对应的方法
func(head_dic) # 执行对应方法
except Exception:
break
def get(self,dic):
'''执行用户输入的下载命令'''
print(dic)
cmd = dic['cmd']
file_name = dic['filename'] # 获取下载的文件名
file_path = os.path.normpath(os.path.join(self.server_dir,file_name)) # 获取文件位置
if not os.path.isfile(file_path):
print('文件%s不存在,请核对后再输入!'%file_name)
else:
file_size = os.path.getsize(file_path) # 获取文件大小
# 制作报头
head_dic = {
'cmd':cmd,
'filename':file_name,
'filesize':file_size,
'md5':'xxxxxx'
}
head_str = json.dumps(head_dic) # 字典转为字符串
head_bytes= head_str.encode(self.coding) # 字符串转为bytes
head_struct = struct.pack('i',len(head_bytes)) # 打包成固定字节长度的串
# 发送报头长度
self.conn.send(head_struct)
# 发送报头
self.conn.send(head_bytes)
# 发送正式数据
send_size = 0
with open(file_path,'rb') as f:
while send_size<file_size:
for line in f:
self.conn.send(line)
send_size += len(line)
print('文件总大小:%s,已发送文件:%s'%(file_size,send_size/file_size))
def put(self,dic):
'''接收用户上传的文件'''
file_path = os.path.normpath(os.path.join(self.server_dir,dic['filename'])) #设置存放文件的位置
file_size= dic['filesize'] # 获取上传文件的大小
recv_size = 0
with open(file_path,'wb') as f:
while recv_size<file_size:
recv_data = self.conn.recv(self.max_size)
f.write(recv_data)
recv_size+=len(recv_data)
print("文件总大小:%s,已接收文件大小:%s"(file_size,recv_size))
myserver = My_server(('127.0.0.1',3301))
if __name__ == '__main__':
myserver.run()
客户端包括client_db文件夹(存放文件)和client.py
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import json
import os
import struct
class My_client:
addres_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_addres = False # 端口重用
max_size = 8192 # 单次最大接收或发出文件数据量
coding = 'utf-8' # 文件或命令编码格式
listen_queue = 5
#client_dir = 'D:\pycharm\Test1\Thrid_module\网络编程\文件传输\文件传输优化_面向对象\客户端\client_db'
client_dir = 'client_db'
def __init__(self,server_addres,connect=True):
'''由构造函数调用来绑定套接字'''
self.server_addres = server_addres
self.socket = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
if connect:
try:
self.client_connect() # 调用链接方法
except:
self.client_connect()
raise
def client_connect(self):
'''链接方法'''
self.socket.connect(self.server_addres)
def client_close(self):
'''关闭链接方法'''
self.socket.close()
def run(self):
'''客户端程序入口'''
while True:
res = input('输入get\put 文件名+后缀>>').strip()
if not res:continue
str =res.split()
cmd = str[0] # 获取get或put命令
file_name = str[1]
if hasattr(self,cmd):
func = getattr(self,cmd)
func(str)
def get(self,res):
'''执行下载方法'''
cmd = res[0] # 得到下载命令
file_name = res[1] # 得到文件名
# 制作报头
head_dic ={
'cmd':cmd,
'filename':file_name,
'md5':'xxxxx'
}
print(head_dic)
head_str = json.dumps(head_dic) # 字典转字符串
head_byte= head_str.encode(self.coding) # 字符串转byte
head_stuct = struct.pack('i',len(head_byte)) # byte打包为指定字节长度的包
# 发送报头长度
self.socket.send(head_stuct)
# 发送报头里的数据
self.socket.send(head_byte)
# 接收下载信息的报头
head_recv_struct = self.socket.recv(4)
# 解析报头,获取下载文件的大小
head_recv_size = struct.unpack('i',head_recv_struct)[0]
# 接收下载信息的报头数据,并解码
head_recv_data = self.socket.recv(head_recv_size).decode(self.coding)
# 将解码的字符串反序列化为字典
head_recv_dic = json.loads(head_recv_data)
print(head_recv_dic)
# 获取待接收的文件的大小和文件名
file_size = head_recv_dic['filesize']
recv_file_name = head_recv_dic['filename']
# 接收下载数据
file_path = os.path.normpath(os.path.join(self.client_dir, file_name)) # 设定下载文件存放位置
recv_size = 0
with open(file_path,'wb') as f:
while recv_size < file_size:
recv_data = self.socket.recv(self.max_size)
f.write(recv_data)
recv_size += len(recv_data)
print('文件总大小:%s,已下载文件:%s'%(file_size,recv_size/file_size))
def put(self, str):
cmd = str[0] # 获得'put'
file_name = str[1]
file_path = os.path.normpath(os.path.join(self.client_dir, file_name))
# 制定报头
if not os.path.isfile(file_path):
# os.path.isfile(path) #判断路径是否为文件
print('%s is not exist' % file_name)
return
else:
file_size = os.path.getsize(file_path) # 获取文件大小
head_dic = {
'cmd': cmd,
'filename': os.path.basename(file_path),
'filesize': file_size,
} # 制作报头
print(head_dic)
head_json = json.dumps(head_dic) # 将字典转为字符串
head_bytes = bytes(head_json, encoding=self.coding) # 字符串转为bytes
print(head_bytes) # b'{"cmd": "put", "filename": "pic.jpg", "filesize": 7}'
head_struct = struct.pack('i', len(head_bytes)) # 打包成固定字节长度的bytes数据
self.socket.send(head_struct) # 发送报头长度
self.socket.send(head_bytes) # 发送报头
send_size = 0
with open(file_path, 'rb') as f:
for line in f:
self.socket.send(line)
send_size += len(line)
print("你上传的文件总大小为:%d\t已上传:%d%%" % (file_size, (send_size / file_size) * 100))
print('上传成功')
myclient = My_client(('127.0.0.1',3301))
if __name__ == '__main__':
myclient.run()
读书原为修身,正己才能正人正世;不修身不正己而去正人正世者,无一不是盗名欺世;你把念过的书能用上十之一二,就是很了不得的人了。——朱先生