后两篇的地址
https://blog.csdn.net/y363893017/article/details/105702848
https://blog.csdn.net/y363893017/article/details/105695328
本例子是单线程的,大家可以再服务端加上多线程
性能未测试,但是很操蛋,再我电脑的两个centos7.4的虚拟机上运行服务端,再win上上传同样的文件,一个秒传,另一个最快要2:3秒,慢都要2:15秒,
由于服务端和客户端的代码差不多,所以服务端做了详细的解释,客户端偶尔解释
#### 下午上传的代码有点小问题,现在修改后重新编辑一次
服务端
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
import socket
import pretty_errors # 一个错误模块,导入即可,它会把错误的信息显示的五颜六色的
import os.path
import time # 如果是linux服务器,则很有可能需要time.sleep(0.1),原因还不太清楚
import struct # 本来传输数字应该就这个模块的,但是不太熟,所以最终未用
import json
#import readline # 如果此脚本运行在linux服务器上,为了输入方便,则导入此模块
# 获取socket对象,如果觉得这样写麻烦,可以不用写在方法里面,直接写在外面即可
def sock_obj():
addr_port = ("0.0.0.0", 8989)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 关闭连接后马上释放端口
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr_port)
s.listen(5)
return s
# 这个方法用于接收文件的字节,目前是单线程的,一次只能有一个人上传
# 这里的想法是三步骤:
# 第一步:接收文件名字和大小的字节的长度(有点拗口哈,我也不知道怎么说,反正就是一个数字)
# 第二步:接收实际文件的名字和大小的数据
# 第三步:接收实际文件的内容
def recv_file(sock, addr, dirname): #这里的 addr参数可以不要,因为我这里未用
# 定义第第一次收到的数字的大小(主要不知道如何解释,看代码能看懂就行)
tcp_min_len = 0
# 定义第二次收到的消息的内容
tcp_head_msg = b""
# 这是第一次收到的消息,结果是一个str类型的数字(由于struct不熟,所以这里把int转换成str来传输)
tcp_head = sock.recv(1024)
tcp_head_len = struct.unpack(">H", tcp_head) #这里是下午注释的地方,研究一下后换成这种方法
time.sleep(0.1) #如果是linux系统的话,最好是延迟一下,win可以不用,因为我实测的时候linux会把几次的消息合并成一次接收,所以给一个延迟,原因未找到
# 循环接收信息,这里循环接收的是第二次收到的消息,这里要保证第二次收到的消息一定是完整的,如果这里收到的消息不完整,那么会影响后面真正的数据
while tcp_min_len < int(tcp_head_len[0]):
# 开始第二次接收数据
msg = sock.recv(1024)
# 把第二次接收到的消息的长度加到tcp_min_len,如果它们之和不小于int(tcp_head_len)的话就说明数据已经接收完了
tcp_min_len += len(msg)
# 把接收到的消息无限的拼接到tcp_head_msg
tcp_head_msg += msg
print(json.loads(tcp_head_msg.decode("utf-8")))
# 当第二次的数据接收完成后,这里就开始解码并且转换成python的dict类型,这里面的数据就是要传送文件的大小和名字
tcp_head_dict = json.loads(tcp_head_msg.decode("utf-8"))
file_min_len = 0
# i = 0 如果想看每次接收的数据,可以把这里和下面有关i的行的注释都去掉
# 打开一个文件
with open(os.path.join(dirname, tcp_head_dict["name"]), "wb") as wf:
# 循环接收到第三次发送的信息
while file_min_len < int(tcp_head_dict["size"]):
# 这里开始接收
file_data = sock.recv(4096)
#print("第%d次数据结果:%s" % (i, file_data))
# 。。。同上
file_min_len += len(file_data)
# 把数据写到文件
wf.write(file_data)
# i += 1
print("上传完毕")
if __name__ == "__main__":
while True:
#dir_name = input("please enter dir:")
#dir_name = "D:\dvd" # 如果觉得每次输入存放文件的目录麻烦,就用这个固定的吧。
dir_name = "/root" # 如果觉得每次输入存放文件的目录麻烦,就用这个固定的吧。
if not os.path.isdir(dir_name):continue
break
sock, addr = sock_obj().accept()
recv_file(sock, addr, dir_name)
客户端
大家可以看见里面每次send前都会time.sleep(0.1)一下,说实话为啥要这样我也不清楚,只是我看recv的信息的时候发现是连在一起的,所以加了sleep,有时候不加没问题,有时候有问题,所以我这里就选择加上了
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
import pretty_errors
import os, os.path
import socket
import json
import struct
import time
from tqdm import tqdm
def is_file(path):
if os.path.exists(path):
if os.path.isfile(path):
return True
else:
return False
else:
return False
def get_file_json(path):
file_size = os.path.getsize(path)
file_name = os.path.basename(path)
file_dict = {"size": file_size, "name": file_name}
return json.dumps(file_dict)
# 未使用此方法
def sock_obj():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((addr, int(port)))
return s
def tcp_link(sock):
buff_size = 4096
while True:
file_path = input("please enter file path:")
if not is_file(file_path):continue
break
file_struct = struct.pack(">H", len(get_file_json(file_path).encode("utf-8")))
sock.send(file_struct)
time.sleep(0.1)
sock.send(get_file_json(file_path).encode("utf-8"))
time.sleep(0.1)
with open(file_path, "rb") as rf:
for i in tqdm(range(int(json.loads(get_file_json(file_path))["size"]) // buff_size + 1)):
sock.send(rf.read(buff_size))
print("send end")
if __name__ == "__main__":
addr = "localhost"
port = 8989
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((addr, port))
tcp_link(s)
下图是我的测试结果,服务端是自己的阿里云不在大陆1M带宽,客户端就是我自己的电脑,上传忒慢。