网络编程------粘包问题以及切换目录

一. 在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()

 

 

 

 

posted @ 2018-08-20 17:10  向往灬  阅读(208)  评论(0编辑  收藏  举报