day 28 黏包及黏包解决方案

1.缓冲区
每个socket被创建以后,都会分配两个缓冲区,输入缓冲区和输出缓冲区,默认大小都是8k,可以通过getsocket()获取,暂时存放传输数据,防止程序在发送的时候卡阻,提高代码运行效率.
首先看python的系统交互subprocess:
import subprocess
 
sub_obj = subprocess.Popen(
    'ls',  #系统命令
    shell = True,  #固定格式
    stdout=subprocess.PIPE,  #标准输出  PIPE管道,保存着指令的执行结果
    stderr=subprocess.PIPE   #标准错误输出
)
 
print('正确输出',sub_obj.stdout.read().decode('gbk'))
print('错误输出',sub_obj.stderr.read().decode('gbk'))
 
 
#测试byte长度
# print(len(b'hello'))
# print(bytes(str(2),encoding='utf-8'))
结果编码是以但钱所在系统为准的,诺为windo,则用GBK解码,且只能从管道里读取一次结果
2.黏包现象
   1.tcp两种黏包现象:
   a. 发送端需要等缓冲区满了才发送出去,造成黏包(发送时间的间隔很短,数据也很小,会被底层优化算法河道一起,产生黏包现象)
server端的代码示例额如下:
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()
conn,addr = tcp_socket_server.accept()
#服务端连接接收两个信息
data1=conn.recv(10)
data2=conn.recv(10)
 
print('------>',data1.decode('utf-8'))
print('------>',data2.decode('utf-8'))
 
conn.close()
client端的实例如下:
import socket
 
BUFSIZE= 1024
ip_port = ('127.0.0.1',8080)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 
res = s.connect(ip_port)
 
s.send('hi'.encode('utf-8'))
s.send('meinv'.encode('utf-8'))
 b. 接受方没有及时接受缓冲区的包,导致多个包接受,(客户端发送一段数据,服务端只收了一小部分,服务区 下次接受的时候还是从缓冲区拿上次遗留的数据,产生黏包)第一次如果发送的数据大小2000B,接受端一次性接受大小为1024,这样就导致剩下的内容会被下一次recv接收到,导致结果的错乱.
server端的代码示例额如下:
import socket
import subprocess
 
 
server = socket.socket()
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn, addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
    print(from_client_cmd.decode('utf-8'))
 
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
 
    std_msg = sub_obj.stdout.read()
    print('指令的执行结果长度>>>>',len(std_msg))
 
    conn.send(std_msg)
client端的实例如下:
import socket
 
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input("请输入指令:")
 
    client.send(cmd.encode('utf-8'))
    server_cmd_result = client.recv(1024)
 
    print(server_cmd_result.decode('gbk'))
解决tcp黏包的方案有两种,第一种是了解一下,第二种是必须记住.
1.方案一:由于接受方不知道发送端将要传送的自接力的长度,导致接收的尾喉,可能接收不全,或者多接收另外一次发送的内容,把自己将要送的字节流总大小让对方知晓,然后接收方发一个确认消息给发送端,然后发送端在发送过来后面的真实数据,接收方在来呢接收完
server端的代码示例额如下:
import socket
import subprocess
 
server = socket.socket( )
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn,addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
 
    print(from_client_cmd.decode('utf-8'))#接收到客户端发送来的系统指令,我服务端通过subprocess模块到服务端自己的系统里面执行这条指令
 
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE, #正确结果的存放的位置
        stderr=subprocess.PIPE  #错误结果的存放的位置
    )
    # 从管道里面拿结果,通过subprocess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    std_msg = sub_obj.stdout.read()
    #为了解决黏包现象,我们统计一下消息的长度,现将消息的长度发送给客户端,客户端通过这个长度来接收后面我们要发送的真实数据
    std_msg_len = len(std_msg)
  #首先将数据长度的数据类型装换成bytes类型
    std_bytes_len = str(len(std_msg)).encode('utf-8')
    print('指令的执行结果长度>>>>>:',len(std_msg))
 
    conn.send(std_bytes_len)
    status = conn.recv(1024)
    if status.decode('utf-8') == 'ok':
 
        conn.send(std_msg)
    else:
        pass
