day028 黏包及黏包解决方案

一.关于cmd黑窗口里面的快捷方法:

 输入文件的前几个关键字,加tab就可以出现神奇的效果!

二.粘包现象.(1,缓冲区.2.关于subprocess模块)

关于缓冲区:

如何查看输入和输出缓冲区 server.getsockopt():

关于subprocess.Popen():

在一些复杂场景中,我们需要将一个进程的执行输出作为另一个进程的输入。在另一些场景中,我们需要先进入到某个输入环境,然后再执行一系列的指令等。这个时候我们就需要使用到suprocess的Popen()方法。该方法有以下参数:

args:shell命令,可以是字符串,或者序列类型,如list,tuple。

bufsize:缓冲区大小,可不用关心

stdin,stdout,stderr:分别表示程序的标准输入,标准输出及标准错误

shell:与上面方法中用法相同

cwd:用于设置子进程的当前目录

env:用于指定子进程的环境变量。如果env=None,则默认从父进程继承环境变量

universal_newlines:不同系统的的换行符不同,当该参数设定为true时,则表示使用\n作为换行符

import subprocess
cmd = input('请输入指令>>>')
res = subprocess.Popen(
    cmd,                     #字符串指令:'dir','ipconfig',等等
    shell=True,              #使用shell,就相当于使用cmd窗口
    stderr=subprocess.PIPE,  #标准错误输出,凡是输入错误指令,错误指令输出的报错信息就会被它拿到
    stdout=subprocess.PIPE,  #标准输出,正确指令的输出结果被它拿到
)
print(res.stdout.read().decode('gbk'))
print(res.stderr.read().decode('gbk'))

 stderr:标准错误

stdout:标准正确

关于粘包现象:

tcp粘包现象中的第一种:接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)发生数据错误.
tcp粘包现象第二种:发送数据时间间隔很短,数据也很小,会合到一起,产生粘包,发生数据错误.

 粘包的原因:主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,
然后接收端发一个确认消息给发送端,然后发送端再发送过来后面的真实内容,接收端再来一个死循环接收完所有数据。
解决方案(一):
服务端:
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) # std_bytes_len = bytes(str(len(std_msg)),encoding='utf-8') #首先将数据长度的数据类型转换为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
客户端:
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'))

 解决方案(二) struct模块 : (调用struct模块,少一步对方回复确认的过程,打包内容的长度,发送过去,对方也用struct解包长度,然后对方根据你发送的长度,调整每次缓冲区截取的长度,从而解决数据错误)

服务端:

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) #封装的数据为4个字节
    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) #由于统一用的是struct模块,所以知道对方发过来的第一组数据一定是4个字节,所以填入4
    msg_len = struct.unpack('i',server_res_len)[0] #因为打包的是个元组,所以索引是0
    print('来自服务端的消息长度',msg_len)
    #通过解包出来的长度,来接收后面的真实数据
    server_cmd_result = client.recv(msg_len)
    print(server_cmd_result.decode('gbk'))

关于struck的介绍:(作用:该模块可以把(-2147483648 <= number <= 2147483647)之间的数字转换成长度为4个字节的数据,进行发送.

目的就是,解决对方不知道自己发送的数据大小,而导致一次获取的过大的粘包现象,或错误现象.)

就是定义了一种结构,里面包含不同类型的数据(int,char,bool等等),方便对某一结构对象进行处理。
而在网络通信当中,大多传递的数据是以二进制流(binary data)存在的。当传递字符串时,不必担心太多的问题,而当传递诸如int、char之类的基本数据的时候,
就需要有一种机制将某些特定的结构体类型打包成二进制流的字符串然后再网络传输,而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据。
通过struck模块将需要发送的内容的长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节,
然后对这四个字节的数据进行解包,拿到你要发送的内容的长度,然后通过这个长度来继续接收我们实际要发送的内容

 

pack():#我在这里只介绍一下'i'这个int类型,上面的图中列举除了可以打包的所有的数据类型,并且struck除了pack和uppack两个方法之外还有好多别的方法和用法.

import struct
a=12
# 将a变为二进制
bytes=struct.pack('i',a) 
-------------------------------------------------------------------------------
struct.pack('i',1111111111111) 如果int类型数据太大会报错struck.error
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
# 注意,unpack返回的是tuple !!
a = struct.unpack('i',bytes)[0] #将bytes类型的数据解包后,得到一个元组,加个索引0,拿到int类型数据

 

猜你喜欢

转载自www.cnblogs.com/lgw1171435560/p/10222980.html
今日推荐