1.大文件的上传与下载
- 基于tcp协议的通信
- 客户端只用传输信息,把信息装入字典,通过序列化将字典传给服务端,服务端实现功能;
- 服务端每次接受数据都是向操作系统要,而不是一个recv对应一个send
- 所以对于大文件可以按字节读,每次读取一个固定字节,通过os模块得到文件大小,文件大小累减到0,文件读完;
#客户端client import socket import os import json sk = socket.socket() sk.connect(("127.0.0.1",8001)) #让用户输入选项选择功能 menu = {"1":"upload","2":"download"} for k,v in menu.items(): print(k,v) num = input("请输入功能选项:") #上传功能 用户只管输入一些信息,存放在字典中,通过序列化发给服务端 #功能都是由服务端实现 if num == "1": dic = {"opt":menu.get(num),"filename":None,"filesize":None} file_path = input("请输入一个绝对路径:") # 文件的绝对路径 filename = os.path.basename(file_path) # 文件名字 filesize = os.path.getsize(file_path) # 获取用户输入的路径中文件的大小 #用于服务端接收的时候也要知道接收到多大才行 dic["filename"] = filename dic["filesize"] = filesize str_dic = json.dumps(dic) sk.send(str_dic.encode("utf-8"))# 将被填充完成的字典先发送给服务器 sk.recv(1024)# 为什么要有一个recv? # 因为上边send字典时,如果程序执行过快,可能会马上执行到下边的send(content) # 此时有可能会发生粘包,所以在此中间加一个recv,为了避免粘包 with open(file_path,"rb") as f: while filesize: content = f.read(1024) sk.send(content) filesize -= len(content) elif num == "2": pass
#服务端 import socket import json sk = socket.socket() sk.bind(("127.0.0.1",8001)) sk.listen() conn,addr = sk.accept() str_dic = conn.recv(100).decode("utf-8") #对于tcp读取多少字节都没有限制,而且对于单一的一个文件传输没有粘包现象 因为在传输过程中,每一段数据都是有编号的,缓存区会自动帮你拆包组合成数据; conn.send(b'ok')#为什么需要一个send,防止与下面的recv发生粘包 # str_dic = {"opt":menu.get(num),"filename":None,"filesize":None} dic = json.loads(str_dic) if dic["opt"] == "upload": filename = "1"+ dic["filename"] #避免文件名重复
with open(filename,"ab") as f: while dic['filesize']: content = conn.recv(1024) f.write(content) dic['filesize'] -= len(content) elif dic["opt"] == "download": pass conn.close() sk.close()
- 通过上面的代码可以看出,在客户端分别传输字典和数据时会容易发生粘包现象;
- 解决方法,如果知道字典有多长,直接可以先按长度接收了字典,再接收数据,引入struct模块
import struct a = {'a':'p','o':'a'} sa = len(a)#最大的长度是21亿 s = struct.pack('i',sa) #这里会将数据的长度打包成一个固定的值,一个四位点进制; print(s,len(s)) #这个会打包成一个bytes类型的数据 print(struct.unpack('i',s)) #拆包的时候,得到是一个元组,第一位是你数据的长度; #输出结果 b'x02x00x00x00' 4 (2,)
#第二版上传下载
#服务器
import socket import json import os import struct sk = socket.socket() sk.bind(('192.168.12.13',8090)) sk.listen() coon,addr = sk.accept() #通过struct模块将字典长度取出,再按长度接收字典 len_dic = coon.recv(4)#先将字典的长度取出 byte_dic = struct.unpack('i',len_dic)[0] str_dic = coon.recv(byte_dic).decode('utf-8') dic = json.loads(str_dic) print(dic) if dic['opt'] == 'uplaod': filename ='1' + dic['filename'] with open(filename,'ab')as f: while dic['filesize']: content = coon.recv(1024) f.write(content) dic['filesize'] -= len(content) elif dic['opt'] == 'download': dic1 = {'tuzhi.py':None,'120180817_095545.mp4':None} filese1 = os.path.getsize('tuzhi.py') filese2 = os.path.getsize('120180817_095545.mp4') dic1['tuzhi.py'] = filese1 dic1['120180817_095545.mp4'] = filese2 str_dic1 = json.dumps(dic1).encode('utf-8') coon.send(str_dic1) num = coon.recv(1024).decode('utf-8') ls = ['tuzhi.py','120180817_095545.mp4'] file1_size = os.path.getsize(ls[int(num)-1]) with open(ls[int(num)-1],'rb')as f1: while file1_size: s = f1.read(1024) coon.send(s) file1_size -= len(s) coon.close() sk.close()
#客户端 import socket import json import os import struct sk = socket.socket() sk.connect_ex(('192.168.12.13',8090)) #创建一个字典用来实现功能 menu = {'1':'uplaod','2':"download"} for k,v in menu.items(): print(k,v) cc = input('输入序号选择>>>:') #上传大文件 if cc == '1': #创建字典用来把用户的想要实现的功能装在字典中发给服务端 dic = {'opt':menu.get(cc),'filename':None,'filesize':None} filename = input("输入文件路径>>>:") file_path = os.path.basename(filename) file_size = os.path.getsize(filename) #填充字典 dic['filename'] = file_path dic['filesize'] = file_size #把字典作为字符串传输,通过struct模块避免粘包现象 str_dic = json.dumps(dic) len_dic = len(str_dic) byte_dic = struct.pack('i',len_dic) sk.send(byte_dic+str_dic.encode('utf-8')) #按字节读取文件 with open(filename,'rb')as f: while file_size: content = f.read(1024) #这里可以少不可以多 sk.send(content) file_size -= len(content) elif cc == '2': dic = {'opt': menu.get(cc), 'filename': None, 'filesize': None} str_dic = json.dumps(dic) len_dic = len(str_dic) byte_dic = struct.pack('i', len_dic) sk.send(byte_dic + str_dic.encode('utf-8')) dic_str = sk.recv(1024).decode('utf-8') dic1 = json.loads(dic_str) ls = [] for k,v in enumerate(dic1.keys(),1): ls.append(v) print(k,v) num = input("请用序号选择文件>>>:") filesize = dic1[ls[int(num)-1]] file_name = '666' sk.send(num.encode('utf-8')) with open(file_name,'ab')as f1: while filesize: filestr = sk.recv(1024) f1.write(filestr) filesize -= len(filestr) else: print('输入错误!') sk.close()
2.执行系统命令
1)在py中如何执行系统命令
- 在工作中有时会让查看当前服务器的状态,服务器一般都是租用大公司的,所以通过一个模块就能了解到当前服务器的状态
- 模块subprocess中的Popen方法,编码以当前系统为准,如果是windows,就用jbk解码编码,且只能从管道中读取一次结果;
#subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)的一些参数:
- cmd:代表系统命令
- shell=True代表这条命令是系统命令,告诉操作系统将cmd当做系统命令去执行
- subprocess.stdout:执行系统命令后返回的一条正确结果;
- subprocess.stderr:执行系统命令后返回的一条错误的结果;
#服务器
sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() coon,addr = sk.accept() while 1: mes = coon.recv(1024).decode('utf-8') result = subprocess.Popen(mes,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #这两个结果总会有一个为空 byte_out = result.stdout.read() byte_err = result.stderr.read() if not byte_err: coon.send(byte_out) else: coon.send(byte_err) coon.close() sk.close()
#客户端 sk = socket.socket() sk.connect_ex(('127.0.0.1',8090)) while 1: dir_out = input(">>>:") sk.send(dir_out.encode('utf-8')) #因为读取的是当前操作系统的内容,所以编码格式为gbk result = sk.recv(1023) print(result.decode('gbk')) sk.close()
3.切换目录
- 主要思想:将客户端输入的第一次绝对路径做为基础路径,在此上面做一些截取或则添加路径;
- 问题是:在创建encode编码解码类时,tcp的客户端和服务器的对象不一样,所以要分开写方法
- os模块中的listdir方法可以获取到当前路径下的所有文件和文件夹
- 在客户端输入cd进入下一层时,要判断下一层是否是文件夹;
#编码重复写编码和解码的过程 import socket #创建一个类基于socket类 class My_socket(socket.socket): def __init__(self, encond_ing='utf-8'): #先定义好一个默认值参数encond_ing,再调用父类方法 self.encond_ing = encond_ing super().__init__() #服务器的连接对象方法 def r_accept(self): coon, addr = self.accept() self.coon = coon #服务器通过链接对象接收和发送 def s(self, msg): return self.coon.send(msg.encode(self.encond_ing)) def r(self, num): str_msg = self.coon.recv(num).decode(self.encond_ing) return str_msg #服务器的关闭方法 def cllo(self): return self.coon.close() #客户端的接收和发送方法 def rc(self, num): str_msg = self.recv(num).decode(self.encond_ing) return str_msg def sc(self, msg): return self.send(msg.encode(self.encond_ing))
#服务器 from my_tcp import My_socket import os import json import struct sk = My_socket() sk.bind(('127.0.0.1',8090)) sk.listen() sk.r_accept() #等待连接, #接收到客户端第一次输入的绝对路径 str_msg = sk.r(1024) def send_data(path): ''' 向客户端发送目录列表 主要用到lisdir(绝对路径)下的所有目录 ''' lis_dir = os.listdir(path) #将列表序列化为字符串 str_dir = json.dumps(lis_dir) sk.s(str_dir) return lis_dir def up_file(path): ''' 向客户端发送..上一层目录的情况 切割客户端输入的绝对路径,因为在开始的时候加了一个/,所以列表中会有一个空字符 然后往上一层的话直接就取切割掉最后一位的绝对路径,再拼接为字符串再加一个/ ''' split_path = path.split('\')[:-2] split_path = '/'.join(split_path)+'/' lst = send_data(split_path) return split_path,lst def down_file(path,path_list,num): ''' 通过客户端输入序号进入下一级目录 :param path: str_dir :return: None ''' down_path = path +path_list[int(num)-1]+'/' list_dir = send_data(down_path) return down_path,list_dir #在返回上一层目录中,如果C:不加/,则进入到当前文件的目录下,所以避免这种情况 #则在开始的时候就加上/ current_dir = str_msg + '/' ls = send_data(current_dir) while 1: #struct模块避免发生粘包 len_menu = sk.r(4).encode('utf-8') len_menu = struct.unpack('i',len_menu)[0] menu = sk.r(len_menu) if menu == '..': current_dir,ls = up_file(current_dir) elif menu == 'cd': str_num = sk.r(1024) if os.path.isdir(current_dir + ls[int(str_num)-1]): current_dir, ls = down_file(current_dir,ls,str_num) continue else: s1 = json.dumps("这不是文件夹") sk.s(s1) continue else: sk.cllo() break sk.close()
#客户端 from my_tcp import My_socket import os import json import struct sk = My_socket() sk.connect_ex(('127.0.0.1',8090)) #先让客户端输入一个基础路径 #切换目录过程中都是以此为进行的 abs_path = input(">>>:") abs_path = os.path.abspath(abs_path) sk.sc(abs_path) str_dir = sk.rc(1024) list_dir = json.loads(str_dir) for k,v in enumerate(list_dir,1): print(str(k)+ ':' + v,end=' ') print() while 1: menu = input("选择功能>>>:") #避免发生粘包 len_menu = len(menu) slen = struct.pack('i',len_menu) sk.send(slen) sk.sc(menu) if menu.upper() == 'Q': break if menu == '..': str_dir = sk.rc(1024) list_dir = json.loads(str_dir) for k, v in enumerate(list_dir, 1): print(str(k)+ ':' + v, end=' ') print() elif menu == 'cd': num = input("按序号选择>>>:") sk.sc(num) str_dir = sk.rc(1024) list_dir = json.loads(str_dir) if type(list_dir) is str: print(list_dir) continue else: for k, v in enumerate(list_dir, 1): print(str(k)+ ':' + v, end=' ') print() else: print("输入错误!") continue sk.close()