黏包现象
当发送网络数据时,TCP协议会根据Nagle算法将时间间隔短、数据量小的多个数据包打包成一个数据包,先发送到自己操作系统的缓存中,然后操作系统将数据包发送到目标程序所对应操作系统的缓存中,最后将目标程序从缓存中取出。
而第一个数据包的长度,应用程序并不知道,所以会直接取出数据或者取出部分数据,留部分数据在缓存中,取出的数据可能第一个数据包和第二个数据包粘到一起。
当超过recv()方法中规定的最大字节数后,会发现内容第一次只会发送到1024【假设recv(1024)】个字节的内容,而剩下的字节内容存放在接收缓冲区将会在下一次接收时候被发送过来。
下图是我们需要完整接收并打印的内容,可以发现图中上部分黄框的内容并没有被接收打印出来,而是直接出现了需要我们再发送需要发送的数据。这是因为我们在上图中已经接收到了最到的1024字节数并且打印完成,进入了新的循环。
当我们在新的循环中发送了数据2 后,此时服务器并没有传输数据过来,但是我们能接收到数据。发现数据是之前我们剩下在缓存区中需接收打印的内容。
这用到了recv()方法的缓存区原理。即假如接收的数据设定为最大为1024,那么超过1024剩下的字节数据将存放到接收缓冲区,等待下一次接收时候发送过去【当服务次在下一次要发送数据的时候,缓冲区的文件优先于当前需要发送的内容被发过去】。
改变recv():方法中的参数,虽然能暂时解决接收完全的问题,但是参数不能无限大,因为发送缓冲区和接收缓冲区的内存数是固定不变的。不能超过缓冲区的大小。
黏包现象
黏包是在TCP协议中出现的现象,将间隔短、数据量小的多个数据包打包成一个数据包。如下图所示:
当代码有两个接收存在的时候:我们预想中的得到的是分开的两个数据,即先收到hello 再收到world。但知道有黏包现象的存在后,预想会是hello 和world一起发送。那么预想中可能是先hello 再hello world 。或者连续两次hello world 等等情况。最终,得到的是hello world 一起发送接收到,第二次接收到的是空。这就是黏包现象。
解决黏包现象
不方便的解决方案:
手动添加发送的等待时间,那么接收到的时候就是接收了两个分开的数据。
但是次方法并不好,因为这是手动的人为设置等待时间,如果不小心删除那么还是会发生黏包现象。
如果将recv参数进行限制比如第一个接收少的,第二个接收多的,会发现并没有达到我们想要的效果,因为第一个接收所剩下的字节会和第二个接收一并被接收打印出来。
先接收缓冲区的内容再接收后发送的内容。
由此将接收进化,多次接收少量字节并拼接后再等待一段时间打印第二次接收数据。
struct模块
导入struct模块
struct.pack(fmt,values):将values的参数根据fmt指定的形式打包。并且是字节形式和固定长度。即在values的中的参数在一定长度内不管多少,都是固定长度不变。
strucr.pack(格式,数据内容):打包后得到的是字节,即一串乱码并不是我们明白的样式。
所以需要我们进行解包。
struct.unpack(fmt, string):解包,以元组形式返回数据(string)的内容。
注意:解包的格式也必须填写。
正确解决方式:
发送方:
- 发送数据长度
- 发送真实数据
接收方:
- 先接收数据长度
- 接收真实的数据
服务器代码:
"""
服务端
1、创建套接字
2、绑定服务器
3、主动为被动listen(128)
4、监听创建新套接字 accept()
5、发送数据长度
6、发送真实数据
7、关闭套接字
"""
import socket
import struct
def fuwuduan():
tes_fu = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tes_fu.bind(("192.168.1.3", 7890))
tes_fu.listen(128)
new_tes_fu, new_duan = tes_fu.accept()
# 需要发送的文章(大篇幅)
txt_1 = """HTTP协议的全称(HyperText Transfer Protocol),即 超文本传输协议。
超文本是超级文本的缩写,指 超越文本限制或者 超链接(a标签),比如图片、音乐、视频、超链接等等都属于超文本。
HTTP协议的制作者是蒂姆·伯纳斯·李。于1991年设计出来的,HTTP协议设计之前目的是传输网页数据,现在允许传输任意类型的数据。
传输HTTP协议格式的数据基于TCP传输协议的,发送数据之前需要先建立连接(常连接)。"""
# len(txt_1)文本的真实长度,解析将这设为一个固定长度
str_len_txt = struct.pack("i", len(txt_1))
# 发送文本长度
new_tes_fu.send(str_len_txt)
# 发送真实文本
new_tes_fu.send(txt_1.encode(("utf-8")))
# 关闭套接字
new_tes_fu.close()
tes_fu.close()
fuwuduan()
客户端代码:
"""
客户端
1、创建套接字
2、连接服务器
3、接收长度 send
4、接收真实数据
5、关闭套接字
"""
import socket
import struct
def kehuduan():
tes_ke = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tes_ke.connect(("192.168.1.3", 7890))
# 通过struct模块的pack方法可以将接收的数据写死,即4
# 接收的是服务器发来的文章长度的字节
txt_len = tes_ke.recv(4)
# 解包文字长度,提取出文本长度
txt = struct.unpack("i", txt_len)[0]
# 接收真实文本
# 初始长度默认为0
toto_len = 0
# 初始接收字节为空
toto_txt = b''
# 当长度大于接收到的长度后退出循环
while toto_len < txt:
# 每次接收到的数据进入toto_txt中
txt_recv = tes_ke.recv(1024)
toto_txt += txt_recv
# 循环退出的条件,添加具体接收到的文字的长度
toto_len += len(txt_recv)
# 打印接收到的数据
print(toto_txt.decode("utf-8"))
# 关闭套接字
tes_ke.close()
kehuduan()