网络编程基础之粘包现象

Posted 暮光微凉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程基础之粘包现象相关的知识,希望对你有一定的参考价值。

一、粘包现象原理分析

  1、我们先来看几行代码,从现象来分析:

    测试程序分为两部分,分别是服务端和客户端

    服务端.py 

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 # write by congcong
 4 
 5 
 6 import socket
 7 
 8 server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
 9 
10 server.bind((127.0.0.1,3306))
11 
12 server.listen(5)
13 
14 conn,client_addres = server.accept()
15 
16 # data1 = conn.recv(1024)
17 # print(‘客户端消息1:‘,data1) # 客户端消息1: b‘helloworld‘
18 # data2 = conn.recv(1024)
19 # print(‘客户端消息2:‘,data2) # 客户端消息2: b‘‘  客户端两次发送的信息被服务端第一次就全部接受了,即粘包

  客户端.py

 1 #!/usr/bin/env python3
 2 #-*- coding:utf-8 -*-
 3 # write by congcong
 4 
 5 import socket
 6 
 7 client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
 8 
 9 client.connect((127.0.0.1,3306))
10 
11 # client.send(‘hello‘.encode(‘utf-8‘))
12 # client.send(‘world‘.encode(‘utf-8‘))
13 #
14 # data1 = client.recv(3) # 接受3个字节的信息
15 # print(‘服务端消息1:‘,data1) # 服务端消息1: b‘hel‘
16 # data2 = client.recv(1024)
17 # print(‘服务端消息2:‘,data2) # 服务端消息2: b‘loserver‘  客户端两次接收信息混在一起,也发生了粘包

  由以上程序代码,我们不难发现,粘包发生的情况主要有两种:一种是第一次接收的字节数据小于发送的数据量,再次接收时,便会粘包;另一种是第

一次准备接收的字节数超过了发送的数据量,再次发送数据时,便会和第一次数据累积在一起,造成粘包。

 

  2、那么这到底是原因导致的呢?--->原因在于内部机制。

    主要有以下几点: 

1、不管 recv 还是 send 都不是直接接受对方的数据,而是操作自己的操作系统内存。
    --> 不是一定一个send对应一个recv(可以1对n,或者n对1)

2、recv:
        wait data 耗时长(一是等待程序发送,二是网络延迟)
   send:
        copy data 时间短
3、优化算法(Nagle算法),将多次时间间隔较短且数据量小的数据,合并成一个大的数据块, 然后封包发送。这样接收方就收到了粘包数据。

    那么,我们有什么解决方法没呢?

    答案肯定是有的,而且有好几种,接着往下看,任君挑选!

 

  3、解决粘包问题的几种方法

    按照方法的适用范围,可以分为以下几个阶段:

    1、凡人阶段

      粘包最简单解决方法,接收信息前事先已知信息的长度,指定接收长度。 

      服务端.py   

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong


import socket

server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

server.bind((127.0.0.1,3306))

server.listen(5)

conn,client_addres = server.accept()

conn.send(hello.encode(utf-8))
conn.send(server.encode(utf-8))

      客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket

client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

client.connect((127.0.0.1,3306))

data1 = client.recv(5)
print(服务端消息1:,data1) # 服务端消息1: b‘hello‘
data2 = client.recv(1024)
print(服务端消息2:,data2) # 服务端消息2: b‘server‘

    2、修仙阶段

      预备知识了解

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import struct

# struct.pack() 封装固定长度的报头,封装后的类型为bytes类型
res = struct.pack(i,2056) # i 表示数据类型,2056 表示真实数据的长度
print(res,type(res),len(res)) # b‘\x08\x08\x00\x00‘ <class ‘bytes‘> 4
res2 = struct.pack(i,1024)
print(res2,type(res2),len(res2)) # b‘\x00\x04\x00\x00‘ <class ‘bytes‘> 4

# struct.unpack() 解析报头数据,解析后的数据存储在一个元组内
data = struct.unpack(i,res) # i 仍然为数据类型,res为封装的包头
# print(data) # (2056,)
print(data[0]) # 2056 获取真实数据的长度

    服务端.py