client端的实例如下:
import socket
 
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input('请输入指令:')
 
    client.send(cmd.encode('utf-8'))
 
    server_res_len = client.recv(1024).decode('utf-8')
 
    print('来自服务端的消息长度',server_res_len)
 
    client.send(b'ok')
 
    server_cmd_result = client.recv(int(server_res_len))
 
    print(server_cmd_result.decode('gbk'))`
2.方案二: 通过struck模块将需要发送的内容长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节,然后对前4个字节的数据进行捷豹,拿到发送的长度,然后通过这个长度来继续接受我们要发送的内容.
struck模块的使用:struck模块中最重的两个函数就是pack()打包,unpack()解包
pack(): 我们这里只介绍'i'这个int类型,上面的途中列举了处理可以打包的所有的数据类型,并且struck处理pack和unpack两个方法之外还有好多别的方法和用法.
import struct
num = 100
# num太大的话会报错,
# struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
# 打包,将int类型的数据打包成4个长度的bytes类型的数据
byt = struct.pack('i',num)
print(byt)
# 解包,将bytes类型的数据,转换为对应的那个int类型的数据
# 注:unpack返回的是truple
int_num = struct.unpack('i',byt)[0]
print(int_num)
解决黏包现象的第二种方案:
服务端:
import socket
import subprocess
import struct
 
server = socket.socket()
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn,addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
    print(from_client_cmd.decode('utf-8'))
    #接收到客户端发送过来的系统指令,我服务端通过subprocess模块到服务端自己的系统里面执行这条指令
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE, #正确结果的存放位置
        stderr=subprocess.PIPE  #错误结果的存放位置
    )
    # 从管道里面拿出结果,通过subprocess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    std_msg = sub_obj.stdout.read()
    #为了解决黏包现象,我们统计了一下消息的长度,先将消息的长度发送给客户端,客户端通过这个长度来接收后面我们要发送的真实的数据
    std_msg_len = len(std_msg)
    print('指令的执行结果长度>>>>>>',len(std_msg))
 
    msg_lenint_struct =struct.pack('i',std_msg_len)
 
    conn.send(msg_lenint_struct+std_msg)
客户端:
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input('请输入指令:')
    #发送指令
    client.send(cmd.encode('utf-8'))
    #接收数据长度,首先接收4个字节长度的数据,因为这个4个字节是长度
    server_res_len=client.recv(4)
    msg_len= struct.unpack('i',server_res_len)[0]
 
    print('来自服务端的消息长度',msg_len)
    #通过解包出来的长度,来接收后面的真实数据
    server_cmd_result =client.recv(msg_len)
 
    print(server_cmd_result.decode('gbk'))
查看自己的缓存的大小
# print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF))
# print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF))
3. udp是面向包的,所以udp是不存在黏包的。
在udp代码中,我们在server端接受返回消息的时候,我们设置的recvfrom(1024),那么当我们输入的执行指令为'dir'的时候,若dir在当前文件夹下输出的内容大于1024,然后就报错了,
解释原因:因为udp是面向报文的,每个消息是一个包,接收端设置接受大小的时候,必须要比你发的这个 包要大,不然一次接受不了就会报错,而tcp是不会报错的,这也是为什么udp会丢包的原因
 打印进度条(简易)

 

import time
 
for i in range(20):
 
    print('\r' + i*'*',end='')
    time.sleep(0.2)
1.缓冲区
每个socket被创建以后,都会分配两个缓冲区,输入缓冲区和输出缓冲区,默认大小都是8k,可以通过getsocket()获取,暂时存放传输数据,防止程序在发送的时候卡阻,提高代码运行效率.
首先看python的系统交互subprocess:
import subprocess
 
sub_obj = subprocess.Popen(
    'ls',  #系统命令
    shell = True,  #固定格式
    stdout=subprocess.PIPE,  #标准输出  PIPE管道,保存着指令的执行结果
    stderr=subprocess.PIPE   #标准错误输出
)
 
print('正确输出',sub_obj.stdout.read().decode('gbk'))
print('错误输出',sub_obj.stderr.read().decode('gbk'))
 
 
#测试byte长度
# print(len(b'hello'))
# print(bytes(str(2),encoding='utf-8'))
结果编码是以但钱所在系统为准的,诺为windo,则用GBK解码,且只能从管道里读取一次结果
2.黏包现象
   1.tcp两种黏包现象:
   a. 发送端需要等缓冲区满了才发送出去,造成黏包(发送时间的间隔很短,数据也很小,会被底层优化算法河道一起,产生黏包现象)
server端的代码示例额如下:
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()
conn,addr = tcp_socket_server.accept()
#服务端连接接收两个信息
data1=conn.recv(10)
data2=conn.recv(10)
 
print('------>',data1.decode('utf-8'))
print('------>',data2.decode('utf-8'))
 
conn.close()
client端的实例如下:
import socket
 
BUFSIZE= 1024
ip_port = ('127.0.0.1',8080)
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 
res = s.connect(ip_port)
 
s.send('hi'.encode('utf-8'))
s.send('meinv'.encode('utf-8'))
 b. 接受方没有及时接受缓冲区的包,导致多个包接受,(客户端发送一段数据,服务端只收了一小部分,服务区 下次接受的时候还是从缓冲区拿上次遗留的数据,产生黏包)第一次如果发送的数据大小2000B,接受端一次性接受大小为1024,这样就导致剩下的内容会被下一次recv接收到,导致结果的错乱.
server端的代码示例额如下:
import socket
import subprocess
 
 
server = socket.socket()
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn, addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
    print(from_client_cmd.decode('utf-8'))
 
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
 
    std_msg = sub_obj.stdout.read()
    print('指令的执行结果长度>>>>',len(std_msg))
 
    conn.send(std_msg)
client端的实例如下:
import socket
 
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input("请输入指令:")
 
    client.send(cmd.encode('utf-8'))
    server_cmd_result = client.recv(1024)
 
    print(server_cmd_result.decode('gbk'))
解决tcp黏包的方案有两种,第一种是了解一下,第二种是必须记住.
1.方案一:由于接受方不知道发送端将要传送的自接力的长度,导致接收的尾喉,可能接收不全,或者多接收另外一次发送的内容,把自己将要送的字节流总大小让对方知晓,然后接收方发一个确认消息给发送端,然后发送端在发送过来后面的真实数据,接收方在来呢接收完
server端的代码示例额如下:
import socket
import subprocess
 
server = socket.socket( )
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn,addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
 
    print(from_client_cmd.decode('utf-8'))#接收到客户端发送来的系统指令,我服务端通过subprocess模块到服务端自己的系统里面执行这条指令
 
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE, #正确结果的存放的位置
        stderr=subprocess.PIPE  #错误结果的存放的位置
    )
    # 从管道里面拿结果,通过subprocess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    std_msg = sub_obj.stdout.read()
    #为了解决黏包现象,我们统计一下消息的长度,现将消息的长度发送给客户端,客户端通过这个长度来接收后面我们要发送的真实数据
    std_msg_len = len(std_msg)
  #首先将数据长度的数据类型装换成bytes类型
    std_bytes_len = str(len(std_msg)).encode('utf-8')
    print('指令的执行结果长度>>>>>:',len(std_msg))
 
    conn.send(std_bytes_len)
    status = conn.recv(1024)
    if status.decode('utf-8') == 'ok':
 
        conn.send(std_msg)
    else:
        pass
client端的实例如下:
import socket
 
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input('请输入指令:')
 
    client.send(cmd.encode('utf-8'))
 
    server_res_len = client.recv(1024).decode('utf-8')
 
    print('来自服务端的消息长度',server_res_len)
 
    client.send(b'ok')
 
    server_cmd_result = client.recv(int(server_res_len))
 
    print(server_cmd_result.decode('gbk'))`
