Python网络编程03/ low版解决粘包问题

Posted liubing8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python网络编程03/ low版解决粘包问题相关的知识,希望对你有一定的参考价值。

Python网络编程03/ low版解决粘包问题

1.操作系统的缓存区

1.为什么存在缓冲区
    1. 暂时存储一些数据.
    2. 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.
缺点: 造成了粘包现象之一.

技术图片

技术图片

2.基于TCP协议的socket循环通信

2.1 服务端(server)

# import socket
# 
# phone = socket.socket()
# 
# phone.bind(('127.0.0.1',8848))
# 
# phone.listen()
# listen: 允许5个人链接我,剩下的链接也可以链接,等待.
# 
# conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
# print(f'链接来了: conn,addr')
# 
# while 1:
#     try:
#         from_client_data = conn.recv(1024)  # 最多接受1024字节
# 
#         if from_client_data.upper() == b'Q':
#             print('客户端正常退出聊天了')
#             break
# 
#         print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#         to_client_data = input('>>>').strip().encode('utf-8')
#         conn.send(to_client_data)
#     except ConnectionResetError:
#         print('客户端链接中断了')
#         break
# conn.close()
# phone.close()

2.2客户端(client)

# import socket
# 
# phone = socket.socket()
# 
# phone.connect(('127.0.0.1',8848))
# while 1:
#     to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
#     if not to_server_data:
#         # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
#         print('发送内容不能为空')
#         continue
#     phone.send(to_server_data)
#     if to_server_data.upper() == b'Q':
#         break
#     from_server_data = phone.recv(1024)  # 最多接受1024字节
#     print(f'来自服务端消息:from_server_data.decode("utf-8")')
# 
# phone.close()
# 
# # s1 = 'q'
# # s2 = b'q'
# # s3 = '中国'
# # print(s3.encode('utf-8'))
# # print(type(s1),type(s2))
# 
# # s1 = 'q'
# # print(s1.encode('utf-8'))
# 
# # bytes类型:
#     # ASCII字符: 在字符串前面b''
#     # 非ASCII字符: encode 转化成 bytes类型

3.基于TCP协议的socket链接+循环 通信

3.1服务端(server)

# import socket
# 
# phone = socket.socket()
# 
# phone.bind(('127.0.0.1',8848))
# 
# phone.listen(2)
# # listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
# 
# while 1:
#     conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#     print(f'链接来了: conn,addr')
# 
#     while 1:
#         try:
#             from_client_data = conn.recv(1024)  # 最多接受1024字节
# 
#             if from_client_data.upper() == b'Q':
#                 print('客户端正常退出聊天了')
#                 break
# 
#             print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#             to_client_data = input('>>>').strip().encode('utf-8')
#             conn.send(to_client_data)
#         except ConnectionResetError:
#             print('客户端链接中断了')
#             break
#     conn.close()
# phone.close()

3.2 客户端(client)

# import socket
# 
# phone = socket.socket()
# 
# phone.connect(('127.0.0.1',8848))
# while 1:
#     to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
#     if not to_server_data:
#         # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
#         print('发送内容不能为空')
#         continue
#     phone.send(to_server_data)
#     if to_server_data.upper() == b'Q':
#         break
#     from_server_data = phone.recv(1024)  # 最多接受1024字节
#     print(f'来自服务端消息:from_server_data.decode("utf-8")')
# 
# phone.close()

4.基于TCP协议的socket应用实例:执行远程命令

4.1服务端(server)

# import socket
# import subprocess
# phone = socket.socket()
# 
# phone.bind(('127.0.0.1',8848))
# 
# phone.listen(2)
# # listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
# 
# while 1:
#     conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#     print(f'链接来了: conn,addr')
# 
#     while 1:
#         try:
# 
#             from_client_data = conn.recv(1024)  # 最多接受1024字节
# 
# 
#             if from_client_data.upper() == b'Q':
#                 print('客户端正常退出聊天了')
#                 break
# 
#             obj = subprocess.Popen(from_client_data.decode('utf-8'),
#                                    shell=True,
#                                    stdout=subprocess.PIPE,
#                                    stderr=subprocess.PIPE,
# 
#                                    )
#             result = obj.stdout.read() + obj.stderr.read()
# 
#             conn.send(result)
#         except ConnectionResetError:
#             print('客户端链接中断了')
#             break
#     conn.close()
# phone.close()
# 
# 
# 
# 
# # shell: 命令解释器,相当于调用cmd 执行指定的命令。
# # stdout:正确结果丢到管道中。
# # stderr:错了丢到另一个管道中。
# # windows操作系统的默认编码是gbk编码。
#

4.2客户端(client)

# import socket
# 
# phone = socket.socket()
# 
# phone.connect(('127.0.0.1',8848))
# while 1:
#     to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
#     if not to_server_data:
#         # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
#         print('发送内容不能为空')
#         continue
#     phone.send(to_server_data)
#     if to_server_data.upper() == b'Q':
#         break
#     from_server_data = phone.recv(1024)  # 最多接受1024字节
#     print(f'from_server_data.decode("gbk")')
# 
# phone.close()

5.粘包现象

5.1服务端(server)

# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。
# import socket
# import subprocess
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(2)
# # listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
#
# while 1:
#     conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#     # print(f'链接来了: conn,addr')
#
#     while 1:
#         try:
#
#             from_client_data = conn.recv(1024)  # 最多接受1024字节
#
#
#             if from_client_data.upper() == b'Q':
#                 print('客户端正常退出聊天了')
#                 break
#
#             obj = subprocess.Popen(from_client_data.decode('utf-8'),
#                                    shell=True,
#                                    stdout=subprocess.PIPE,
#                                    stderr=subprocess.PIPE,
#
#                                    )
#             result = obj.stdout.read() + obj.stderr.read()
#             print(f'总字节数:len(result)')
#             conn.send(result)
#         except ConnectionResetError:
#             print('客户端链接中断了')
#             break
#     conn.close()
# phone.close()



