首页 > 其他分享 >通信循环,粘包现象

通信循环,粘包现象

时间:2024-05-15 20:42:16浏览次数:23  
标签:socket recv 通信 send server client 粘包 data 循环

Ⅰ 通信循环

【一】UDP协议模型

【1】UDP模板

# 服务端
import socket

ip_port = ('127.0.0.1', 9000)
BUFSIZE = 1024
udp_server_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)

while True:
    msg, addr = udp_server_client.recvfrom(BUFSIZE)
    print(msg, addr)

    udp_server_client.sendto(msg.upper(), addr)
# 客户端
import socket

ip_port = ('127.0.0.1', 9000)
BUFSIZE = 1024
udp_client_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    msg = input('>>: ').strip()
    if not msg: continue

    udp_client_server.sendto(msg.encode('utf-8'), ip_port)

    back_msg, addr = udp_client_server.recvfrom(BUFSIZE)
    print(back_msg.decode('utf-8'), addr)

【2】UPD示例

# 客户端
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个client对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_DGRAM:连接模式是UDP协议的报式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 【三】直接发送数据
to_server_send_data = f'这是来自客户端的一条消息!'
to_server_send_data = to_server_send_data.encode()
client.sendto(to_server_send_data, settings.ADDR)
print(f'client :>>>> {client}')
# client :>>>> <socket.socket fd=428, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('0.0.0.0', 63316)>
# 【四】接收到服务端回的消息
from_server_recv_data,addr = client.recvfrom(1024)
from_server_recv_data = from_server_recv_data.decode()
print(f'from_server_recv_data :>>>> {from_server_recv_data}')
# from_server_recv_data :>>>> 这是来自服务端的一条消息!
# 【五】关闭连接对象
client.close()
#  服务端
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_DGRAM:连接模式是UDP协议的报式模式
# 只会发送一次数据
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
print(f'server :>>>>{server}')
# server :>>>><socket.socket fd=332, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8002)>
# 【四】接收到客户端的数据
from_client_recv_data, addr = server.recvfrom(1024)
from_client_recv_data = from_client_recv_data.decode()
print(f'from_client_recv_data :>>>> {from_client_recv_data}')
# from_client_recv_data :>>>> 这是来自客户端的一条消息!
print(f'addr :>>>>{addr}')
# addr :>>>>('127.0.0.1', 58450)
# 【五】返回给客户端数据
to_client_send_data = '这是来自服务端的一条消息!'
to_client_send_data = to_client_send_data.encode()
server.sendto(to_client_send_data, addr)
# 【六】关闭连接和服务
server.close()
# settings
IP = '127.0.0.1'
PORT = 8002

ADDP = (IP, PORT)

【二】TCP协议模型

示例

# 客户端
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
# 【四】直接发送数据
to_server_send_data = f'这是来自客户端的一条消息!'
to_server_send_data = to_server_send_data.encode()
client.send(to_server_send_data)
print(f'to_server_send_data :>>>> {client}')
# to_server_send_data :>>>> <socket.socket fd=416, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 50163), raddr=('127.0.0.1', 8002)>
# 【五】接收到服务端回的消息
from_server_recv_data = client.recv(1024)
from_server_recv_data = from_server_recv_data.decode()
print(from_server_recv_data)
# 【六】关闭连接对象
client.close()
# 服务端
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
conn, addr = server.accept()
print(f'当前是 conn :>>>> {conn}')
# 当前是 conn :>>>>
# <socket.socket fd=336, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8002), raddr=('127.0.0.1', 50158)>
print(f'当前是 addr :>>>> {addr}')
# 当前是 addr :>>>> ('127.0.0.1', 50158)
# 【六】接收到客户端的数据
from_client_recv_data = conn.recv(1024)
from_client_recv_data = from_client_recv_data.decode()
print(from_client_recv_data)
# 【七】返回给客户端数据
to_client_send_data = '这是来自服务端的一条消息!'
to_client_send_data = to_client_send_data.encode()
conn.send(to_client_send_data)
# 【八】关闭连接和服务
conn.close()
server.close()
# settings
IP = '127.0.0.1'
PORT = 8002

ADDP = (IP, PORT)

【1】补充localhost和IP区别

IP = '127.0.0.1'   又称为本机IP地址,只能在本地用,基于网络用不了
# localhost和IP区别

