[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)

ops/2025/2/3 2:47:16/

[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)

简介

简单版本

函数版本

面向对象版本

简介

        到此为止网络编程基础的介绍已经接近尾声了,而在本篇当中我们会基于上一篇博客代码的基础上来实现文件传输功能。文件传输其实与远程执行命令的程序原理是一摸一样的,比如下载文件的过程:

  1. 客户端提交命令
  2. 服务器端接收命令,解析和执行下载文件的方法,即以读的方式打开文件,for 循环读出文件的一行行内容,然后用 send() 发送给客户端
  3. 客户端以写的方式打开文件,将接收的内容写入文件中

简单版本

目录结构:

./简单版本/

| --  client/

|      | --  download/         # 用于存放客户端从服务器端下载的文件

|      | --  share/               # 用于存放客户端需要上传到服务器端的文件 

|      | --  客户端.py

|

| --  server/

|      | --  put/                   # 用于存放客户端上传上来的文件

|      | --  share/               # 用于存放服务器端提供下载的文件

|      | --  服务器端.py

服务器端:

python">import socket
import struct
import json
import osip_port = ('127.0.0.1',8080)
res_size = 8096share_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 = 0while recv_size < total_size:line = conn.recv(1024)f.write(line)recv_size += len(line)except ConnectionResetError:breakconn.close()
server.close()

客户端:

python">import socket
import struct
import json
import osip_port = ('127.0.0.1',8080)
info_size = 1024download_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:continueclient.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 = 0while 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 = 0for 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

服务器端:

python">import socket
import struct
import json
import osshare_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 = 0while 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 = 8096server = 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:breakconn.close()server.close()if __name__ == '__main__':run()

客户端:

python">import socket
import struct
import json
import osdownload_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 = 0while 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 = 0for 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 = 1024client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)client.connect(ip_port)while True:# 1、发命令cmd = input('>>: ').strip()if not cmd:continueclient.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

服务器端:

python">import socket
import struct
import json
import osclass Server:""" 服务器 """address_family = socket.AF_INETaddress_reuse = Truesocket_type = socket.SOCK_STREAMshare_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\share'put_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\put'max_listen = 5recv_max_size = 8096def __init__(self, server_address, bind_and_active = True):self.server_address = server_addressself.socket = socket.socket(self.address_family, self.socket_type)if bind_and_active:try:self.bind()self.listen()except:self.socket.close()raisedef 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 = 0while 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: breakcmds = res.decode('utf-8').split()if hasattr(self, cmds[0]):  # 通过反射找到对应的方法getattr(self, cmds[0])(cmds)except ConnectionResetError:breakself.connect_close()if __name__ == '__main__':print('starting...')ip_port = ('127.0.0.1', 8080)server = Server(ip_port)server.run()server.socket.close()

客户端:

python">import socket
import struct
import json
import osclass Client:""" 客户端 """address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMrecv_max_size = 1024download_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_addressself.socket = socket.socket(self.address_family,self.socket_type)if connect_and_active:try:self.connect()except:self.socket.close()raisedef 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 = 0while 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 = 0for 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: continueself.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()

代码效果如下:

下载:

上传:


http://www.ppmy.cn/ops/155198.html

相关文章

数据分析系列--②RapidMiner导入数据和存储过程

一、下载数据 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从本地选择.csv或.xlsx 三、界面说明 四、存储过程 1.保存 Congratulations, you are done. 一、下载数据 点击下载AssociationAnalysisData.xlsx数据集 二、导入数据 1. 在本地计算机中创建3个文件夹 2. 从…

vue中的el是指什么

简介&#xff1a; 在Vue.js中&#xff0c;el指的是Vue实例的挂载元素。 具体来说&#xff0c;el是一个选项&#xff0c;用于指定Vue实例应该挂载到哪个DOM元素上。通过这个选项&#xff0c;Vue可以知道应该从哪个元素开始进行模板编译和渲染。它可以是一个CSS选择器字符串&…

【react+redux】 react使用redux相关内容

首先说一下&#xff0c;文章中所提及的内容都是我自己的个人理解&#xff0c;是我理逻辑的时候&#xff0c;自我说服的方式&#xff0c;如果有问题有补充欢迎在评论区指出。 一、场景描述 为什么在react里面要使用redux&#xff0c;我的理解是因为想要使组件之间的通信更便捷…

双层Git管理项目,github托管显示正常

双层Git管理项目&#xff0c;github托管显示正常 背景 在写React项目时&#xff0c;使用Next.js,该项目默认由git托管。但是我有在项目代码外层记笔记的习惯&#xff0c;我就在外层使用了git托管。 目录如下 code 层内也有.git 文件&#xff0c;对其托管。 我没太在意&…

日志2025.2.1

日志2025.2.1 1.做了敌人状态机 public class EnermyStateMachine { public EnermyState currentState { get; private set; } public void InitializeState(EnermyState startState) { currentState startState; currentState.Enter(); } public void Change…

C#面向对象(封装)

1.什么是封装? C# 封装 封装 被定义为“把一个或多个项目封闭在一个物理的或者逻辑的包中”。 在面向对象程序设计方法论中&#xff0c;封装是为了防止对实现细节的访问。 抽象和封装是面向对象程序设计的相关特性。 抽象允许相关信息可视化&#xff0c;封装则使开发者实现所…

Python之Excel操作 - 读取数据

我们将使用 openpyxl 库&#xff0c;它是一个功能强大且易于使用的库&#xff0c;专门用于处理 Excel 文件。 1. 安装 openpyxl 首先&#xff0c;你需要安装 openpyxl 库。你可以使用 pip 命令进行安装&#xff1a; pip install openpyxl2. 读取 Excel 文件 要读取 Excel 文…

conda配置channel

你收到 CondaKeyError: channels: value https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main not present in config 错误是因为该镜像源&#xff08;https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main&#xff09;可能没有被正确添加到 Conda 的配置文件中&…