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 粘包的主要内容,如果未能解决你的问题,请参考以下文章

第三模块:面向对象&网络编程基础 第2章 网络编程

Netty进阶——粘包与半包(短链接方式解决粘包问题)

Netty进阶——粘包与半包(短链接方式解决粘包问题)

Netty进阶——粘包与半包(代码示例)

Netty进阶——粘包与半包(固定长度方式解决粘包问题)

Netty进阶——粘包与半包(固定长度方式解决粘包问题)