# s1 = '太白jx'
# # print(len(s1))
# b1 = s1.encode('utf-8')
# # print(b1)
# print(len(b1))


'''
         客户端                          服务端

第一次:  ipconfig                       317字节
         300个字节                       17个字节
         

         客户端                          服务端

第二次:   dir                           376字节
          17字节                        376字节
         

'''


# 2. 连续短暂的send多次(数据量很小),你的数据会统一发送出去.

# import socket
#
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(5)
#
#
# conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#
# from_client_data = conn.recv(1024)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
# conn.close()
# phone.close()


# 展示一些收发的问题。

5.2客户端(client)

# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
# while 1:
#     to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
#     if not to_server_data:
#         # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
#         print('发送内容不能为空')
#         continue
#     phone.send(to_server_data)
#     if to_server_data.upper() == b'Q':
#         break
#     from_server_data = phone.recv(300)  # 最多接受1024字节
#     # print(f'from_server_data.decode("gbk")')
#     print(len(from_server_data))
#
# phone.close()



# 2. 连续短暂的send多次(数据量很小),你的数据会统一发送出去.

# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
#
#
# phone.send(b'he')
# phone.send(b'll')
# phone.send(b'o')
#
#
# phone.close()
# Nigle算法

5.3展示收发问题的服务端(server)

# 发多次收一次
# import socket
#
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(5)
#
#
# conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#
# from_client_data = conn.recv(1024)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#
# from_client_data = conn.recv(1024)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
# conn.close()
# phone.close()



# 发一次收多次

# import socket
#
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(5)
#
#
# conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#
# from_client_data = conn.recv(3)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#
# from_client_data = conn.recv(3)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#
# from_client_data = conn.recv(3)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#
# from_client_data = conn.recv(3)  # 最多接受1024字节
# print(f'来自客户端addr消息:from_client_data.decode("utf-8")')
#
# conn.close()
# phone.close()

5.4 展示收发问题的客户端(client)

# 发多次收一次
# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
#
#
# phone.send(b'he')
# phone.send(b'llo')
#
#
# phone.close()
# Nigle算法


# 发一次收多次
# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
#
#
# phone.send(b'hello world')
#
#
# phone.close()

6.如何解决粘包现象

解决粘包现象的思路:

服务端发一次数据 10000字节,  
客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码.
1. 遇到的问题: recv的次数无法确定.
   你发送总具体数据之前,先给我发一个总数据的长度:5000个字节。然后在发送总数据。
   客户端: 先接收一个长度。 5000个字节。
   然后我再循环recv 控制循环的条件就是只要你接受的数据< 5000 一直接收。
2. 遇到的问题: 总数据的长度转化成的字节数不固定
服务端:
conn.send(total_size) 

conn.send(result)
total_size int类型


客户端:
total_size_bytes = phone.recv(4)
total_size
data = b''
while len(data) < total_size:
    data = data + phone.recv(1024)
你要将total_size int类型转化成bytes类型才可以发送

387    ---- > str(387)  '387'  ---->bytes   b'387'        长度   3bytes

4185  ---->  str(4185)  '4185'  ---->bytes   b'4185'  长度   4bytes

18000------------------------------------------------------>  长度   5bytes

我们要解决: 
将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来。
struct模块

技术图片

5.low版解决粘包现象

5.1服务端

# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。
import socket
import subprocess
import struct
phone = socket.socket()

phone.bind(('127.0.0.1',8848))

phone.listen(2)
# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错

while 1:
    conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    # print(f'链接来了: conn,addr')

    while 1:
        try:

            from_client_data = conn.recv(1024)  # 接收命令


            if from_client_data.upper() == b'Q':
                print('客户端正常退出聊天了')
                break

            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,

                                   )
            result = obj.stdout.read() + obj.stderr.read()
            total_size = len(result)
            print(f'总字节数:total_size')

            # 1. 制作固定长度的报头
            head_bytes = struct.pack('i',total_size)

            # 2. 发送固定长度的报头
            conn.send(head_bytes)

            # 3. 发送总数据
            conn.send(result)
        except ConnectionResetError:
            print('客户端链接中断了')
            break
    conn.close()
phone.close()


# import struct
# # 将一个数字转化成等长度的bytes类型。
# ret = struct.pack('i', 180000000)
# # print(ret, type(ret), len(ret))
#
# # 通过unpack反解回来
# ret1 = struct.unpack('i',ret)[0]
# # print(ret1)
# print(ret1, type(ret1))

# 总数据:总数据长度

# s1 = 'lagfdkjglkhjklh'
# b1 = s1.encode('utf-8')
# print(b1)
# print(len(b1))

5.2客户端(client)

import socket
import struct
phone = socket.socket()

phone.connect(('127.0.0.1',8848))
while 1:
    to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    if not to_server_data:
        # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
        print('发送内容不能为空')
        continue
    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':
        break

    # 1. 接收报头
    head_bytes = phone.recv(4)
    # 2. 反解报头
    total_size = struct.unpack('i',head_bytes)[0]

    total_data = b''

    while len(total_data) < total_size:
        total_data += phone.recv(1024)

    print(len(total_data))
    print(total_data.decode('gbk'))

phone.close()

以上是关于Python网络编程03/ low版解决粘包问题的主要内容,如果未能解决你的问题,请参考以下文章

python------Socket网络编程粘包问题

20 网络编程 粘包现象与解决方案

python 网络编程-03 粘包问题及处理

python----socket编程

python基础之socket编程

python基础之socket编程