# DNS解析 ----> 一串字符解析成IP
# localhost就是一个域名 ----> 只能在本地使用

【2】代码优化

初代代码

# 客户端

import socket

client = socket.socket() # 产生一个socket对象
client.connect(('127.0.0.1', 8080))   # 根据服务端的地址链接

message = f'客户端数据'
# 用户如果什么都不输入 直接回车 那么不该往下走

client.send(message.encode('utf-8')) # 给服务端发送消息

data = client.recv(1024) # 接收服务端回复的消息

print(data.decode('utf-8'))

client.close()  # 关闭客户端
# 服务端

import socket

server = socket.socket()  # 买手机
'''
通过查看源码
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
'''

server.bind(('127.0.0.1', 8080))   # 插电话卡
'''
服务端应该具备的特征
    1. 具有固定的地址
    ...
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
'''

# 拨打电话:listen 监听当前是否有人接通电话
server.listen(5)   # 开机
'''
半连接池
'''

sock, addr = server.accept() # 等待并接听电话
'''listen和accept对应TCP三次握手服务端的两个状态'''
print(addr)  # 客户端地址

data = sock.recv(1024)  # 听别人说话
print(data.decode('utf-8'))

message = f'这是服务端数据'
sock.send(message.encode('utf-8')) # 回复别人说的话
'''recv和send接收和发送的都是bytes类型的数据'''

sock.close() # 挂电话

server.close() # 关机

(1)先解决消息固定的问题

  • 利用input获取用户数据
# 客户端

message = input("请输入您想要发送的消息:>>> ").strip()
# 用户如果什么都不输入 直接回车 那么不该往下走

client.send(message.encode('utf-8')) # 给服务端发送消息

data = client.recv(1024) # 接收服务端回复的消息


# 服务端
sock, addr = server.accept() # 等待并接听电话
'''listen和accept对应TCP三次握手服务端的两个状态'''
print(addr)  # 客户端地址

data = sock.recv(1024)  # 听别人说话
print(data.decode('utf-8'))

message = input(f'请回复消息:>>> ').strip()
sock.send(message.encode('utf-8')) # 回复别人说的话
'''recv和send接收和发送的都是bytes类型的数据'''

(2)再解决通信循环的问题

  • 利用while True将双方用于数据交互的代码循环起来

  • sock, addr = server.accept() # 等待并接听电话
    
  • 注意上句代码写在while循坏外

如果放在while循坏内他每次循环都要接收新对象,忘记上一个连结过的对象

就会造成只能通信一次

 # 服务端
while True:
    data = sock.recv(1024)  # 听别人说话
    print(data.decode('utf-8'))

    message = input(f'请回复消息:>>> ').strip()
    sock.send(message.encode('utf-8')) # 回复别人说的话
    '''recv和send接收和发送的都是bytes类型的数据'''
    
# 客户端

while True:
    message = input("请输入您想要发送的消息:>>> ").strip()
    client.send(message.encode('utf-8')) # 给服务端发送消息

    data = client.recv(1024) # 接收服务端回复的消息


    print(data.decode('utf-8'))

(3)发消息不能为空

  • 统计长度并判断即可
while True:
    message = input("请输入您想要发送的消息:>>> ").strip()
    # 用户如果什么都不输入 直接回车 那么不该往下走
    if len(message) == 0:
        print(f'消息不能为空')
        continue

    client.send(message.encode('utf-8')) # 给服务端发送消息

    data = client.recv(1024) # 接收服务端回复的消息

(4)特殊报错处理

反复重启服务端可能会报错:>>> address in usev:端口占用问题

​ 这个错在苹果电脑报的频繁 windows频率较少

方法一:换 端口号

# settings
IP = '127.0.0.1'
PORT = 8002

ADDP = (IP, PORT)
这是TCP协议模型里面处理方法:>>>> 将 settings 里面的 PORT手动更改一下

自写里面 修改 PORT:8080即可
server.bind(('127.0.0.1', 8080))   # 插电话卡

方法二:加一个配置

from socket import SOL_SOCKET, SO_REUSEADDR
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加
# 服务端

import socket
from socket import SOL_SOCKET, SO_REUSEADDR

server = socket.socket()  # 买手机
'''
通过查看源码
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
'''
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加
server.bind(('127.0.0.1', 8080))   # 插电话卡
'''
服务端应该具备的特征
    1. 具有固定的地址
    ...
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
'''

