博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python学习【第17篇】:网络编程之粘包
阅读量:6682 次
发布时间:2019-06-25

本文共 5509 字,大约阅读时间需要 18 分钟。

一、什么是粘包

须知:只有TCP有粘包现象,UDP永远不会粘包

粘包不一定会发生

如果发生了:1.可能是在客户端已经粘了

      2.客户端没有粘,可能是在服务端粘了

首先需要掌握一个socket收发消息的原理

应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。(因为TCP是流式协议,不知道啥时候开始,啥时候结束)。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

二、发生粘包的两种情况

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会当做一个包发出去,产生粘包)

复制代码
1 from socket import * 2 phone = socket(AF_INET,SOCK_STREAM) 3 phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1) 4 phone.bind(('127.0.0.1',8080)) 5 phone.listen(5) 6 print('start running...') 7  8 coon,addr = phone.accept() #等待连接 9 10 data1 = coon.recv(10) 11 data2 = coon.recv(10) 12 13 print('------------>',data1.decode('utf-8')) 14 print('------------>',data2.decode('utf-8')) 15 coon.close() 16 phone.close()
复制代码
复制代码
1 from socket import *2 import time3 phone = socket(AF_INET,SOCK_STREAM)4 phone.connect(('127.0.0.1',8080))5 6 phone.send('hello'.encode('utf-8'))7 phone.send('helloworld'.encode('utf-8'))8 phone.close()
复制代码

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

复制代码
1 from socket import * 2 phone = socket(AF_INET,SOCK_STREAM) 3 phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1) 4 phone.bind(('127.0.0.1',8080)) 5 phone.listen(5) 6 print('start running...') 7  8 coon,addr = phone.accept() #等待连接 9 10 data1 = coon.recv(2) #一次没有接收完整11 data2 = coon.recv(10)  #下一次接收的时候会先取旧的数据,然后取新的12 # data3 = coon.recv(1024)  #接收等5秒后的信息13 print('------------>',data1.decode('utf-8')) 14 print('------------>',data2.decode('utf-8')) 15 # print('------------>',data3.decode('utf-8')) 16 coon.close() 17 phone.close()
复制代码
复制代码
1 from socket import *2 import time3 phone = socket(AF_INET,SOCK_STREAM)4 phone.connect(('127.0.0.1',8080))5 6 phone.send('hello'.encode('utf-8'))7 time.sleep(5)8 phone.send('haiyan'.encode('utf-8')) 9 phone.close()
复制代码

三、解决粘包的方法

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

复制代码
1 import socket 2 import subprocess 3 import struct 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 5 phone.bind(('127.0.0.1',8080)) #绑定手机卡 6 phone.listen(5) #阻塞的最大数 7 print('start runing.....') 8 while True: #链接循环 9     coon,addr = phone.accept()# 等待接电话10     print(coon,addr)11     while True: #通信循环12         # 收发消息13         cmd = coon.recv(1024) #接收的最大数14         print('接收的是:%s'%cmd.decode('utf-8')) 15 #处理过程 16 res = subprocess.Popen(cmd.decode('utf-8'),shell = True, 17 stdout=subprocess.PIPE, #标准输出 18 stderr=subprocess.PIPE #标准错误 19  ) 20 stdout = res.stdout.read() 21 stderr = res.stderr.read() 22 #先发报头(转成固定长度的bytes类型,那么怎么转呢?就用到了struct模块) 23 #len(stdout) + len(stderr)#统计数据的长度 24 header = struct.pack('i',len(stdout)+len(stderr))#制作报头 25  coon.send(header) 26 #再发命令的结果 27  coon.send(stdout) 28  coon.send(stderr) 29  coon.close() 30 phone.close()
复制代码
复制代码
1 import socket 2 import struct 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone.connect(('127.0.0.1',8080)) #连接服 5 while True: 6     # 发收消息 7     cmd = input('请你输入命令>>:').strip() 8     if not cmd:continue 9     phone.send(cmd.encode('utf-8')) #发送10     #先收报头11     header_struct = phone.recv(4) #收四个12     unpack_res = struct.unpack('i',header_struct) 13 total_size = unpack_res[0] #总长度 14 #后收数据 15 recv_size = 0 16 total_data=b'' 17 while recv_size
复制代码

