[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)
[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)
简介
简单版本
函数版本
面向对象版本
简介
到此为止网络编程基础的介绍已经接近尾声了,而在本篇当中我们会基于上一篇博客代码的基础上来实现文件传输功能。文件传输其实与远程执行命令的程序原理是一摸一样的,比如下载文件的过程:
- 客户端提交命令
- 服务器端接收命令,解析和执行下载文件的方法,即以读的方式打开文件,for 循环读出文件的一行行内容,然后用 send() 发送给客户端
- 客户端以写的方式打开文件,将接收的内容写入文件中
简单版本
目录结构:
./简单版本/
| -- client/
| | -- download/ # 用于存放客户端从服务器端下载的文件
| | -- share/ # 用于存放客户端需要上传到服务器端的文件
| | -- 客户端.py
|
| -- server/
| | -- put/ # 用于存放客户端上传上来的文件
| | -- share/ # 用于存放服务器端提供下载的文件
| | -- 服务器端.py
服务器端:
import socket
import struct
import json
import os
ip_port = ('127.0.0.1',8080)
res_size = 8096
share_dir = r'G:\joveProject\socket\文件传输\简单版本\server\share'
put_dir = r'G:\joveProject\socket\文件传输\简单版本\server\put'
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(ip_port)
server.listen(5)
print('starting...')
while True: # 链接循环
conn, client_addr = server.accept()
print(client_addr)
while True: # 通讯循环
try:
# 1、收命令
res = conn.recv(res_size) # 这里的命令格式是由你,程序开发者定义,是需要用户按规定使用的
if not res: break
# 2、解析命令,提取相应命令参数
cmds = res.decode('utf-8').split() # ['get', 'a.txt']
filename = cmds[1]
if cmds[0] == 'get':
# 3、以读的方式打开文件,读取文件内容发送给客户端
# 第一步: 制作报头
header_dic = { # 使用字典,解决了报头信息少的问题
'filename': filename,
'md5': 'xxxxdxxx',
'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
# 第二步: 先发送报头长度
conn.send(struct.pack('i',len(header_bytes))) # 字典的bytes的长度很小,'i'已经足够使用了
# 第三步: 再发报头
conn.send(header_bytes)
# 第四步: 再发送真实的数据
with open('%s/%s' % (share_dir, filename), 'rb') as f:
for line in f: # 每读出文件当中一行数据就发一行,每次内存当中就只有一行
conn.send(line)
elif cmds[0] == 'put':
# 3、以写的方式打开一个新文件,接收客户端上传的文件内容
# 第一步: 先收报头长度
obj = conn.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步: 再接收报头
header_bytes = conn.recv(header_size)
# 第三步: 从报头中解析出真实数据的描述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['file_size']
# 第四步: 接收真实数据
with open('%s/%s' % (put_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = conn.recv(1024)
f.write(line)
recv_size += len(line)
except ConnectionResetError:
break
conn.close()
server.close()
客户端:
import socket
import struct
import json
import os
ip_port = ('127.0.0.1',8080)
info_size = 1024
download_dir = r'G:\joveProject\socket\文件传输\简单版本\client\download'
share_dir = r'G:\joveProject\socket\文件传输\简单版本\client\share'
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client.connect(ip_port)
while True:
# 1、发命令
cmd = input('>>: ').strip()
if not cmd:continue
client.send(cmd.encode('utf-8'))
if cmd.split()[0] == 'get':
# 2、以写的方式打开一个新文件,接收服务端发来的文件内容,写入客户的新文件中
# 第一步: 先收报头的长度
obj = client.recv(4)
header_size = struct.unpack('i',obj)[0]
# 第二步: 再收报头
header_bytes = client.recv(header_size)
# 第三步: 从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
filename = header_dic['filename']
total_size = header_dic['file_size']
# 第四步: 接收真实的数据
with open('%s/%s' % (download_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(info_size)
f.write(line)
recv_size += len(line) # 计算真实的接收长度,如果以后增加打印进度条的时候就可以精确无误的表示
print(int((recv_size / total_size) * 100), '%')
elif cmd.split()[0] == 'put':
# 2、解析命令,提取参数
cmds = cmd.split()
filename = cmds[1]
# 3、以读的方式打开文件,读取文件内容上传给服务器
# 第一步: 制作报头
header_dic = {
'filename': filename,
'md5': 'xxxxxxx',
'file_size': os.path.getsize('%s/%s' % (share_dir, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
# 第二步: 先发送报头长度
client.send(struct.pack('i', len(header_bytes)))
# 第三步: 再发报头
client.send(header_bytes)
# 第四步: 再发送真实数据
with open('%s/%s' % (share_dir, filename), 'rb') as f:
send_size = 0
for line in f:
client.send(line)
send_size += len(line)
print(((send_size / header_dic['file_size']) * 100), '%')
client.close()
代码效果如下:
下载:
上传:
函数版本
函数版本主要的变化是函数把代码封装成了一个函数,可以更加高效的复用,组织结构相对于简单版本更加好了,可扩展性也更加强了。
目录结构:
./函数版本/
| -- client/
| | -- download/ # 用于存放客户端从服务器端下载的文件
| | -- share/ # 用于存放客户端需要上传到服务器端的文件
| | -- 客户端.py
|
| -- server/
| | -- put/ # 用于存放客户端上传上来的文件
| | -- share/ # 用于存放服务器端提供下载的文件
| | -- 服务器端.py
服务器端:
import socket
import struct
import json
import os
share_dir = r'G:\joveProject\socket\文件传输\函数版本\server\share'
put_dir = r'G:\joveProject\socket\文件传输\函数版本\server\put'
def get(conn, cmds):
filename = cmds[1]
# 3、以读的方式打开文件,读取文件内容发送给客户端
# 第一步: 制作报头
header_dic = { # 使用字典,解决了报头信息少的问题
'filename': filename,
'md5': 'xxxxdxxx',
'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
# 第二步: 先发送报头长度
conn.send(struct.pack('i', len(header_bytes))) # 字典的bytes的长度很小,'i'已经足够使用了
# 第三步: 再发报头
conn.send(header_bytes)
# 第四步: 再发送真实的数据
with open('%s/%s' % (share_dir, filename), 'rb') as f:
for line in f: # 每读出文件当中一行数据就发一行,每次内存当中就只有一行
conn.send(line)
def put(conn, cmds):
filename = cmds[1]
# 3、以写的方式打开一个新文件,接收客户端上传的文件内容
# 第一步: 先收报头长度
obj = conn.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步: 再接收报头
header_bytes = conn.recv(header_size)
# 第三步: 从报头中解析出真实数据的描述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['file_size']
# 第四步: 接收真实数据
with open('%s/%s' % (put_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = conn.recv(1024)
f.write(line)
recv_size += len(line)
def run():
ip_port = ('127.0.0.1',8080)
res_size = 8096
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(ip_port)
server.listen(5)
print('starting...')
while True: # 链接循环
conn, client_addr = server.accept()
print(client_addr)
while True: # 通讯循环
try:
# 1、收命令
res = conn.recv(res_size) # 这里的命令格式是由你,程序开发者定义,是需要用户按规定使用的
if not res: break
# 2、解析命令,提取相应命令参数
cmds = res.decode('utf-8').split() # ['get', 'a.txt']
if cmds[0] == 'get':
get(conn, cmds)
elif cmds[0] == 'put':
put(conn, cmds)
except ConnectionResetError:
break
conn.close()
server.close()
if __name__ == '__main__':
run()
客户端:
import socket
import struct
import json
import os
download_dir = r'G:\joveProject\socket\文件传输\函数版本\client\download'
share_dir = r'G:\joveProject\socket\文件传输\函数版本\client\share'
def get(client, info_size):
# 2、以写的方式打开一个新文件,接收服务端发来的文件内容,写入客户的新文件中
# 第一步: 先收报头的长度
obj = client.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步: 再收报头
header_bytes = client.recv(header_size)
# 第三步: 从报头中解析出对真实数据的描述信息
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
filename = header_dic['filename']
total_size = header_dic['file_size']
# 第四步: 接收真实的数据
with open('%s/%s' % (download_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(info_size)
f.write(line)
recv_size += len(line) # 计算真实的接收长度,如果以后增加打印进度条的时候就可以精确无误的表示
print(int((recv_size / total_size) * 100), '%')
def put(client, cmds):
filename = cmds[1]
# 3、以读的方式打开文件,读取文件内容上传给服务器
# 第一步: 制作报头
header_dic = {
'filename': filename,
'md5': 'xxxxxxx',
'file_size': os.path.getsize('%s/%s' % (share_dir, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
# 第二步: 先发送报头长度
client.send(struct.pack('i', len(header_bytes)))
# 第三步: 再发报头
client.send(header_bytes)
# 第四步: 再发送真实数据
with open('%s/%s' % (share_dir, filename), 'rb') as f:
send_size = 0
for line in f:
client.send(line)
send_size += len(line)
print(((send_size / header_dic['file_size']) * 100), '%')
def run():
ip_port = ('127.0.0.1',8080)
info_size = 1024
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client.connect(ip_port)
while True:
# 1、发命令
cmd = input('>>: ').strip()
if not cmd:continue
client.send(cmd.encode('utf-8'))
# 2、解析命令,提取参数
cmds = cmd.split()
if cmd.split()[0] == 'get':
get(client, info_size)
elif cmd.split()[0] == 'put':
put(client, cmds)
client.close()
if __name__ == '__main__':
run()
代码效果如下:
下载:
上传:
面向对象版本
面向对象版本在函数版本的基础上进一步进行了封装,使代码模块化,可以把我们写的文件传输功能直接复制到其他项目当中使用,并且加入了面向对象之后我们所使用的数据和方法整合到了一起了,与函数版本相比程序的组织结构提高得更好,可扩展性更强。
目录结构:
./面向对象版本/
| -- client/
| | -- download/ # 用于存放客户端从服务器端下载的文件
| | -- share/ # 用于存放客户端需要上传到服务器端的文件
| | -- 客户端.py
|
| -- server/
| | -- put/ # 用于存放客户端上传上来的文件
| | -- share/ # 用于存放服务器端提供下载的文件
| | -- 服务器端.py
服务器端:
import socket
import struct
import json
import os
class Server:
""" 服务器 """
address_family = socket.AF_INET
address_reuse = True
socket_type = socket.SOCK_STREAM
share_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\share'
put_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\put'
max_listen = 5
recv_max_size = 8096
def __init__(self, server_address, bind_and_active = True):
self.server_address = server_address
self.socket = socket.socket(self.address_family, self.socket_type)
if bind_and_active:
try:
self.bind()
self.listen()
except:
self.socket.close()
raise
def bind(self):
""" socket绑定地址 """
if self.address_reuse:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
def listen(self):
""" socket监听 """
self.socket.listen(self.max_listen)
def accpet(self):
""" socket等待接收 """
return self.socket.accept()
def recv(self, conn):
""" 接收函数 """
return conn.recv(self.recv_max_size)
def connect_close(self):
""" 断开连接 """
self.conn.close()
def get(self, cmds):
"""
下载
:param cmds:
:return:
"""
filename = cmds[1]
header_dic = {
'filename': filename,
'md5': 'xxxxxx',
'file_size': os.path.getsize('%s/%s' % (self.share_dir, filename))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
self.conn.send(struct.pack('i', len(header_bytes)))
self.conn.send(header_bytes)
with open('%s/%s' % (self.share_dir, filename), 'rb') as f:
for line in f:
self.conn.send(line)
def put(self, cmds):
"""
上传
:param cmds:
:return:
"""
obj = self.conn.recv(4)
header_size = struct.unpack('i', obj)
header_bytes = self.conn.recv(header_size[0])
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['file_size']
with open('%s/%s' % (self.put_dir, header_dic['filename']), 'wb') as f:
rec_size = 0
while rec_size < total_size:
line = self.conn.recv(self.recv_max_size)
f.write(line)
rec_size += len(line)
def run(self):
""" run运行程序 """
while True: # 链接循环
self.conn, self.client_addr = self.accpet()
print(self.client_addr)
while True: # 通讯循环
try:
res = self.recv(self.conn)
if not res: break
cmds = res.decode('utf-8').split()
if hasattr(self, cmds[0]): # 通过反射找到对应的方法
getattr(self, cmds[0])(cmds)
except ConnectionResetError:
break
self.connect_close()
if __name__ == '__main__':
print('starting...')
ip_port = ('127.0.0.1', 8080)
server = Server(ip_port)
server.run()
server.socket.close()
客户端:
import socket
import struct
import json
import os
class Client:
""" 客户端 """
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
recv_max_size = 1024
download_dir = r'G:\joveProject\socket\文件传输\面向对象版本\client\download'
share_dir = r'G:\joveProject\socket\文件传输\面向对象版本\client\share'
def __init__(self,server_address,connect_and_active = True):
self.server_address = server_address
self.socket = socket.socket(self.address_family,self.socket_type)
if connect_and_active:
try:
self.connect()
except:
self.socket.close()
raise
def connect(self):
""" 与服务器创建链接 """
self.socket.connect(self.server_address)
def connect_close(self):
""" 断开连接 """
self.socket.close()
def get(self, cmds):
"""
接收下载数据
:return:
"""
obj = self.socket.recv(4)
header_size = struct.unpack('i', obj)
header_bytes = self.socket.recv(header_size[0])
header_json = header_bytes.decode('utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['file_size']
filename = header_dic['filename']
with open('%s/%s' % (self.download_dir, filename), 'wb') as f:
recv_size = 0
while recv_size < total_size:
line = self.socket.recv(self.recv_max_size)
f.write(line)
recv_size += len(line)
print(((recv_size / total_size) * 100), '%')
def put(self, cmds):
"""
上传数据到服务端
:type cmds:
:return:
"""
header_dic = {
'filename': cmds[1],
'md5': 'xxxxxx',
'file_size': os.path.getsize('%s/%s' % (self.share_dir, cmds[1]))
}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8')
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes)
with open('%s/%s' % (self.share_dir, cmds[1]), 'rb') as f:
send_size = 0
for line in f:
self.socket.send(line)
send_size += len(line)
print(((send_size / header_dic['file_size']) * 100), '%')
def run(self):
""" run运行程序 """
while True:
cmd = input('>>: ').strip()
if not cmd: continue
self.socket.send(cmd.encode('utf-8'))
cmds = cmd.split()
if hasattr(self, cmds[0]): # 通过反射找到对应的方法
getattr(self, cmds[0])(cmds)
if __name__ == '__main__':
ip_port = ('127.0.0.1', 8080)
client = Client(ip_port)
client.run()
client.socket.close()
代码效果如下:
下载:
上传: