スティックパック現象
.recv(1024)ピット:一度だけのrecvが1024バイトを受け入れるようにするのでデータは、1024bytes時間以上を転送する場合、パイプラインは送信これに戻ってつながるデータ処理の次のrecv→バックログを受信し続けるデータ→バックログを転送します結果は、結果の一部は、最後の内容を返すことがあり、
現象スティックパッケージ:TCPプロトコルメッセージに対応する表示されていない、すなわち、バイト数、(トランスポートストリーム全体のデータである)プロトコルストリームデータです。受信者は、データの混乱を作り、データを区切る方法がわからない受け入れるときにこれが結果。これは、スティックパック現象と呼ばれています
注:のみ、TCPパケットが粘着性の現象があり、UDPはパッケージスティックはありません
スティックパッケージ詳細説明センドとのrecv
まず、我々は強調する:
アプリケーション・ソフトウェアがハードウェアを直接操作することができない、ハードウェアがOSによって呼び出される必要があります。したがって、アプリケーションは、送信およびOSは、データ送信(アプリケーション層)のためのものであるrecvを。
他は完全にOSによって引き継がれている基本的なネットワークプロトコルを実現します。
- スティックのパッケージの概要:
送信またはrecvのは、データのローカルOSメモリではなく、ターゲット・サーバーとの直接データ交換のために交換されているかどうか
注意:あなたが唯一の対応が一度ではありませんrecvを送信することができ、任意の回数に対応することができ、任意の回数
- RECV、処理を送信します。
- RECV:
- データを待つ:OSは時間のかかるデータを待つ(待機する必要がありする必要があります)
- コピーデータ:データは、メモリ、OSのアプリケーションにコピーします
- 送信:
- データを送信する:メモリ、OSにアプリケーションソフトからのデータをコピーします
- スティックパッケージ:
- メモリの残りの部分のデータように、データをフェッチ受信すると、データパケットは、大きすぎます。
- データをフェッチ受信すると、(ネーグルによる)複数のパケットなどのデータパケットが受信されました。
- RECV:
これは、パッケージを固執する理由です:TCPは、マージされたパケットの送信を最適化するために、Nagleアルゴリズムをストリーミング
Nagleアルゴリズム:少量のデータパケット送信パケット組成物の比較的短い時間間隔
- ソリューション:
- バイトのパケットの正確番号を知っている、受信側が情報のパケットであることができ、事前に伝える→スティックパッケージを回避することが可能です
問題を解決するために、パッケージをスティック
私たちはsshのシミュレーションプログラムの説明を使用します。
何を私は大きなによって設定されたパラメータがrecvを、これは固定値であることができ、最大メモリ容量を超えることはありません、それは良い計画ではありません
- だから、どのように我々は、ケースを分析します:
- これらのメッセージは、お互いに送信され、そのデータを送信するときにしています
- 互いに送信データ長
- そして、お互いにデータを送信します
- これらのメッセージは、お互いに送信され、そのデータを送信するときにしています
server.py
import socket
import subprocess
import struct
import locale
ssh_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssh_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ssh_server.bind(('127.0.0.1', 8080))
ssh_server.listen(3)
while True:
conn, addr = ssh_server.accept()
while True:
try:
cmd = conn.recv(1024).decode('utf-8')
if not cmd: break
print("excute cmd:", cmd)
obj = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
os_locale = locale.getdefaultlocale()
os_encode = os_locale[1]
print(stdout.decode(os_encode))
# 第一步:生成数据的报头(报头:固定长度):需要用到struct模块
# 我们首先需要把发送数据的描述信息发送给客户端,但是客户端并不知道这个包是干什么的,而且会和数据的包粘包
# 所以我们把这段数据作为数据的报头发送
total_size = len(stdout) + len(stderr)
header = struct.pack('i', total_size) # 打包报头,打包返回的数据是一个固定长度(4bytes)的bytes类型
# struct.pack(模式,报头数据),注意:'i'模式int范围:正负2*10^9之内(因为只有4bytes)
# 另外有'l'模式:长整型。但是这个模式报头为8bytes。也有范围限制
# 第二部:发送报头
conn.send(header) # 因为是bytes类型所以可以直接发送
# 把命令结果返回给客户端
# conn.send(stdout+stderr) # 这里之前用+连接一起发送,但其实粘包也可以一起发送
conn.send(stdout) # 连续发送可以粘包
conn.send(stderr) # 与上一行粘包成一个数据包了
except ConnectionResetError as e:
print(e)
break
conn.close()
ssh_server.close()
client.py
import socket
import struct
ssh_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssh_client.connect(('127.0.0.1', 8080))
while True:
# 发命令
cmd = input(">>>>:").strip()
if not cmd:continue
ssh_client.send(cmd.encode('utf-8'))
# 第一步:先收报头
header = ssh_client.recv(4) # 因为报头固定4byte 所以我们收4
# 第二部:从报头解析出数据的描述(数据的长度)
total_size = struct.unpack('i', header)[0] # 解包报头,返回一个元组类型,这里我们第一个元素是长度
print(total_size) # 打印一下报头内容
# 第三部:接受真实收据
recv_size = 0
recv_data = b''
while recv_size < total_size:
res = ssh_client.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('cp932')) # 因为粘包问题解决了,这里我们直接打印
ssh_client.close()