技术分享图片
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import subprocess
import struct
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 实例化一个套接字对象
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 端口重用
server.bind((127.0.0.1,3231))  #  绑定IP和端口
server.listen(5)  # 监听

while True: # 链接循环
    conn,client_addres = server.accept()
    while True: # 通信循环
        try: # windows适用
            #1 接收命令
            cmd = conn.recv(8096)
            if not cmd:break # linux适用(未收到命令)
            # 2 执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode(gbk),shell=True,
                                   stdout=subprocess.PIPE, # 命令正确时创建一条通道
                                   stderr=subprocess.PIPE) # 命令错误时创建另一条通道
            stdout = obj.stdout.read() # 命令正确执行返回的信息
            stderr = obj.stderr.read() # 命令错误时返回的信息
            # print(‘from client‘,cmd)
            # 3 把结果返回给客户端
            # 3.1 制定固定长度的报头
            total_size = len(stdout) + len(stderr)
            header = struct.pack(i,total_size)
            # 3.2 发送报头信息
            conn.send(header)
            # 3.3 发送真实数据信息
            conn.send(stdout)
            conn.send(stderr)
            # conn.send(‘from server‘.encode(‘utf-8‘)) # 发命令

        except ConnectionResetError:
            break
    conn.close()

server.close()
View Code

    客户端.py

技术分享图片
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct

client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

client.connect((127.0.0.1,3231))

while True: # 通信循环
    cmd = input(输入命令:).strip() # 输入命令
    if not cmd:continue
    client.send(cmd.encode(gbk)) # 发送命令
    # 1、接收报头信息
    header = client.recv(4)
    # 2、获取真实数据的长度
    total_size = struct.unpack(i,header)[0]
    recv_size = 0   # 初始化接收的信息长度
    recv_data = b‘‘ # 初始化接收的数据
    while recv_size < total_size: # 判断已接收的数据长度是否大于需要接收的数据
        data = client.recv(1024)   # 每次接收的数据量
        recv_data += data       # 已经接收的总数居
        recv_size += len(data)  # 已经统计的数据长度
    print(recv_data.decode(gbk))

client.close()
View Code

    3、成仙阶段

      服务端.py

技术分享图片
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import subprocess
import struct
import json

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind((127.0.0.1,3301))
server.listen(5)

while True:
    conn,client_adress = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode(gbk),shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 把执行结果返回给客户端
            # 1、制定固定长度的报头
            head_dic = {
                file_name:head.txt,
                md5:xxxxxxxx,
                total_size:len(stdout)+len(stderr)
            } # 报头
            header_json = json.dumps(head_dic)  # json转成字符串
            header_bytes = header_json.encode(gbk) # 字符串转成bytes类型
            # 2、先发送报头长度
            conn.send(struct.pack(i,len(header_bytes)))
            # 3、发送报头
            conn.send(header_bytes)
            # 4、发送执行后的信息
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()

server.close()
View Code

      客户端.py

技术分享图片
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

client.connect((127.0.0.1,3301))

while True:
    cmd = input(输入命令>>:).strip()
    if not cmd:continue
    client.send(cmd.encode(gbk))
    # 获取服务器返回的信息
    # 1、获取报头长度
    obj = client.recv(4)
    header_size = struct.unpack(i,obj)[0]
    # 2、获取报头
    header_bytes = client.recv(header_size)
    # 3、从报头解析出对真实数据的描述(数据长度)
    header_json = header_bytes.decode(gbk) # 转成字符串
    header_dic = json.loads(header_json)  # 转成报头原数据类型
    total_size = header_dic[total_size] # 获取真实数据长度
    # 4、获取真实数据
    recv_size = 0
    recv_data = b‘‘
    while recv_size < total_size:
        data = client.recv(1024)
        recv_data += data
        recv_size += len(data)
    print(recv_data.decode(gbk))

client.close()
View Code

 

    



以上是关于网络编程基础之粘包现象的主要内容,如果未能解决你的问题,请参考以下文章

socket之粘包

python/socket编程之粘包

socket网络编程之粘包问题详解

网络编程之粘包

网络编程 之粘包问题使用socketserver实现并发

Learning-Python29:网络编程之粘包