网络编程------粘包问题以及切换目录
一. 在py代码中如何调用操作系统的命令:
使用模块: subprocess
r = subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 基本样式: subprocess.Popen(cmd,shell=True,subprocess.stdout,subprocess.stderr) # cmd : 代表系统命令 # shell = True 代表这条命令是 系统命令,告诉操作系统,将cmd当成系统命令去执行 # stdout 是执行完系统命令之后,用于保存结果的一个管道 # stderr 是执行完系统命令之后,用于保存错误结果的一个管道 print(r.stdout.read().decode('gbk')) print(r.stderr.read().decode('gbk'))
客户端和服务端代码:
import socket sk = socket.socket() sk.connect_ex(('127.0.0.1',8080)) while 1: cmd = input('请输入一个命令>>>') sk.send(cmd.encode('utf-8')) result = sk.recv(102400).decode('gbk') print(result) sk.close()
import socket import subprocess sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() while 1: cmd = conn.recv(1024).decode('utf-8') r = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = r.stdout.read() stderr = r.stderr.read() if stderr: conn.send(stderr) else: conn.send(stdout) conn.close() sk.close()
二.粘包问题.
粘包问题只发生在TCP协议中, UDP协议不会发生.
1, 发生粘包问题的原因:
发送端发送数据时, 接收端不知道应该如何接收, 造成的一种数据混乱的现象.
TCP协议中发生粘包的两种原因:
合包机制(nagle 算法): 多次连续发送切间隔较小的数据, 进行打包成一块数据传
送, TCP协议在进行数据传输的时候是没有限制边界, 电脑在接受后不知道如何进
行划分, 从而发生粘包问题.
拆包机制: 在发送端,因为受到网卡的MTU限制, 会将大的超过MTU限制的数据进
行拆分,拆分成多个小数据进行传输, 当传输到目标主机的操作系统层时, 会重新将
多个小数据合并成元数据, (同理因为TCP传输的数据没有边界限制, 在最后一次传
输的数据没有达到传输的数据大小后, 会从下一个数据中自行补齐,从而造成粘包)
# MTU 限制一般是在1500个字节
注意: 拆包机制和合包机制均发生在发送端.
使用UDP协议发送数据,UDP不会发生粘包问题, UDP协议本层对一次收发数据大小
的限制是:
65535 - ip包头(20) - udp包头(8) = 65507
站在数据链路层: 因为网卡的MTU一般被限制在了1500, 所以对于数据链路层来说,
一次收发的数据大小被限制在 1500 - ip包头(20) - udp包头(8) = 1472
得到结论:
如果 sendto(num) ( 传输的内容)
num > 65507的时候, 会报错
1472<num<65507的时候, 会在数据链路层进行拆包, UDP本身就是不可靠协议, 所以一旦拆包之后, 造成的多个小数据包在网络传输中, 如果丢任何一个, 此次数据传输都会失败.
num< 1472 的时候是比较理想的状态
TCP协议粘包问题的解决:
1, 小文件传输时:
理念(以文件上传为例子): 先对操作进行一个判断,创建一个字典, 将要进行的操作
分别添加到字典中,然后使用os模块中的 os.path.bassname(文件名) 获得文件名称,
同时打开文件将内容读取出来传入到字典中.再然后将字典序列化后传输给服务器
进行存储.
# 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,"content":None} # file_path = input("请输入一个绝对路径:") # filename = os.path.basename(file_path) # 获取文件名 # with open(file_path,"r",encoding="utf-8") as f: # content = f.read() # dic["filename"] = filename # 将获取到的文件名添加到字典中 # dic["content"] = content # 将读取到的文件内容添加到字典中 # str_dic = json.dumps(dic) # 序列化字典 # sk.send(str_dic.encode("utf-8")) # 进行传输. # # 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(9090).decode("utf-8") # 接收客户端发送来的内容 # dic = json.loads(str_dic) # 进行反序列化 # if dic["opt"] == "upload": # 判断进行的操作步骤 # filename = "1"+ dic["filename"] # with open(filename,"w",encoding="utf-8") as f:
# # 创建文件并将文件内容写入到文件中 # f.write(dic["content"]) # elif dic["opt"] == "download": # 判断进行的操作 # pass # # conn.close() # sk.close()
大文件传输的解决粘包问题:原理:
原理: 与小文件传输时类似, 只是现在不是直接把文件内容写入字典,而是通过os模块中的
os.path.getsize() 获得文件大小后, 将文件大小写入字典, 先将字典序列化后传输给服务器,服
务器接收后, 根据文件大小,持续写入到文件中.(服务器传送的内容大小应和服务器端写入的时候的大小是对等的.)
服务端代码:
import socket import os import json import struct 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("请输入一个绝对路径:")# 文件的绝对路径 # E:\Python S14\day32\实现大文件的传输\11.mp4 filename = os.path.basename(file_path)# 文件名字 filesize = os.path.getsize(file_path)# 获取用户输入的路径中文件的大小 dic["filename"] = filename dic["filesize"] = filesize str_dic = json.dumps(dic) len_dic = len(str_dic)# 获取到字典的长度,是一个int类型的数据 46 146 b_len_dic = struct.pack('i',len_dic)# 用一个4bytes的数据表示字典的长度 sk.send(b_len_dic + str_dic.encode("utf-8"))# 将bytes类型的字典的长度 + bytes类型的字典的内容,一起发送给服务器 # 因为上边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 import struct sk = socket.socket() sk.bind(("127.0.0.1",8001)) sk.listen() conn,addr = sk.accept() b_len_dic = conn.recv(4) len_dic = struct.unpack('i',b_len_dic)[0]# 获取到int类型字典的长度, # unpack得到的是一个元组,要取下标为0的位置 str_dic = conn.recv(len_dic).decode('utf-8') # 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()
知识补充:
文件目录的切换:
import socket import os sk = socket.socket() sk.connect(('127.0.0.1',8080)) abs_path = input('请输入您的根目录:') sk.send(abs_path.encode('utf-8')) # 将路径栓输给服务端 current_dir = sk.recv(1024).decode('utf-8') # 接收服务端返回的当前根目录下的所有内容 print(current_dir.split('--')) # 将接收到的文件夹以及文件打印出来. while 1:
# 循环体, 编写 需要进行的操作 cmd = input('请输入>>>') # cd + 文件夹 .. if cmd == '..': sk.send(cmd.encode('utf-8')) current_dir = sk.recv(1024).decode('utf-8') print(current_dir.split('--')) if cmd == 'cd': filename = input('请输入一个文件夹名:') sk.send((cmd+' '+filename).encode('utf-8')) current_dir = sk.recv(1024).decode('utf-8') print(current_dir.split('--'))
切换目录服务端代码:
import socket import os sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() def send_data(conn,path): '''你给我一个目录,我把目录发给client''' lis_dir = os.listdir(path) str_dir = '--'.join(lis_dir) conn.send(str_dir.encode('utf-8')) abs_path = conn.recv(1024).decode('utf-8')# 获取用户输入的绝对路径 current_dir = abs_path + '/'
# 以下再处理,都要根据当前路径去处理,无论是返回上一层,还是进入下一层 send_data(conn,current_dir)# 把用户输入的路径下的所有文件及文件夹返回给客户端 # C:/Program Files (x86)/Common Files while 1: cmd = conn.recv(1024).decode('utf-8') if cmd == '..': current_dir = current_dir.split('/')[:-2] current_dir = '/'.join(current_dir)+'/' # if 如果当前是C盘: # 就返回给客户端告诉说没有上一层了! send_data(conn, current_dir) else: filename = cmd.split(' ')[1]# 获取用户输入的文件名字 current_dir += filename + '/'
# 将文件名字添加到当前路径下,组成一个完整的新路径 if os.path.isdir(current_dir):# 如果客户输入的文件名字是一个文件夹 send_data(conn, current_dir) else:# 如果不是一个文件夹 conn.send(b'bu shi wen jian jia') # conn.close() # sk.close()