远程执行命令与粘包问题
一.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能传的只有字节