远程执行命令与粘包问题

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

二.远程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算法  如果数据量小 并且时间间隔短会将数据合并一个包

 

三.解决粘包的方案 自定义报头

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能传的只有字节

 

posted @ 2018-11-05 21:20  Milford  阅读(187)  评论(0编辑  收藏  举报