粘包问题
一、模拟 ssh
- ============================= server.py ===================================
import subprocessimport 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')
# 发送执行结果给clientif len(stdout_data) == 0:conn.send(stderr_data)else:conn.send(stdout_data)
except Exception as ex:print(ex, 'connection broken....')breakconn.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) == 0if cmd_inp == 'exit':break# client不能发送空消息if len(cmd_inp) == 0:continue# 发送要执行的cmd给serversk.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 socketimport 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:# 接收 cmdcmd = 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:continueif 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])
# 循环接收 datamsg = b''recv_size = 0while recv_size < data_size:recv_data = sk.recv(1024)msg += recv_datarecv_size += len(recv_data)else:print('recive: {}'.format(recv_size)) # 打印最终接收了多少数据大小
# 显示给client,windows上为gbk,linux上为utf-8sk.close()print(msg.decode('gbk'))