python 黏包

黏包

黏包的起因:

连续send多个小数据,会发生黏包现象,这个是tcp协议优化算法造成的

当发送一个数据超过本次接收的最大范围之后,剩下的数据会留到下次接收时接收

黏包的现象:

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept()
ret = conn.recv(2)   #在这里先接收一个两字节的内容
ret2 = conn.recv(10) #然后在这里在接收一个长度为10字节的内容
print(ret)
print(ret2)
conn.close()
sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
sk.send(b'helloeve')  #在这里发送一个长度为8的字节
sk.close()

b'he'
b'lloeve'

可以看见接收的内容被分开了

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept()
ret1 = conn.recv(12)  #在这里接收两次
print(ret1)
ret2 = conn.recv(12)
print(ret2)
conn.close()
sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8090))
sk.send(b'hello')  #这里连续发送两次数据
sk.send(b'eve')
sk.close()

b'helloeve'
b''

原本预计的接收两个内容被合成一个接收了

这就是黏包

所以,黏包的根本原因就是,接收方不知道本次接收的数据具体大小,才会发生黏包

如何解决黏包

竟然知道是什么原因才发生黏包,那么,只要每次发送数据前,告诉对方我要发多少数据不就行了

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    conn.send(cmd.encode('gbk'))
    num = conn.recv(1024).decode('utf-8')  #将接到的长度赋给一个变量
    conn.send(b'ok')                        #想用户端发送消息表示已经接收到长度
    res = conn.recv(int(num)).decode('gbk')  # 将接收到的长度值给接收字节数
    print(res)
conn.close()
sk.close()
import socket
import subprocess      #调用subprocess模块
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd,shell=True,
                     stdout=subprocess.PIPE,    #将取到的东西放到管道里
                     stderr=subprocess.PIPE)    # PIPE 管道
    std_out = res.stdout.read()       #将管道里的数据赋给一个变量
    std_err = res.stderr.read()       #因为管道里的东西只能取一次
    sk.send(str(len(std_out)+len(std_err)).encode('utf-8'))   #叫测量的长度发给服务端
    sk.recv(1024)       #接收服务端的反馈
    sk.send(std_out)    # 将数据发过去
    sk.send(std_err)
sk.close()

这样每次发送前都将本次要发送的数据字节数发过去,让对方对应接收就不会发生黏包现象了

不过,虽然不会黏包,但是每次发送都需要一次交互,增加了代码的工作

怎么才能一次交互就解决这些问题呢

这时候就要用到

struct模块

struct模块可以把一个类型转成固定长度的bytes

我们这次只需要用到int

import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    conn.send(cmd.encode('gbk'))
    num = conn.recv(4)               #接受客户端的传来的
    num = struct.unpack('i',num)[0]  #反向得到字节数
    res = conn.recv(int(num)).decode('gbk')  #将字节数传给接收端当参数
    print(res)               
conn.close()
sk.close()
import struct    #这时候就需要用到struct模块
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd,shell=True,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
    std_out = res.stdout.read()
    std_err = res.stderr.read()
    len_num = len(std_out)+len(std_err)   #将计算的字节数赋给一个变量
    num_by = struct.pack('i',len_num)     #在将字节数用strect模块转成长度为4的bytes
    sk.send(num_by)                       #然后将它发给服务端
    sk.send(std_out)
    sk.send(std_err)
sk.close()
在网络上传输的所有数据 都叫数据包
数据包里的所有数据 都叫报文
报文里不止有你的数据 还有 ip地址 mac地址 端口号

所有的报文 都有 报头
posted @ 2018-01-29 20:10  GrandDarkness  阅读(164)  评论(0编辑  收藏  举报