第三十三天 远程执行命令与粘包问题

上周回顾:

1.三层结构

  一种程序的框架

  用户视图   与用户交互 接受和输出数据

  业务逻辑   复杂对数据进行  判断  验证  组装

  数据访问层 复杂处理数据的存取

2.异常处理

  异常处理的目的是为了 提高程序的健壮性(不容易奔溃)

  异常是? 发生错误前的一种信号,如果没有正确处理这种信号  程序终止执行  并抛出信息

  异常是哪个组成部分

    1.异常的追踪信息

    2.异常的类型

    3.异常的详细信息

    排除错误是 先查看异常类型和详细信息  最后根据追踪信息找到发生的位置进行修改!

  主动抛出异常

    raise 异常类型(消息)类型必须是Exception的子类

  

  断言
    assert 一个条件表达式 结果必须是bool
    当后续的逻辑执行前必须要保证某个条件成立时 使用
    也可以使用if 来手动抛出异常
    所以assert 就是为了简化代码

  语法: *****
  try:
    except 异常类型 | (多个类型,...) | 万能类型 as 别名:
  else:
  finally:
  使用场景:
    无法预知错误发生的原因时 或者 知道为什么错 但是程序无法解决


  网络编程:
  为了使程序能够分布在不同计算机上 本质问题是通讯:
  通讯的两个条件
  1.物理连接介质
  2.通讯协议 ******

  OSI 七层协议:
    应用层
    表示
    会话
    传输
    网络层
    数据链路
    物理层

  表示层和会话都属于应用层
    应用层 没有固定的协议 需要双方程序员商量
    传输 TCP/UDP port端口号 用于唯一标识一个应用程序
    网络层 ip协议 找到一个唯一的局域网 在找到局域网内的一台计算机
    数据链路层 以太网协议 发送二进制的格式 mac标识唯一一台计算机
    物理层 发送二进制

    我们需要关注的是传输层的协议 然而像TCP这种协议原理还是很复杂
    为了简化这些复杂操作 在传输层之上封装了socket抽象层
    这样一来 要完成网络通讯 就不需要在与底层的TCP/UDP协议打交道
    直接使用socket封装好的功能

二.今日内容

  1.TCP协议详解

  2.粘包问题

  3.粘包的解决方案

1.TCP的模板代码

  手法消息的循环  通讯循环

  不断的连接客户端循环  连接循环

  判断  用于判断客户端异常退出(抛异常)或close(死循环)

#客户端
import socket
c = socket.socket()
#连接服务器
c.connect("127.0.01",65535))
while True:
    #发送数据
    msg = input(">>:")
    if not msg:continue
    c.send(msg.encode("utf-8"))
    print("send!")
    #收数据
    data = c.recv(1024).decode("utf-8")
    print("receiver!")
    print(data)
c.close()


#服务器
import socket
#使用TCP 可以直接默认
server = socket.socket()
#指定端口 和ip端口 0 - 1023 是系统保留的
server.bind(("127.0.0.1",65535))

