socket模块/TCP协议/黏包处理

socket模块

如果我们需要编写基于网络进行数据交互的程序 意味着我们需要自己通过代码来控制我们之前
所学习的OSI七层(很繁琐 很复杂 类似于我们自己编写操作系统)
socket类似于操作系统 封装了丑陋复杂的接口提供简单快捷的接口

socket 也叫套接字
	基于文件类型的套件字家族(单机)
 	 	AF_UNIX
  基于网络类型的套接字家族(联网)
  	AF_INET
    
#python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET

代码展示

服务端展示:

import socket
service = socket.socket()
# 默认使用的是TCP协议

service.bind(('127.0.0.1',9999)) 
# 把地址绑定到套接字上,设定本地的地址,和一个端口号

service.listen(5) 
# 最大同时监听连接数量

sock,addr = service.accept()
# 等待客户端传输数据

data = sock.recv(1024)
# 设置接受的最大消息字节

print(data.encode('utf8'))
# 打印接收到的消息

sock.send('回复客户端消息'.encode('utf8'))
# 给客户端发送消息 注意消息必须是bytes类型

sock.close()
# 关闭此次连接通道

server.close
# 关闭服务端服务



客户端展示:

import socket

client = socket.socket()

client.connect(('127.0.0.1',9999))
# 通过服务端的地址 去连接服务端

client.send('客户端发送消息给服务端'.encode('utf8'))

data = client.recv(1024)
# 接受服务端发送过来的消息 并设置最大字节
print(data.encode('utf8'))

client.close()
# 断开于服务器的连接

黏包现象

1.在TCP协议下服务端连续执行3次接受消息,recv
2.客户端一次连续执行3次发送消息 send
服务端会一次性接收到客户端发的三次消息我们称这种问题为:'黏包现象'


黏包现在产生的原因
    1.不知道每次接收的数据到底多大. soke.recv(1024) 这里出现问题导致
    2.因为tcp协议是流式协议,数据就像流水一下,都会源源不绝的发送过来,没有间隔,所以导致会针对一些数据量小的,间隔时间短的信息误认为是同一条数据一起接收,或一起发送
    
如果解决黏包现象:核心 如何明确的知道接下来要接收的数据是多大,这样就能控制一次性接收多少数据,不会导致多收

# 如何将长度变化的数据全部完成制作成固定长度的数据

struct模块

import struct

msg = b'我是要发送的内容'
print(len(msg))
# 这样可以获取到待发送数据的真实长度
res = struct.pack('i',len(msg))
# 将数据打包成固定长度,打包模式'i'
print(len(res))
# 经过这样打包后的数据长度为 4  可以作为报头 

real_len = struct.uppack('i',res)
# 这样可以解压打包,获取到真实数据的真实长度



msg = b'我是要发送的内容,即使我非常长 也是可以直接打包的哦'
res = struct.pack('i',len(msg))
print(len(res))
# 较长的数据打包后还是长度为 4 可以作为报头
real_lan = struct.uppick('i',res)
# 解包后还是会展示出真实长度的


'''
利用这个struct模块就可以生成出报头,
   客户端:
    	1.将真实数据转换为bytes类型并计算长度
    	2.利用该模块,将真实长度制作成一个固定长度的报头 报头长度为4
    	3.将固定长度的报头先发送给服务端,服务端只需要在recv括号内填写固定长度的报头数字4 
    	4.发完报头后再发送真实数据
    	
   
   服务端:
       1.服务端发送消息前会先发送一个固定长度的报头
       2.收到报头,利用struct模块反向解包获得真实数据长度
       3.在用recv接收准确真实数据长度即可
'''

    此方法可以暂时解决黏包问题,但是也有弊端
弊端:
  	1. struct模块无法打包数据量较大的数据,任何模式都不可以,大概可以打包的数据大小是 1字节- 8000字节左右
   res = struct.packe('i',18888)
	 # 不建议打包相对较大的数据哦
  
    2.报头是否可以传递更多信息,例如除了传递真实数据的大小,还有其他数据,电影名称 电影大小 电影简介等

struct模块正确用法

相对完美利用struct解决黏包问题的方法

设置一个字典,字典内可以包含多种信息,例如 :电影标题,电影文件大小,电影简介等 然后将字典左右报头 打包发送

data_dict = {'file_name':'倚天屠龙记''file_size': 2812819,
              'file_desc': '一代经典,人人皆知'}

先利用 json 模块 把字典转换为bytes类型,

data_json = json.dumps(data_dict)
res = struct.pack('i',data_json.encode('utf8'))

'''
完美解决方案
     客户端
         1.制作一个真实数据的信息字典(数据长度,数据简介,数据名称)
         2.然后把字典作为报头发送
         3.发送字典数据
         4.发送真实数据
         
     服务端
     		1.接收固定长度的字典报头
     		2.解析出字典的真实长度并接收
     		3.通过字典获得真实数据的各项信息
     		4.接收真实数据长度

'''

处理黏包代码实战

import socket
import struct
import json

服务端:

import json
import socket
import struct


server = socket.socket()
# 1.创建一个TCP协议对象
server.bind(('192.168.1.99',8889))
# 2.服务端设定地址
server.listen(3)
# 3.设定半连接池等待接受最大人数
while True:
    print('等待用户接入中....')
    sock,addr = server.accept()
    # 4.开启接收信息状态。sock=数据,addr = 对方 ip + 端口
    print(f'用户:{addr[0]}已接入...')
    while True:
        data_dict_head = sock.recv(4)
        # 接收报头

        data_dict_len = struct.unpack('i',data_dict_head)[0]
        # 获得报头字典真实长度
        data_dict_bayes = sock.recv(data_dict_len)
        # 接收真实字典

        data_dict = json.loads(data_dict_bayes)
        # 反序列化字典,拿到字典内容

        total_size = data_dict.get('file_size') # 拿到真实数据长度
        print(total_size)
        with open(data_dict.get('file_name'),'wb')as f:
            recv_size = 0
            # 设置已接收字节数量
            while recv_size < total_size:
              # 循环接收数据,判断已接收字节是否和真实全部字节相同
                data = sock.recv(2048)
                # 一次最大接收2048字节
                f.write(data)
                recv_size += len(data)
            print(f'文件:{data_dict.get("file_name")}接收完毕')
            break



客户端:
		
import socket
import struct
import os
import json

client = socket.socket()
# 创建一个TCP协议对象
client.connect(('192.168.1.99', 8889))
# 连接一个服务端地址
file_size = os.path.getsize('/Users/moongod/Desktop/IMG_2368.MOV')
# 获取要传输文件的真实大小
print(file_size)
data_dict = {'file_name': 'IMG_2368.MOV', 'file_size': file_size,
             'file_info': '设计文件'}
# 构建报头字典
data_dict_bytes = json.dumps(data_dict).encode('utf8')
# 打包报头字典
data_dict_len = struct.pack('i', len(data_dict_bytes))
client.send(data_dict_len)
# 发送字典报头
client.send(data_dict_bytes)
# 发送字典
with open('/Users/moongod/Desktop/IMG_2368.MOV', 'rb') as f:
    for line in f:
        client.send(line)
   # 循环发送真实数据


  
'''
个人理解: 
客户端: 1.制作字典报头 	发字典报头     2.发真实字典    3.发真实数据 

服务端: 1.收字典报头 解压出来字典字节长短
          2.收真实字典数据, 解压出来 真实数据字节
         3.收真实数据
'''

posted @   Python-moon  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示