ssh 粘包
Posted lxx7
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ssh 粘包相关的知识,希望对你有一定的参考价值。
import subprocess #模块 res = subprocess.Popen(cmd.decode(‘utf-8‘),shell=True, #紫色-终端命令 stdout=subprocess.PIPE, #输出 stdin=subprocess.PIPE, #输出 stderr=subprocess.PIPE) #错误
stderr = res.stderr.read()
stdin = res.stderr.read()
stdout = res.stderr.read()
大致了解上面的模块,我先举一个 ssh 的例子
#服务端
import socket #from socket import *#用哪一种都可以 import subprocess ip_port=(‘127.0.0.1‘,8888) #ip地址和端口数 BUFSIZE=1024 #一次性最多接受的字节数量 server = socket.socket() server.bind(ip_port) server.listen(5) #最大排队数 while True: #循环等待链接 conn,addr = server.accept() #等待客户端来链接 ,conn是客户端的套接字对象 print(‘客户端‘,addr) while True: cmd = conn.recv(BUFSIZE) #循环等待客户端发来终端命令 if len(cmd) == 0:break res=subprocess.Popen(cmd.decode(‘utf-8‘),shell=True, #P要大写 stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) stderr=res.stderr.read() #如果对方发来不存在终端命令,那就数报错,就把错误信息回复对方 stdout=res.stdout.read() #把执行过的内容发给对方 conn.send(stderr) conn.send(stdout) #直接读出来的就是用gbk进行编码的字节,到客户端再利用gbk进行解码
##客户端
import socket BUFSIZE = 1024 ip_port = (‘127.0.0.1‘,8888) clint = socket.socket() #参数位空默认tcp协议 clint.connect(ip_port) while True: msg = input(‘>>: ‘).strip() if len(msg) == 0: continue if msg == ‘quit‘: break clint.send(msg.encode(‘utf-8‘)) #send 是不能发空的 act_res = clint.recv(BUFSIZE) print(act_res.decode(‘utf-8‘),end=‘‘)
上面就是ssh的流程,结束了
***下面讨论一下粘包 (只有TCP协议会发生粘包,UDP不会发生)
发生粘包的两种情况
情 况一 :发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
情况二: 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
from socket import * ip_port=(‘127.0.0.1‘,8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次没有收完整 data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 print(‘----->‘,data1.decode(‘utf-8‘)) print(‘----->‘,data2.decode(‘utf-8‘)) conn.close()
总结:表面上看,粘包的问题主要是发送方和接受方的缓存机制,TCP协议面向流通信的特点
实际上:主要是接收方不知道消息之间的界限,不知道一次性提取多少字节suoy
引起的.
在说明粘包的解决方案之前,先说一下struct模块
import struct res = struct.pack("i",11111)#第二个参数必须是int类型 print(res,len(res))#b‘g+x00x00‘ 4
下面举个例子
我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
发送时 接收时
先发报头长度 先收报头长度,用struct取出来
再编码报头内容然后发送 根据取出的长度收取报头内容,然后解码,反序列化
最后发真实内容 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
#为避免粘包,必须自定制报头 header={‘file_size‘:1073741824000,‘file_name‘:‘/a/b/c/d/e/a.txt‘,‘md5‘:‘8f6fbf8347faa4924a76856701edb0f3‘} #1T数据,文件路径和md5值 #为了该报头能传送,需要序列化并且转为bytes head_bytes=bytes(json.dumps(header),encoding=‘utf-8‘) #序列化并转成bytes,用于传输 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节 head_len_bytes=struct.pack(‘i‘,len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度 #客户端开始发送 clint = socket.socket() clint.connect((‘127.0.0.1‘,8888)) clint.send(head_len_bytes) #先发报头的长度,4个bytes clint.send(head_bytes) #再发报头的字节格式 clint.sendall("文件内容") #然后发真实内容的字节格式 #服务端开始接收 #在此段代码只是模仿了客户端和服务端怎样发送接收,并没有创建服务端, head_len_bytes = s.recv(4) #先收报头4个bytes,得到报头长度的字节格式 x=struct.unpack(‘i‘,head_len_bytes)[0] #提取报头的长度 head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式 header=json.loads(json.dumps(header)) #提取报头 #最后根据报头的内容提取真实的数据,比如 real_data_len=s.recv(header[‘file_size‘]) s.recv(real_data_len)
下面的例子是真的代码哈哈哈
#服务端
import socket,struct,json import subprocess server = socket.socket() server.bind((‘127.0.0.1‘,8080)) server.listen(5) while True: conn,addr = server.accept() while True: cmd = conn.recv(1024) if not cmd:break print(‘cmd: %s‘ %cmd) res = subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) err = res.stderr.read() print(err) if err: back_msg = err else: back_msg = res.stdout.read() conn.send(struct.pack(‘i‘,len(back_msg))) #先发back_msg的长度 conn.sendall(back_msg) #在发真实的内容 conn.close()
#3接收端
import socket,struct clint = socket.socket() clint.connect((‘127.0.0.1‘,8080)) cmd = input("请输入命令:")
if len(cmd) == 0:
continue
if cmd == "quit"
break
clint.send(cmd.encode("utf8")) head = clint.recv(4) head_len = struct.unpack("i",head)[0] date_len = 0 #date = ""
date = b"
while date_len < head_len: date_num = clint.recv(1024) date += date_num
#date += date_num.decode("gbk") #Windows终端默认gbk编码,其实字节也可以相加,最后在解码 date_len += len(date_num) #print(date)
print(date.decode("gbk) clint.close()
#用紫色的逻辑
说一下sendall和send的区别
send()的返回值是发送的字节数量,这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
socket.sendall(string[, flags])
尝试发送string的所有数据,成功则返回None,失败则抛出异常
如果客户端异常退出,服务端会在与客户端此次通话报错,因为服务端不知道客户端异常退出了
举个例子
以上是关于ssh 粘包的主要内容,如果未能解决你的问题,请参考以下文章