2.方案二: 通过struck模块将需要发送的内容长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节,然后对前4个字节的数据进行捷豹,拿到发送的长度,然后通过这个长度来继续接受我们要发送的内容.
struck模块的使用:struck模块中最重的两个函数就是pack()打包,unpack()解包
pack(): 我们这里只介绍'i'这个int类型,上面的途中列举了处理可以打包的所有的数据类型,并且struck处理pack和unpack两个方法之外还有好多别的方法和用法.
import struct
num = 100
# num太大的话会报错,
# struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
# 打包,将int类型的数据打包成4个长度的bytes类型的数据
byt = struct.pack('i',num)
print(byt)
# 解包,将bytes类型的数据,转换为对应的那个int类型的数据
# 注:unpack返回的是truple
int_num = struct.unpack('i',byt)[0]
print(int_num)
解决黏包现象的第二种方案:
服务端:
import socket
import subprocess
import struct
 
server = socket.socket()
ip_port = ('127.0.0.1',8001)
 
server.bind(ip_port)
server.listen()
 
conn,addr = server.accept()
 
while 1:
    from_client_cmd = conn.recv(1024)
    print(from_client_cmd.decode('utf-8'))
    #接收到客户端发送过来的系统指令,我服务端通过subprocess模块到服务端自己的系统里面执行这条指令
    sub_obj = subprocess.Popen(
        from_client_cmd.decode('utf-8'),
        shell=True,
        stdout=subprocess.PIPE, #正确结果的存放位置
        stderr=subprocess.PIPE  #错误结果的存放位置
    )
    # 从管道里面拿出结果,通过subprocess.Popen的实例化对象.stdout.read()方法来获取管道中的结果
    std_msg = sub_obj.stdout.read()
    #为了解决黏包现象,我们统计了一下消息的长度,先将消息的长度发送给客户端,客户端通过这个长度来接收后面我们要发送的真实的数据
    std_msg_len = len(std_msg)
    print('指令的执行结果长度>>>>>>',len(std_msg))
 
    msg_lenint_struct =struct.pack('i',std_msg_len)
 
    conn.send(msg_lenint_struct+std_msg)