server.listen(5)
while True:
    
    c,addr = server.accept()
    while True:
        try:
            data = c.recv(1024).decode("utf-8")
            #如果客户端断开连接 结束循环
            if not data:
                print("client closed!")
                c.close()
                break
            print(data)
            c.send(data.upper().encode("utf-8")
        except ConnectionResetError:
            print("客户端异常关闭!!")
            c.close()
            break
server.close()

2.远程CMD
粘包问题
一方发送空数据 导致程序卡死 今后会通过多线程处理

# 服务器
#  1.服务器先启动 -> 客户端发送指令 -> 服务器接收后使用subprocess执行命令->将执行结果返回给客户端

import socket,subprocess
# 使用TCP 可以直接默认
server = socket.socket()

# 指定端口 和 ip     端口 0 - 1023是系统保留的
server.bind(("127.0.0.1",65535))

# 监听请求  参数为最大半连接数(三次握手未完成的请求  可能是服务器来不及 客户端恶意攻击)
server.listen(5)
# 为了可以不断的接受客户端连接请求
while True:
    # 接受连接请求
    c,addr = server.accept()
    # 为了可以重复收发数据
    while True:
        try:
            # 1024  程序的最大缓冲区容量   返回值类型为bytes类型
            cmd = c.recv(1024).decode("utf-8")
            # 如果客户端断开连接(客户端调用了close) recv 返回值为kong 此时应该结束循环
            if not cmd:# 在linux中 客户端异常关闭 服务器也会收空
                print("client closed!")
                c.close()
                break
            #解码
            print(cmd)
            # 执行命令
            p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 将错误信息和正确信息拼接到一起
            res = p.stdout.read() + p.stderr.read()
            print("执行结果长",len(res))
            # 将执行结果发送给客户端
            c.send(res)
        except ConnectionResetError:
            print("客户端异常关闭!!")
            c.close()
            break
# 关闭资源
server.close()


# TCP断开连接的正确姿势
# 客户端调用close
# 服务器判断如果接收数据为空则相应的调用close

#客户端1
import socket

c = socket.socket()

# 连接服务器
c.connect(("127.0.0.1",65535))

while True:
    # 发送数据
    msg = input(">>>:")
    if not msg:continue
    c.send(msg.encode("utf-8"))
    # while True:
        #     # 收数据
    data = c.recv(1024).decode("gbk")
    print(data)

# 关闭资源
c.close()

# 问题?  服务器发送的数据超过了接收端缓冲区大小 可直接修改大小来满足服务器传输的大小 但是不长远
# 上述问题 称之为粘包
# 思考: 循环每次读取一小部分 直到取完为止
# 什么时候可以结束循环  前提是让客户端直知道你的数据到底有多长
# 正确思路:
"""
    发送方
    1.先告诉对方你要发的数据的长度
    2.在发送真实数据
    
    接收方
    1.先接收数据的长度信息
    2.根据长度信息循环获取直到以获取的长度等于总长度
    
"""

#客户端2
import socket,time

c = socket.socket()

# 连接服务器
c.connect(("127.0.0.1",65535))

while True:
    # 发送数据

    c.send("dir".encode("utf-8"))
    time.sleep(1)
    c.send("dir".encode("utf-8"))

    data = c.recv(1024).decode("gbk")
    print(data)

# 关闭资源
c.close()

# 问题2 当客户端连续两行代码都发送一个dir时  服务器收到了一个dirdir
# 两个命令黏在一起
# TCP协议内的一个nagle算法  如果数据量小 并且时间间隔短会将数据合并一个包

3.解决粘包的方案 自定义报头
1.先用报头传输数据的长度
对于我们远程CMD程序来说 只要先传输长度就能解决粘包的问题
但是如果做得是一个文件上传下载 除了数据的长度 还需要传输文件的名字 md5等等信息
又该如何?

# 服务器
#  1.服务器先启动 -> 客户端发送指令 -> 服务器接收后使用subprocess执行命令->将执行结果返回给客户端

import socket,subprocess,struct
# 使用TCP 可以直接默认
server = socket.socket()

# 指定端口 和 ip     端口 0 - 1023是系统保留的
server.bind(("127.0.0.1",65535))

# 监听请求  参数为最大半连接数(三次握手未完成的请求  可能是服务器来不及 客户端恶意攻击)
server.listen(5)
# 为了可以不断的接受客户端连接请求
while True:
    # 接受连接请求
    c,addr = server.accept()
    # 为了可以重复收发数据
    while True:
        try:
            # 1024  程序的最大缓冲区容量   返回值类型为bytes类型
            cmd = c.recv(1024).decode("utf-8")
            # 如果客户端断开连接(客户端调用了close) recv 返回值为kong 此时应该结束循环
            if not cmd:# 在linux中 客户端异常关闭 服务器也会收空
                print("client closed!")
                c.close()
                break
            #解码
            print(cmd)
            # 执行命令
            p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 将错误信息和正确信息拼接到一起
            res = p.stdout.read() + p.stderr.read()
            print("执行结果长",len(res))


            # 1.先发送数据的长度
            data_len = len(res)
            # 长度是一个整型 需要转为字节  1000 b'\x001'  2000 b'\x001\x002'
            # 另外 需要保证 长度信息转换后的结果长度是固定的 否则客户端也会粘包(不知道取多少字节)
            # struct 模块负责将python中的数据类型 转为c语言中结构体
            # 整型转字节
            bytes_len = struct.pack("i",data_len)
            c.send(bytes_len)
            # 2.发送真实数据
            c.send(res)
        except ConnectionResetError:
            print("客户端异常关闭!!")
            c.close()
            break
# 关闭资源
server.close()


# TCP断开连接的正确姿势
# 客户端调用close
# 服务器判断如果接收数据为空则相应的调用close


#客户端
import socket,struct

c = socket.socket()

# 连接服务器
c.connect(("127.0.0.1",65535))

while True:
    # 发送数据
    msg = input(">>>:")
    if not msg:continue
    c.send(msg.encode("utf-8"))

    # 1.先获取长度
    bytes_len = c.recv(4) #对方是i格式 固定4字节
    # 2.转回整型
    total_len = struct.unpack("i",bytes_len)[0]
    # 已经接收的长度
    recv_len = 0
    # 一个表示最终数据的bytes
    finally_data = b''
    # 3.收到的长度小于总长度就继续
    while recv_len < total_len:
        # 循环收数据
        data = c.recv(1024)
        recv_len += len(data)
        finally_data += data
    # 整体解码
    print(finally_data.decode("gbk"))

# 关闭资源
c.close()

# 问题?  服务器发送的数据超过了接收端缓冲区大小 可直接修改大小来满足服务器传输的大小 但是不长远
# 上述问题 称之为粘包
# 思考: 循环每次读取一小部分 直到取完为止
# 什么时候可以结束循环  前提是让客户端直知道你的数据到底有多长
# 正确思路:
"""
    发送方
    1.先告诉对方你要发的数据的长度
    2.在发送真实数据
    
    接收方
    1.先接收数据的长度信息
    2.根据长度信息循环获取直到以获取的长度等于总长度
    
    自定义报头未讲
"""

2.自定义复杂报头 完成发送

#服务器
#  1.服务器先启动 -> 客户端发送指令 -> 服务器接收后使用subprocess执行命令->将执行结果返回给客户端

import socket,subprocess,struct,json
# 使用TCP 可以直接默认
server = socket.socket()

# 指定端口 和 ip     端口 0 - 1023是系统保留的
server.bind(("127.0.0.1",65535))

# 监听请求  参数为最大半连接数(三次握手未完成的请求  可能是服务器来不及 客户端恶意攻击)
server.listen(5)
# 为了可以不断的接受客户端连接请求
while True:
    # 接受连接请求
    c,addr = server.accept()
    # 为了可以重复收发数据
    while True:
        try:
            # 1024  程序的最大缓冲区容量   返回值类型为bytes类型
            cmd = c.recv(1024).decode("utf-8")
            # 如果客户端断开连接(客户端调用了close) recv 返回值为kong 此时应该结束循环
            if not cmd:# 在linux中 客户端异常关闭 服务器也会收空
                print("client closed!")
                c.close()
                break
            #解码
            print(cmd)
            # 执行命令
            p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 将错误信息和正确信息拼接到一起
            res = p.stdout.read() + p.stderr.read()
            print("执行结果长",len(res))

            # 1.组装一个报头信息
            head_dic = {
                "name":"仓老师视频教学 如何做炸鸡!",
                "md5":"asasasasaas",
                "total_size":len(res),
                "type":"video"
            }
            # 2.转json字符串
            head_str = json.dumps(head_dic)
            # 3.转字节
            head_bytes = head_str.encode("utf-8")
            # 4.发送报头长度
            bytes_len = struct.pack("i",len(head_bytes))
            c.send(bytes_len)
            # 5.发送报头
            c.send(head_bytes)
            # 6.发送真实数据
            c.send(res)

        except ConnectionResetError:
            print("客户端异常关闭!!")
            c.close()
            break
# 关闭资源
server.close()


# TCP断开连接的正确姿势
# 客户端调用close
# 服务器判断如果接收数据为空则相应的调用close

#客户端


#  1.服务器先启动 -> 客户端发送指令 -> 服务器接收后使用subprocess执行命令->将执行结果返回给客户端

import socket,subprocess,struct,json
# 使用TCP 可以直接默认
server = socket.socket()

# 指定端口 和 ip     端口 0 - 1023是系统保留的
server.bind(("127.0.0.1",65535))

# 监听请求  参数为最大半连接数(三次握手未完成的请求  可能是服务器来不及 客户端恶意攻击)
server.listen(5)
# 为了可以不断的接受客户端连接请求
while True:
    # 接受连接请求
    c,addr = server.accept()
    # 为了可以重复收发数据
    while True:
        try:
            # 1024  程序的最大缓冲区容量   返回值类型为bytes类型
            cmd = c.recv(1024).decode("utf-8")
            # 如果客户端断开连接(客户端调用了close) recv 返回值为kong 此时应该结束循环
            if not cmd:# 在linux中 客户端异常关闭 服务器也会收空
                print("client closed!")
                c.close()
                break
            #解码
            print(cmd)
            # 执行命令
            p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 将错误信息和正确信息拼接到一起
            res = p.stdout.read() + p.stderr.read()
            print("执行结果长",len(res))

            # 1.组装一个报头信息
            head_dic = {
                "name":"仓老师视频教学 如何做炸鸡!",
                "md5":"asasasasaas",
                "total_size":len(res),
                "type":"video"
            }
            # 2.转json字符串
            head_str = json.dumps(head_dic)
            # 3.转字节
            head_bytes = head_str.encode("utf-8")
            # 4.发送报头长度
            bytes_len = struct.pack("i",len(head_bytes))
            c.send(bytes_len)
            # 5.发送报头
            c.send(head_bytes)
            # 6.发送真实数据
            c.send(res)

        except ConnectionResetError:
            print("客户端异常关闭!!")
            c.close()
            break
# 关闭资源
server.close()


# TCP断开连接的正确姿势
# 客户端调用close
# 服务器判断如果接收数据为空则相应的调用close

额外的信息 例如文件名
1.将要发送的额外数据打包成一个字典
2.将字典转为bytes类型
3.计算字典的bytes长度 并先发送
4.发送字典数据
5.发送真实数据

服务器端示例:
# 为了方便存取 可以把需要的信息打包为一个字典
dic{
"filename":"仓老师视频教学 如何做炸鸡!",
"md5":"xzxbzxkbsa1212121",
"total_size":2121221
}
# 字典转字符串? json
head_dic = str(dict)
bytes = head_dic.encode("utf-8")
# 先发送这个字典字符串的长度
dic_len = len(head_dic)
#将长度转为了 字节
bytes_len = struct.pack("i",dic_len)
# 发送报头的长度
c.send(bytes_len)

# 发送真实数据
c.send(xxx.mp4.bytes)
TCP能传的只有字节

猜你喜欢

转载自www.cnblogs.com/gongcheng-/p/9911634.html