VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > python入门教程 >
  • Python基础之网络编程:4、黏包及解决办法

一、黏包现象

1、什么是黏包

​ 黏包是指,当我们基于TCP协议,客户端可服务端进行数据传输时,会自动将多个小部份的数据打包成一个大的数据进行发送,例如,在客户端给服务端发送数据时,我们分开发送了 ABC , 123 这两段信息,按照常理来说,客户端收到的消息也应该两段分开的消息,而服务端实际收到的消息是 ABC123,可是这并不是我们想要的结果,因为在实际中,我们收到这种消息后很难将多条连接在一起的消息区分开来

代码表现

1、客户端
    # 导入socket
    import socket

    # 产生socket对象
    server = socket.socket()
    # 连接主机地址
    server.connect(('192.168.1.188', 8899))
    # 向主机发送信息
    server.send(b'hello tom')
    server.send(b'hello jason')
    server.send(b'hello jerry')

2、服务端
	# 产生socket对象
    server = socket.socket()
    # 设置主机IP地址
    server.bind(('192.168.1.188', 8899))
    # 设置半连接池
    server.listen(5)
    # 获取客户端接入
    sock, addre = server.accept()
    sock = sock.recv(1024).decode('utf8')
    # 打印客户端信息
    print(sock)

---------------------------------------------------------------
hello tomhello jasonhello jerry

2、产生黏包的原因

  • 1、在使用socket的模块,接收消息时 需要设置 recv 也就是接收的字节数,可是我们并不知道对方发送来的消息具体大小是多少,所以就会默认接收recv设置的字节以内的多段的消息
  • 2、TCP也称为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)

3、如何避免黏包

核心思路:

​ 1、明确即将接收的消息具体有多大

​ 2、针对消息的大小设置recv的字节数

解决办法:

​ 1、借助struct模块

二、struct模块

1、模块简介

​ 可以将一组数据打包为固定的大小,也可将打包的数据进行解包

2、常用方法

1、struct
	功能:导入模块

1、pack('模式''待打包的数据')
	功能:将一组数据按照指定模式进行打包,打包后的数据为Bytes类型
 
2、unpack(bytes_data)	
	功能:解析打包后的数据

3、常见格式

格式 python类型 标准尺寸
c 1个字节长度 1
ns n个字符 n
i 整数 4
f 浮点 4

三、黏包问题实战

1、解决思路

​ 上述,我们了解了产生黏包的原因,并且知道了大致解决思路,下面我们将针对黏包问题指定详细解决办法

第一步:

​ 分别在客户端和服务端导入struct模块,通过了解struct模块,我们知道在pack方法的‘i’模式下,会固定加打包的数据大小压缩为4个字节

第二步:

​ 产生一个‘字典’类型,在字典内存入我们需要发送的目标文件详细信息,并将目标文件的大小存入键值对,将信息设置为键值对格式

第三步:

​ 使用‘len’方法,获得字典编码后的字节个数,将字典的个数用'struct'模块的下'pack'方法进行打包,接收端将'recv'设置为4个字节,并接收打包后的‘字典字节个数’,将‘recv’设置为字典的大小,接收字典后,获取字典内目标文件大小,再将‘recv’设置为目标文件大小,进行接收目标文件

第四步:

​ 将打包的'字典'使用'unpack'方法解析,并读取字典信息,获取到需要接收的数据详细大小,在将'recv'设置为需要接收的文件大小,进行接收数据

2、代码实战

模拟客户端向服务端发送信息,将存有数据的'txt'格式文本发送给服务端

1、服务端
import socket
import struct
import json


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

sock, addr = server.accept()
# 1.接收固定长度的字典报头
data_dict_head = sock.recv(4)
# 2.根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes)  # 自动解码再反序列化
# 4.获取真实数据的各项信息
# total_size = data_dict.get('file_size')
# with open(data_dict.get('file_name'), 'wb') as f:
#     f.write(sock.recv(total_size))
'''接收真实数据的时候 如果数据量非常大 recv括号内直接填写该数据量 不太合适 我们可以每次接收一点点 反正知道总长度'''
# total_size = data_dict.get('file_size')
# recv_size = 0
# with open(data_dict.get('file_name'), 'wb') as f:
#     while recv_size < total_size:
#         data = sock.recv(1024)
#         f.write(data)
#         recv_size += len(data)
#         print(recv_size)


2、客户端
import socket
import os
import struct
import json

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

'''任何文件都是下列思路 图片 视频 文本 ...'''
# 1.获取真实数据大小
file_size = os.path.getsize(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt')
# 2.制作真实数据的字典数据
data_dict = {
    'file_name': '有你好看.txt',
    'file_size': file_size,
    'file_desc': '内容很长 准备好吃喝 我觉得营养快线挺好喝',
    'file_info': '这是我的私人珍藏'
}
# 3.制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 4.发送字典报头
client.send(data_dict_len)  # 报头本身也是bytes类型 我们在看的时候用len长度是4
# 5.发送字典
client.send(data_dict_bytes)
# 6.最后发送真实数据
with open(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt', 'rb') as f:
    for line in f:  # 一行行发送 和直接一起发效果一样 因为TCP流式协议的特性
        client.send(line)
import time
time.sleep(10)

四、UDP协议

1、UDP协议特点

​ UDP协议是面向无连接的通讯协议,UDP是不可靠协议,发送方所发送的数据对方不一定能够接收到

2、代码实现

服务端:

import socket

# UDP客户端、创建一个服务器端的Socket
socket_server = socket(AF_INET, SOCK_DGRAM)

# 2、定义服务器端的ip地址和端口号,元组形式
host_port = ('192.168.108.43', 8090)

# 3、服务器端的Socket来绑定地址和端口,只有绑定了地址和端口,才能称为服务器的Socket
socket_server.bind(host_port)

# 4、接收客户端发送过来的数据,每次接收1kb的数据,如果一直没收到数据会阻塞
# 收到的每一个数据报,里面是一个元组,第一个值是数据内容,第二个值是源地址
data = socket_server.recvfrom(1024)

# 解码
print(data[0].decode('utf-8'))
print(data)

# 5、关闭套接字、释放资源
socket_server.close()

服务器端的结构:
(1) 使用函数socket(),生成套接字描述符;
(2) 通过host_post 结构设置服务器地址和监听端口;
(3)使用bind()函数绑定监听端口,将套接字文件描述符和地址类型变(host_post) 进行绑定;
(4)接收客户端的数据,使用recvfrom()函数接收客户端的网络数据;
(5)关闭套接字,使用close()函数释放资源;

客户端:

impo

# 客户端发送一个请求也需要端口,端口是随机分配的
# 创建一个UDP协议的套接字,然后发送一条数据到网络上的另外一个进程

# UDP客户端、创建套接字
client_socket = socket(AF_INET, SOCK_DGRAM)  # SOCK_DGRAM:UDP协议

# 2、定义一个接收消息的目标,8080是一个目标服务器的端口,127.0.0.1是目标服务器地址
server_host_port = ('192.168.108.43', 8080)
# 3、准备即将发送的数据,encode表示按照一种编码格式把数据变成字节数组bytes
# 数据一定是字节数据才能发送
datas = input('请输入:').encode('utf-8')

# 4、发送数据,标识一个进程是通过ip+端口+协议
client_socket.sendto(datas, server_host_port)

print('发送完成')

# 5、关闭套接字,其实就是释放了系统资源
client_socket.close()
 


相关教程