客户端:
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1',8001))
 
while 1:
    cmd = input('请输入指令:')
    #发送指令
    client.send(cmd.encode('utf-8'))
    #接收数据长度,首先接收4个字节长度的数据,因为这个4个字节是长度
    server_res_len=client.recv(4)
    msg_len= struct.unpack('i',server_res_len)[0]
 
    print('来自服务端的消息长度',msg_len)
    #通过解包出来的长度,来接收后面的真实数据
    server_cmd_result =client.recv(msg_len)
 
    print(server_cmd_result.decode('gbk'))
查看自己的缓存的大小
# print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF))
# print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF))
3. udp是面向包的,所以udp是不存在黏包的。
在udp代码中,我们在server端接受返回消息的时候,我们设置的recvfrom(1024),那么当我们输入的执行指令为'dir'的时候,若dir在当前文件夹下输出的内容大于1024,然后就报错了,
解释原因:因为udp是面向报文的,每个消息是一个包,接收端设置接受大小的时候,必须要比你发的这个 包要大,不然一次接受不了就会报错,而tcp是不会报错的,这也是为什么udp会丢包的原因
 打印进度条(简易)
import time
 
for i in range(20):
 
    print('\r' + i*'*',end='')
    time.sleep(0.2)
posted @ 2019-01-14 19:36  杨洪涛  阅读(158)  评论(0编辑  收藏  举报