四、解决粘包问题升级版:完整的解决了

复制代码
1 import socket 2 import subprocess 3 import struct 4 import json 5 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机 6 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 7 phone.bind(('127.0.0.1',8080)) #绑定手机卡 8 phone.listen(5) #阻塞的最大数 9 print('start runing.....') 10 while True: #链接循环 11 coon,addr = phone.accept()# 等待接电话 12 print(coon,addr) 13 while True: #通信循环 14 # 收发消息 15 cmd = coon.recv(1024) #接收的最大数 16 print('接收的是:%s'%cmd.decode('utf-8')) 17 #处理过程 18 res = subprocess.Popen(cmd.decode('utf-8'),shell = True, 19 stdout=subprocess.PIPE, #标准输出 20 stderr=subprocess.PIPE #标准错误 21  ) 22 stdout = res.stdout.read() 23 stderr = res.stderr.read() 24 # 制作报头 25 header_dic = { 26 'total_size': len(stdout)+len(stderr), # 总共的大小 27 'filename': None, 28 'md5': None 29  } 30 header_json = json.dumps(header_dic) #字符串类型 31 header_bytes = header_json.encode('utf-8') #转成bytes类型(但是长度是可变的) 32 #先发报头的长度 33 coon.send(struct.pack('i',len(header_bytes))) #发送固定长度的报头 34 #再发报头 35  coon.send(header_bytes) 36 #最后发命令的结果 37  coon.send(stdout) 38  coon.send(stderr) 39  coon.close() 40 phone.close()
复制代码
复制代码
1 import socket 2 import struct 3 import json 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 phone.connect(('127.0.0.1',8080)) #连接服务器 6 while True: 7     # 发收消息 8     cmd = input('请你输入命令>>:').strip() 9 if not cmd:continue 10 phone.send(cmd.encode('utf-8')) #发送 11 #先收报头的长度 12 header_len = struct.unpack('i',phone.recv(4))[0] #吧bytes类型的反解 13 #在收报头 14 header_bytes = phone.recv(header_len) #收过来的也是bytes类型 15 header_json = header_bytes.decode('utf-8') #拿到json格式的字典 16 header_dic = json.loads(header_json) #反序列化拿到字典了 17 total_size = header_dic['total_size'] #就拿到数据的总长度了 18 #最后收数据 19 recv_size = 0 20 total_data=b'' 21 while recv_size
复制代码

五、struct模块

复制代码
1 #该模块可以把一个类型,如数字,转成固定长度的bytes类型 2 import struct 3 res = struct.pack('i',12345) 4 print(res,len(res),type(res))  #长度是4 5  6 res2 = struct.pack('i',12345111) 7 print(res,len(res),type(res2))  #长度也是4 8  9 unpack_res =struct.unpack('i',res2)10 print(unpack_res)  #(12345111,)11 print(unpack_res[0]) #12345111
复制代码

 

转载于:https://www.cnblogs.com/kcwxx/p/10145249.html

你可能感兴趣的文章
分享一个简单的画刷动画效果:
查看>>
新年伊始也来谈谈Webfont
查看>>
(0,fn)()的执行原理
查看>>
地址栏中输入url到显示出网页间的过程
查看>>
H5 分层屏幕适配
查看>>
Django笔记---环境搭建
查看>>
[译]理解JS中的闭包
查看>>
推荐:聚合局域网服务的扫描插件 LN2(蓝图)
查看>>
自己总结的手写代码片段
查看>>
系统SDK介绍-01
查看>>
copy strong weak assign的区别
查看>>
SpringMVC运行原理
查看>>
Eureka简介以及工作原来
查看>>
iOS 后台语音播报功能开发过程中的那些坑
查看>>
Uniqlo与Fast Retailing发出警告 46万消费者账号遭入侵
查看>>
拯救不会函数的我!!
查看>>
js预解析+作用域+this指向
查看>>
iOS KVO监听readonly属性
查看>>
线性判别分析随记
查看>>
十一课堂|通过小游戏学习Ethereum DApps编程(2)
查看>>