# 拨打电话:listen 监听当前是否有人接通电话
server.listen(5)   # 开机
'''
半连接池
'''

sock, addr = server.accept() # 等待并接听电话
'''listen和accept对应TCP三次握手服务端的两个状态'''
print(addr)  # 客户端地址
while True:
    data = sock.recv(1024)  # 听别人说话
    print(data.decode('utf-8'))

    message = input(f'请回复消息:>>> ').strip()
    sock.send(message.encode('utf-8')) # 回复别人说的话
    '''recv和send接收和发送的都是bytes类型的数据'''

sock.close() # 挂电话

server.close() # 关机

端口冲突问题

[WinError 10013]以一种访问权限不允许的方式做了一个访问套接字的尝试

解决办法:

(1)Windows系统

  • [1]进入cmd窗口

    • 进入windows中的命令行窗口(win+R之后输入cmd就可以进去)
  • [2]过滤当前端口号进程

    • 输入以下命令查看当前启动服务的端口号

    • netstat -ano|findstr 端口号
      
      • 此时你应该能看到一串进程,最后位置的数字即为进程号
  • [3]杀死当前进程

    • taskkill /pid 进程号 /F
      

(2)MacOS系统

  • [1]进入到终端窗口

  • [2]过滤当前端口号进程

    • ps aux|grep 端口号
      
  • [3]杀死当前进程

    • kill 进程号 -9
      
      # kill 命令的用途
      1(HUP):重新加载进程。
      9 (KILL):杀死进程。
      15(TERM):完美地停止一个进程。
      kill pid //同下-15默认的安全停止进程
      kill -15 pid //
      kill -9 pid  //彻底杀死进程
      

(5)解决服务端主动结束服务,客户端无响应

# 客户端
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
while True:
    # 【四】直接发送数据
    to_server_send_data = input("请输入发送给服务端的数据 :>>>> ").strip()
    if not to_server_send_data:
        print(f'不允许发送空的数据')
        continue
    if to_server_send_data == 'q':
        print(f'当前连接已退出!')
        break
    to_server_send_data = to_server_send_data.encode()
    client.send(to_server_send_data)
    # 【五】接收到服务端回的消息
    from_server_recv_data = client.recv(1024)
    from_server_recv_data = from_server_recv_data.decode()
    if from_server_recv_data == 'q':
        break
    print(f'这是来自服务端的数据 :>>>>  {from_server_recv_data}')
# 【六】关闭连接对象
client.close()
# 服务端

from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
# 放在这里 进入的 while 循环中的哪个连接对象会一致不变
conn, addr = server.accept()
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        from_client_recv_data = conn.recv(1024)
        from_client_recv_data = from_client_recv_data.decode()
        if not from_client_recv_data:
            break
        print(f'这是来自客户端的数据 :>>>>  {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break
# 【八】关闭连接和服务
conn.close()
server.close()
# settings
IP = '127.0.0.1'
PORT = 8002

ADDP = (IP, PORT)

(6)链接循环

'''
如果是Windows 客户端异常退出之后服务端会直接报错
	处理方式
		异常处理(try...except...)
如果是mac或Linux 服务端会接收到一个空消息 不会报错
	处理方式
		len判断
'''
# 例如  服务端 异常处理(try...except...)
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        from_client_recv_data = conn.recv(1024)
        from_client_recv_data = from_client_recv_data.decode()
        if not from_client_recv_data:
            break
        print(f'这是来自客户端的数据 :>>>>  {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break

客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

解决办法 服务端再加一个while循环 让其在原地等待别的 客户端接入即可

# 服务端

import socket
from socket import SOL_SOCKET, SO_REUSEADDR

server = socket.socket()  # 买手机
'''
通过查看源码
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
'''
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 在bind前加
server.bind(('127.0.0.1', 8080))   # 插电话卡
'''
服务端应该具备的特征
    1. 具有固定的地址 
    ...
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
'''

# 拨打电话:listen 监听当前是否有人接通电话
server.listen(5)   # 开机
'''
半连接池
'''
while True:
    sock, addr = server.accept() # 等待并接听电话
    '''listen和accept对应TCP三次握手服务端的两个状态'''
    print(addr)  # 客户端地址
    while True:
        try:
            data = sock.recv(1024)  # 听别人说话
            if len(data) == 0:
                break
            print(data.decode('utf-8'))

            message = input(f'请回复消息:>>> ').strip()
            if len(message) == 0:
                message = '应用太火爆 请重试'
            sock.send(message.encode('utf-8')) # 回复别人说的话
        except Exception:
            break

    '''recv和send接收和发送的都是bytes类型的数据'''

# 客户端


import socket

client = socket.socket() # 产生一个socket对象
client.connect(('127.0.0.1', 8080))   # 根据服务端的地址链接

while True:
    message = input("请输入您想要发送的消息:>>> ").strip()
    # 用户如果什么都不输入 直接回车 那么不该往下走
    if len(message) == 0:
        continue

    client.send(message.encode('utf-8')) # 给服务端发送消息

    data = client.recv(1024) # 接收服务端回复的消息


    print(data.decode('utf-8'))

client.close()  # 关闭客户端

【三】半连接池

listen(5)
# py文件默认同一时间只能运行一次 如果想单独分开运行多次

#半连接池
	设置的最大等待人数 >>>: 节省资源 提高效率

【四】粘包现象

【1】导学

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8090))

client.send(b'baby')
client.send(b'silence')
client.send(b'can i ask you')

# 服务端
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8090))
server.listen(5)

conn, addr = server.accept()

data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)


'''
三次打印结果
    # b'babysilencecan i ask you'
    # b''
    # b''
'''

【2】粘包问题

# 【一】粘包问题的背景
# 粘包问题只会发生在TCP协议 ---> 流式协议 ---> 不断的传输数据
# 比如有一个大缸:先放红颜色的颜料放了一缸但是还有一勺没有放进去
# 没有放进去的这一勺红颜色颜料和下一缸的绿颜色颜料混到了一起

# 不会发生在UDP协议 ---> 报式协议 ---> 一次性传输数据
# 比如有一个大缸:先放红颜色的颜料放了一缸但是还有一勺没有放进去
# 直接将这勺颜料扔了

# 【二】粘包问题
# 客户端发送的数据远远超出服务端的接收范围
# 导致了不同数据之间的数据混乱问题

# 【三】实例
# 执行本地的 ipconfig 命令会获取到当前执行的结果
# 将结果传递给 服务端
# 服务端接收到数据 只能接收一部分 另外一部分发现接收不到
# 只能和第二次的数据合并到一起发送

# 【四】解决办法
# 【1】解决思路
# 问题产生在服务端
# 原因是客户端向服务端发送数据,但是服务端不知道总的数据大小,只能按照默认的数据大小接收
# 解决思路:
# 客户端在发送数据的时候将数据的总大小一起发送给服务单
# 服务单接收到总的大小的数据长度 , 根据自己的容量大小分批次接收

【3】粘包模板问题演示

from conf import settings
# 【一】引入socket模块
import socket
import subprocess


def run_cmd(command):
    result = subprocess.run(
        command,  # 子进程要执行的命令
        shell=True,  # 执行的是shell的命令
        # 存放的是执行命令成功的结果
        stdout=subprocess.PIPE,
        # 存放的是执行命令失败的结果
        stderr=subprocess.PIPE,
        encoding="gbk",
        timeout=1)
    # returncode属性是run()函数返回结果的状态。
    if result.returncode == 0:
        return result.stdout
    else:
        return result.stderr


# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
while True:
    # 【四】直接发送数据
    command = input("请输入需要执行的命令 :>>>> ").strip()
    if not command:
        print(f'不允许发送空的数据')
        continue
    if command == 'q':
        print(f'当前连接已退出!')
        break
    to_server_send_data = run_cmd(command=command)
    to_server_send_data = to_server_send_data.encode()
    client.send(to_server_send_data)
    # 【五】接收到服务端回的消息
    from_server_recv_data = client.recv(1024)
    from_server_recv_data = from_server_recv_data.decode()
    if from_server_recv_data == 'q':
        break
    print(f'这是来自服务端的数据 :>>>>  \n{from_server_recv_data}')
# 【六】关闭连接对象
client.close()
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
# 放在这里 进入的 while 循环中的哪个连接对象会一致不变
conn, addr = server.accept()
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        from_client_recv_data = conn.recv(1024)
        from_client_recv_data = from_client_recv_data.decode()
        if not from_client_recv_data:
            break
        print(f'这是来自客户端的数据 :>>>>  \n {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break
# 【八】关闭连接和服务
conn.close()
server.close()

【4】TCP协议的特点

  • 会将数据量比较小并且时间间隔比较短的数据整合到一起发送
    • 流式协议:跟水流一样不间断
'''
问题产生原因是 因为recv括号内我们不知道即将要接收的数据到底有多大
	并且还会受制于recv括号内的数字大小
		如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现粘包
'''
# 服务端
data1 = conn.recv(5)   # 这是将recv修改成5  
print(data1)
data2 = conn.recv(5)
print(data2)
data3 = conn.recv(5)
print(data3)

'''
b'babys'
b'silen'
b'canns'
'''

# 客户端
client.send(b'babys')
client.send(b'silen')
client.send(b'canns')

【5】解决粘包

(1) struct模块

方向: 精准获取数据的大小

# struct模块
# 【一】模块介绍
# struct.pack()是Python内置模块struct中的一个函数
# 它的作用是将指定的数据按照指定的格式进行打包
# 并将打包后的结果转换成一个字节序列(byte string),可以用于在网络上传输或者储存于文件中。
# 【二】参数简介
# struct.pack(fmt, v1, v2, ...)
# 其中,fmt为格式字符串,指定了需要打包的数据的格式,后面的v1,v2,...则是需要打包的数据。
# 这些数据会按照fmt的格式被编码成二进制的字节串,并返回这个字节串。



'''
pack 可以将任意长度的数字打包成固定长度
unpack 可以将固定长度的数字解包成打包之前数据的真实长度



思路:
	1.先将真实数据打包成固定长度的包
	2.将固定长度的包发给对方
	3.对方接收到包之后再解包获取真实数据长度
	4.接收真实数据长度
	
这种遇到的问题就是:负责打包的方法数据量如果特别巨大 是无法打包的
	换个思路 不直接打包真实数据长度 而是先弄个字典
		字典之后不单单可以再字典中放入数据的长度还能携带额外的数据


最终方案:
发送方:
	1.先构造一个字典
		内部存储了真实数据相关信息
			大小,名称......
	2.对字典进行打包处理
	3.将固定长度的数据(字典)发送给对方
	4.发送真实的字典数据
	5.发送真实的真正数据
		
接收方:
	1.先接收固定长度的字典包
	2.解析成字典的真实长度
	3.接收字典数据
	4.从字典数据中解析出各种信息
	5.接收真实的数据
'''
# 【三】示例
import struct

# 定义一个包含不同类型字段的格式字符串
format_string = 'i'

# 示例数据:整数、四个字节的原始数据、短整数
data_to_pack = '十七dasdadsad asd 撒大撒多所adsaddasdadsa da dsa asad撒大大带我去大青蛙大大大大大萨达去问问恰饭恰饭放散阀昂发昂发沙发阿发发发放上千万请发送方三房启发法阿发发发ad sada dsa dsa dsa sa dsa dsa as ad sad ad ada顿撒大大三大撒打我前端'
data_to_pack_bytes = data_to_pack.encode()

data_to_pack_len = len(data_to_pack_bytes)
print(data_to_pack_len)
# 使用 struct.pack 将数据打包成二进制字节串
packed_data = struct.pack(format_string, data_to_pack_len)

# 41000000
# 64000000
# 19010000
print("Packed data:", len(packed_data))  # 打印打包后的十六进制表示

# 解析二进制字节串,恢复原始数据
unpacked_data = struct.unpack(format_string, packed_data)
#
print("Unpacked data:", unpacked_data)  # 打印解析后的数据

(2)pack 可以将任意长度的数字打包成固定长度

import struct

data = "hello baby i love you may i ask you a sice"

print(len(data))

res = struct.pack('i', len(data))
print(len(res))

# 42
# 4

unpack 可以将固定长度的数字解包成打包之前数据的真实长度

import struct

data = "hello baby i love you may i ask you a sice"

print(len(data))

res = struct.pack('i', len(data))
print(len(res))

ret = struct.unpack('i', res)
print(ret)

# 42
# 4
# (42,)

(3)subprocess模块回顾

  • subprocess模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。
  • 简单理解就是:使用我们自己的电脑去链接别人的电脑 (socket模块)
import subprocess


def run_cmd(command):
    result = subprocess.run(
        command,  # 子进程要执行的命令
        shell=True,  # 执行的是shell的命令
        # 存放的是执行命令成功的结果
        stdout=subprocess.PIPE,
        # 存放的是执行命令失败的结果
        stderr=subprocess.PIPE,
        encoding="gbk",
        timeout=1)
    # returncode属性是run()函数返回结果的状态。
    if result.returncode == 0:
        return result.stdout
    else:
        return result.stderr


if __name__ == '__main__':
    print(run_cmd(['dir']))

【6】粘包模板问题解决方案

# 客户端
import json

from conf import settings
# 【一】引入socket模块
import socket
import subprocess
import uuid


def run_cmd(command):
    result = subprocess.run(
        command,  # 子进程要执行的命令
        shell=True,  # 执行的是shell的命令
        # 存放的是执行命令成功的结果
        stdout=subprocess.PIPE,
        # 存放的是执行命令失败的结果
        stderr=subprocess.PIPE,
        encoding="gbk",
        timeout=1)
    # returncode属性是run()函数返回结果的状态。
    if result.returncode == 0:
        return result.stdout
    else:
        return result.stderr


# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
while True:
    # 【四】直接发送数据
    command = input("请输入需要执行的命令 :>>>> ").strip()
    if not command:
        print(f'不允许发送空的数据')
        continue
    if command == 'q':
        print(f'当前连接已退出!')
        break
    # 【1】执行本地的命令,获取到当前命令结果
    result = run_cmd(command=command)
    # 【2】对命令的结果进行编码---> 转成二进制数据
    result_bytes = result.encode()
    # 【3】计算长度
    data_length = len(result_bytes)
    # 【4】增加一个数据概览 --> 字典格式 做数据概览
    # 存储当前文件名 / 结果名 / md5加密盐(用来校验数据的完整性)
    salt = uuid.uuid4().hex
    encrypted = settings.encrypt_data(data=result_bytes, salt=salt)
    send_data_info = {
        'command': command,
        'data_length': data_length,
        'salt': salt,
        'encrypted': encrypted
    }
    # 【5】将上面打包好的数据全部发送给服务端
    # (1)字典格式无法发送
    # 将字典转换为字符串数据 ----> json
    # dump : 处理文件数据
    # dumps : 做格式转换的
    json_str = json.dumps(send_data_info)
    # (2)将json字符串数据转换为二进制数据
    json_bytes = json_str.encode()
    # 【6】问题产生
    # JSON字符串转换为的二进制数据还是会很长
    # 让数据变短
    # struct 模块 ---> 将某几个数字转换为四个字节的二进制数据
    json_length_pack = settings.pack_data(data_length=len(json_bytes))
    # 【7】发送struct打包的数据(四个字节) + JSON数据 + 原始数据
    # JSON数据里面存的是所有数据信息而没有原始的二进制数据
    # 服务端接受的顺序取决于客户端发送的顺序
    # 先发送struct打包后的数据
    client.send(json_length_pack)  # 4 字节 --> 包含json二进制数据的长度
    # 先发送 json_bytes 打包后的数据
    client.send(json_bytes)  # 不知道
    # 再发送 result_bytes 原始数据
    client.send(result_bytes)
    # 【五】接收到服务端回的消息
    from_server_recv_data = client.recv(1024)
    from_server_recv_data = from_server_recv_data.decode()
    if from_server_recv_data == 'q':
        break
    print(f'这是来自服务端的数据 :>>>>  \n{from_server_recv_data}')
# 【六】关闭连接对象
client.close()
# 服务端
import json

from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
# 放在这里 进入的 while 循环中的哪个连接对象会一致不变
conn, addr = server.accept()
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        # 【1】先接接收四个字节的数据 ---> struct打包好的四个字节的数据
        json_pack_data = conn.recv(4)
        if not json_pack_data:
            break
        json_bytes_length = settings.unpack_data(data=json_pack_data)
        # 【2】根据json二进制数据长度解出JSON二进制数据
        json_data_bytes = conn.recv(json_bytes_length)
        # 【3】将json二进制数据转为json字符串数据
        json_str = json_data_bytes.decode()
        # 【4】将json字符串数据转换为python的字典
        data_info = json.loads(json_str)
        # 【5】从字典中获取自定的参数
        # 获取到总的数据长度
        # 10000
        data_length = data_info.get('data_length')
        # 【6】定义参数
        # (1)总数据
        all_data = b''
        # (2)每次接收的数据大小
        size = 1024
        # data_length : 5
        # size : 2
        # count : 2 , last_size : 1
        count, last_size = divmod(data_length, size)
        # (3)已经接受的数据大小
        all_size = 0
        while all_size < count + 1:
            all_size += 1
            # 接收到每一次的数据并和总数据拼接
            if all_size == count + 1:
                all_data += conn.recv(last_size)
            else:
                all_data += conn.recv(size)
        from_client_recv_data = all_data.decode()
        print(f'这是来自客户端的数据 :>>>>  \n {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break
# 【八】关闭连接和服务
conn.close()
server.close()

标签:socket,recv,通信,send,server,client,粘包,data,循环
From: https://www.cnblogs.com/zyb123/p/18194660

相关文章

  • 串口通信原理
          异步串行:异步说明不带时钟信号,串行说明是按位(一位=8bit),一位一位传输       ......
  • kettle从入门到精通 第六十课 ETL之kettle for循环处理每条数据,so easy!
    1、kettle原生是支持for循环处理的,无需通过javascript脚本或者java脚本开发for循环控制。当然如果想通过脚本挑战下也是可以的。本节课主要讲解如何通过kettle中的job来实现for循环控制,如下图所示:1)步骤【设置变量】设置单个job级别的变量。2)步骤【转换】加载数据集清单列表,返......
  • 利用深度循环神经网络对心电图降噪
    具体的软硬件实现点击http://mcu-ai.com/MCU-AI技术网页_MCU-AI我们提出了一种利用由长短期记忆(LSTM)单元构建的深度循环神经网络来降噪心电图信号(ECG)的新方法。该网络使用动态模型ECG生成的合成数据进行预训练,并使用来自PhysionetPDB心电图信号数据库的真实数......
  • 网络通信
    2.0】操作系统与网络通信【零】引入【1】什么是网络编程网络编程是指通过编程语言在计算机之间建立通信的一种方式。它是在互联网上进行数据传输的关键组成部分,使计算机能够相互通信、交换信息和共享资源。网络编程涉及许多不同的技术和协议,包括TCP/IP(传输控制协议/因特网协......
  • 粘包
    粘包问题【一】什么是粘包须知:只有TCP有粘包现象,UDP永远不会粘包【1】socket收发消息的原理首先需要掌握一个socket收发消息的原理发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据也就是说......
  • 使用c#强大的表达式树实现对象的深克隆之解决循环引用的问题
    在上一期博客里,我们提到使用使用c#强大的表达式树实现对象的深克隆,文章地址:https://www.cnblogs.com/gmmy/p/18186750。但是文章里没有解决如何实现循环引用的问题。循环引用在C#中,循环引用通常发生在两个或更多的对象相互持有对方的引用,从而形成一个闭环。这种情况在使用面向对......
  • Shell编程之循环语句与函数
    目录1.循环语句(1)for循环语句(2)while循环语句(3)untli循环语句(4)双层循环2.Shell函数(1)函数返回值:(2)函数传参:(3)函数变量的作用范围:(4)递归3.Shell数组(1)定义数组(2)查看数组(3)数组分片、字符替换和删除(4)追加(5)数组排序算法:1.循环语句(1)for循环语句for变量in取值列表for((变量=初始值;变量......
  • shell循环
    echoecho-n表示不换行输出echo-e表示输出转义符常用的转义符\r光标移至行首,并且不换行\s当前shell的名称,如bash\t插入Tab键,制表符\n输出换行\f换行,但光标仍停留在原处\表示插入"\"本身转义\b表示退格不显示前一个字符\c抑制更多......
  • 多线程循环控制字段失效造成死循环的坑
    编程的时候遇到一个场景:A,B两个线程,B是一个while(flag),有个控制字段flag,刚开始是trueB会一直循环,A某个情况回把flag置为false,但是如果B的循环里什么都没干,就一直不退出,陷入死循环本来以为是哪里逻辑写错了,于是在B里面加入了一个printf,没想到结果就能正常退出了 ......
  • MYSQL存储过程-练习3 repeat循环
    repeat循环1DELIMITER$$23CREATEPROCEDURE`sp_repeat`()4BEGIN5DECLAREiINT;6SETi=1;7REPEAT8SELECTi;9SETi=i+1;10UNTILi>=511ENDREPEAT;12END$$1314DELIMITER;执行存储过程1mysql>callsp_repea......