粘包问题

 



一、模拟 ssh

    • ============================= server.py ===================================
import subprocess
import socket

server_ip = ('127.0.0.1', 9999)
sk = socket.socket()
sk.bind(server_ip)
sk.listen(1)

while True:
conn, addr = sk.accept()
msg = bytes('welcome to my ssh server', encoding='utf-8')
conn.send(msg)

while True:
try:
cmd = conn.recv(1024).decode()
# client 主动进入 close()
if len(cmd) == 0:
raise Exception('客户端主动进入close状态')

# 获取cmd 执行结果( windows 为gbk编码后的byte;linux为utf-8编码后的byte)
shell_obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_data, stderr_data = shell_obj.communicate()   # communicate方法返回的是一个元组 (b'stdout;, b'stderro')

# 发送执行结果给client
if len(stdout_data) == 0:
conn.send(stderr_data)
else:
conn.send(stdout_data)        

except Exception as ex:
print(ex, 'connection broken....')
break
conn.close()
    • ============================= client.py ===================================
import socket

server_ip = ('127.0.0.1', 9999)

sk = socket.socket()
sk.connect(server_ip)

msg = sk.recv(1024).decode()
print(msg)

while True:
cmd_inp = input('>>').strip()
# 若为exit 则主动进入close状态,触发server  len(recv) == 0
if cmd_inp == 'exit':
break
# client不能发送空消息
if len(cmd_inp) == 0:
continue
# 发送要执行的cmd给server
sk.send(bytes(cmd_inp, encoding='utf-8')) 
# 接收cmd的执行结果
recv_data = sk.recv(1024).decode()  # 若为windows,则decode('gbk')
print(recv_data)

sk.close()

二、粘包问题:
  • 成因:
    • server 若有很大的数据流返回给client,而client在recv的时候却指定了每次最大1024字节(server也指定了1024 ,但几乎没有cmd能超过1024)
    • 即第二次socket取回的是第一次返回数据的残留数据
  • 解决思路1:保证传输一次,就传完所有数据               
    • recv 范围:1024-8192       
    • 以太网最大包传输 1500,千兆网卡,所以每次报文最大1500,即recv范围调大也没有用,超过1500,则会切割成多个报文发送
  • 解决思路2: ACK 校验
    • server传输前发送总共数据的长度,client不断循环接受,每次收1024并计数,当长度相等时,停止接受
  • exmaple:
    • ==================================== server ======================================
import socket
import subprocess

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)

while True:
conn, address = sk.accept()
conn.send(bytes('welcome to my ssh server'.center(40, '*'), encoding='utf-8'))

while True:
try:
# 接收 cmd
cmd = conn.recv(1024).decode()
if len(cmd) == 0:
raise Exception('链路状态改变,client go to close()')

# 获取cmd执行结果
obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout_data, stderror_data = obj.communicate()  # 本身执行结果就为字节,只不过windows上为GBK的字节,linux上为UTF-8的字节

if len(stdout_data) == 0:
send_data = stderror_data # 若标准输出管道中为空,则命令执行错误,应该将标准错误管道数据返回给用户
else:
send_data = stdout_data   # 若标准错误管道中为空,则命令执行正确,应该将标准输出管道数据返回给用户

# 发送len(data)
conn.send(bytes('begin:{}'.format(len(send_data)), encoding='utf-8'))
# 接收 ACK, 开始发送数据
ack = conn.recv(1024).decode()
if len(ack) == 0:
raise Exception('链路状态改变,ack 没有接收到')
if ack.startswith('start'):
conn.send(send_data)

except Exception as ex:
print(ex)
break

conn.close()
    • ==================================== client ======================================
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 9999,))

welcome_msg = sk.recv(1024).decode()
print(welcome_msg)

while True:
cmd = input('>> ').strip()
if len(cmd) == 0:
continue
if cmd == 'exit':
break

sk.send(bytes(cmd, encoding='utf-8'))

# 接收 len(data)
data_msg = sk.recv(1024).decode()
print('send: {}'.format(data_msg))  # 打印总共需要接收的数据大小

# 发送ACK,记录len(data)
if data_msg.startswith('begin'):
sk.send(bytes('start', encoding='utf-8'))
data_size= int(data_msg.split(':')[-1])

# 循环接收 data
msg = b''
recv_size = 0
while recv_size < data_size:
recv_data = sk.recv(1024)
msg += recv_data
recv_size += len(recv_data)
else:
print('recive: {}'.format(recv_size))  # 打印最终接收了多少数据大小

# 显示给client,windows上为gbk,linux上为utf-8
print(msg.decode('gbk'))

sk.close()






 

posted on 2016-09-05 14:47  台灯不太亮  阅读(171)  评论(0编辑  收藏  举报

导航