TCP 网络通讯进行大文件无损传输
TCP 传输小文件数据很简单,一次读取发送就OK了,但是大文件以及像多媒体数据除了要解决多次传输数据的数据读取合成还要解决编码问题;废话不多说直接上干货。
---- 亲测传输文本文件、图片、视频多媒体完美运行!!
TCP文件下载器-服务端-大文件版本
from socket import *
from tcp_transport_config import filler, valid_data_fixed_len, record_filler_len, data_package_size
import logging
import base64
logging.basicConfig(format='[%(name)s] %(levelname)s: %(message)s', level=logging.INFO)
logger = logging.getLogger("服务器")
def get_package_from_file(reader):
real_data = reader.read(valid_data_fixed_len)
if not real_data:
return None
# 获取读取到的数据长度,不足使用填充符填充
current_data_len = len(real_data)
# 获取填充数据
fill_len = valid_data_fixed_len - current_data_len
fill_data = (fill_len * filler).encode()
# record_data 用来记录数据包的信息
record_data = f"{fill_len:0>{record_filler_len}d}".encode()
send_data = real_data + fill_data + record_data
logger.debug(f"待发送数据包的 真实数据-填充-填充字符长度分别是: {len(real_data)} - {len(fill_data)} - {len(record_data)}")
# 将768个字节 utf8编码数据 转换1024个字节 base64编码数据
data_package = base64.b64encode(send_data)
return data_package
def send_file_content(tcp_socket):
file_name = tcp_socket.recv(data_package_size).decode()
try:
with open(file_name, "rb") as rf:
logger.info(f"正在加载文件 {file_name}.....")
while True:
# 读取文件数据包
data_package = get_package_from_file(rf)
if not data_package:
logger.info(f"{file_name} 传输完成!!")
break
tcp_socket.send(data_package)
logger.debug(f"数据包为{data_package} 长度:{len(data_package)}")
except Exception:
logger.warning(f"文件 {file_name} 不存在!")
def main():
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
tcp_server_socket.bind(("192.168.0.178", 8000))
tcp_server_socket.listen(128)
while True:
tcp_socket, client_addr = tcp_server_socket.accept()
logger.info(f"正在为{client_addr}服务!!")
send_file_content(tcp_socket)
tcp_socket.close()
logger.info(f"为{client_addr}服务结束!!")
tcp_server_socket.close()
if __name__ == '__main__':
main()
TCP文件下载器-客户端-大文件版本
from socket import *
from tcp_transport_config import record_filler_len, data_package_size
import logging
import base64
import io
logging.basicConfig(format='[%(name)s] %(levelname)s: %(message)s', level=logging.INFO)
logger = logging.getLogger("客户端")
def write_package_to_file(writer, data_package):
# 将1024个字节 base64编码数据解码为 768个字节utf8数据
recv_data = base64.b64decode(data_package)
logger.debug(f"接收的数据为: {recv_data}")
logger.debug(f"接收的数据长度为: {len(recv_data)}")
# 字节流读取数据
recv_data_stream = io.BytesIO(recv_data)
# 返回一个对应于缓冲区内容的可读写视图而不必拷贝其数据
recv_data_stream = recv_data_stream.getvalue()
# 获取填充字符的长度和记录填充字符所占用的长度 根据字节截取想要的数据
fill_char_len = recv_data_stream[-record_filler_len:].decode()
cut_len = int(fill_char_len) + record_filler_len
recv_real_data = recv_data[:-cut_len]
# 返回数据包的字节数据
writer.write(recv_real_data)
def main():
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
tcp_client_socket.connect(("192.168.0.178", 8000))
# 客户输入要下载的文件名, 设置文件下载的保存路径
download_filename = input("please enter file name: ")
save_filename = "copy_" + download_filename
# 发送需要下载文件名到服务器
tcp_client_socket.send(download_filename.encode())
data_package = tcp_client_socket.recv(data_package_size)
logger.debug(f"接收的第一个编码数据包长度: {len(data_package)}")
if data_package:
with open(save_filename, "wb") as wf:
while True:
write_package_to_file(wf, data_package)
data_package = tcp_client_socket.recv(data_package_size)
# 文件传输结束
if not data_package:
logger.info(f"{download_filename} 下载完成!!")
break
tcp_client_socket.close()
if __name__ == '__main__':
main()
配置文件 tcp_transport_config
def size_convert_utf8_to_base64(utf8_len):
fill_byte_num = 0
if utf8_len % 3 != 0:
fill_byte_num = 1
return (utf8_len // 3 + fill_byte_num) * 4
def size_convert_base64_to_utf8(base64_len):
return (base64_len // 4) * 3
# 填充字符
filler = "$"
# 进行数据传输 数据包的尺寸(单位为字节 <base64编码>) 256的倍数
data_package_size = 1024
# 计算数据包长度
data_fixed_len = size_convert_base64_to_utf8(data_package_size)
# 记录填充符个数的字符串占用的长度
record_filler_len = len(str(data_fixed_len))
# 计算数据包传输是有效数据的最大字节数
valid_data_fixed_len = data_fixed_len - record_filler_len
客户端运行界面:
服务端运行界面: