一、粘包现象原理分析

  1、我们先来看几行代码,从现象来分析:

    测试程序分为两部分,分别是服务端和客户端

    服务端.py 

 #!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) server.bind(('127.0.0.1',3306)) server.listen(5) conn,client_addres = server.accept() # data1 = conn.recv(1024)
# print('客户端消息1:',data1) # 客户端消息1: b'helloworld'
# data2 = conn.recv(1024)
# print('客户端消息2:',data2) # 客户端消息2: b'' 客户端两次发送的信息被服务端第一次就全部接受了,即粘包

  客户端.py

 #!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) client.connect(('127.0.0.1',3306)) # client.send('hello'.encode('utf-8'))
# client.send('world'.encode('utf-8'))
#
# data1 = client.recv(3) # 接受3个字节的信息
# print('服务端消息1:',data1) # 服务端消息1: b'hel'
# data2 = client.recv(1024)
# print('服务端消息2:',data2) # 服务端消息2: b'loserver' 客户端两次接收信息混在一起,也发生了粘包

  由以上程序代码,我们不难发现,粘包发生的情况主要有两种:一种是第一次接收的字节数据小于发送的数据量,再次接收时,便会粘包;另一种是第

一次准备接收的字节数超过了发送的数据量,再次发送数据时,便会和第一次数据累积在一起,造成粘包。

  2、那么这到底是原因导致的呢?--->原因在于内部机制。

    主要有以下几点: 

1、不管 recv 还是 send 都不是直接接受对方的数据,而是操作自己的操作系统内存。
--> 不是一定一个send对应一个recv(可以1对n,或者n对1) 2、recv:
wait data 耗时长(一是等待程序发送,二是网络延迟)
send:
copy data 时间短
3、优化算法(Nagle算法),将多次时间间隔较短且数据量小的数据,合并成一个大的数据块,
然后封包发送。这样接收方就收到了粘包数据。

    从原理上看:程序实际上无权直接操作网卡的,你操作网卡都是通过操作系统给用户程序暴露出来的接口,那每次你的程序要给远程发数据时,其实是先把数据从用户态copy到内核态,这样的操作是耗资源和时间的,频繁的在内核态和用户态之前交换数据势必会导致发送效率降低, 因此socket 为提高传输效率,发送方往往要收集到足够多的数据后才发送一次数据给对方。若连续几次需要send的数据都很少,通常TCP socket 会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

    如下图所示:

    

    那么,我们有什么解决方法没呢?

    答案肯定是有的,而且有好几种,接着往下看,任君挑选!

二、解决粘包问题的几种方法

    按照方法的适用范围,可以分为以下几个阶段:

    1、凡人阶段

      粘包最简单解决方法,接收信息前事先已知信息的长度,指定接收长度。 

      服务端.py   

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) server.bind(('127.0.0.1',3306)) server.listen(5) conn,client_addres = server.accept() conn.send('hello'.encode('utf-8'))
conn.send('server'.encode('utf-8'))

      客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) client.connect(('127.0.0.1',3306)) data1 = client.recv(5)
print('服务端消息1:',data1) # 服务端消息1: b'hello'
data2 = client.recv(1024)
print('服务端消息2:',data2) # 服务端消息2: b'server'

    2、修仙阶段

      预备知识了解

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import struct # struct.pack() 封装固定长度的报头,封装后的类型为bytes类型
res = struct.pack('i',2056) # i 表示数据类型,2056 表示真实数据的长度
print(res,type(res),len(res)) # b'\x08\x08\x00\x00' <class 'bytes'> 4
res2 = struct.pack('i',1024)
print(res2,type(res2),len(res2)) # b'\x00\x04\x00\x00' <class 'bytes'> 4 # struct.unpack() 解析报头数据,解析后的数据存储在一个元组内
data = struct.unpack('i',res) # i 仍然为数据类型,res为封装的包头
# print(data) # (2056,)
print(data[0]) # 2056 获取真实数据的长度

    服务端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket
import subprocess
import struct
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) # 实例化一个套接字对象
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 端口重用
server.bind(('127.0.0.1',3231)) # 绑定IP和端口
server.listen(5) # 监听 while True: # 链接循环
conn,client_addres = server.accept()
while True: # 通信循环
try: # windows适用
#1 接收命令
cmd = conn.recv(8096)
if not cmd:break # linux适用(未收到命令)
# 2 执行命令,拿到结果
obj = subprocess.Popen(cmd.decode('gbk'),shell=True,
stdout=subprocess.PIPE, # 命令正确时创建一条通道
stderr=subprocess.PIPE) # 命令错误时创建另一条通道
stdout = obj.stdout.read() # 命令正确执行返回的信息
stderr = obj.stderr.read() # 命令错误时返回的信息
# print('from client',cmd)
# 3 把结果返回给客户端
# 3.1 制定固定长度的报头
total_size = len(stdout) + len(stderr)
header = struct.pack('i',total_size)
# 3.2 发送报头信息
conn.send(header)
# 3.3 发送真实数据信息
conn.send(stdout)
conn.send(stderr)
# conn.send('from server'.encode('utf-8')) # 发命令 except ConnectionResetError:
break
conn.close() server.close()

    客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
import socket
import struct client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) client.connect(('127.0.0.1',3231)) while True: # 通信循环
cmd = input('输入命令:').strip() # 输入命令
if not cmd:continue
client.send(cmd.encode('gbk')) # 发送命令
# 1、接收报头信息
header = client.recv(4)
# 2、获取真实数据的长度
total_size = struct.unpack('i',header)[0]
recv_size = 0 # 初始化接收的信息长度
recv_data = b'' # 初始化接收的数据
while recv_size < total_size: # 判断已接收的数据长度是否大于需要接收的数据
data = client.recv(1024) # 每次接收的数据量
recv_data += data # 已经接收的总数居
recv_size += len(data) # 已经统计的数据长度
print(recv_data.decode('gbk')) client.close()

    3、成仙阶段

      服务端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket
import subprocess
import struct
import json server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',3301))
server.listen(5) while True:
conn,client_adress = server.accept()
while True:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('gbk'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 把执行结果返回给客户端
# 1、制定固定长度的报头
head_dic = {
'file_name':'head.txt',
'md5':'xxxxxxxx',
'total_size':len(stdout)+len(stderr)
} # 报头
header_json = json.dumps(head_dic) # json转成字符串
header_bytes = header_json.encode('gbk') # 字符串转成bytes类型
# 2、先发送报头长度
conn.send(struct.pack('i',len(header_bytes)))
# 3、发送报头
conn.send(header_bytes)
# 4、发送执行后的信息
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close() server.close()

      客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket
import struct
import json client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',3301)) while True:
cmd = input('输入命令>>:').strip()
if not cmd:continue
client.send(cmd.encode('gbk'))
# 获取服务器返回的信息
# 1、获取报头长度
obj = client.recv(4)
header_size = struct.unpack('i',obj)[0]
# 2、获取报头
header_bytes = client.recv(header_size)
# 3、从报头解析出对真实数据的描述(数据长度)
header_json = header_bytes.decode('gbk') # 转成字符串
header_dic = json.loads(header_json) # 转成报头原数据类型
total_size = header_dic['total_size'] # 获取真实数据长度
# 4、获取真实数据
recv_size = 0
recv_data = b''
while recv_size < total_size:
data = client.recv(1024)
recv_data += data
recv_size += len(data)
print(recv_data.decode('gbk')) client.close()

三、UDP协议 

  1、含义: UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务的协议。  

  2、特点:不使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这也决定了UDP不存在粘包现象。

  3、与TCP协议的区别

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

    tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

  4、UDP通信示例:

    服务端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',3361)) while True:
res,client_adds = server.recvfrom(1024)
print(res,client_adds)
server.sendto('hello,from server!'.encode('gbk'),client_adds)

    客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
while True:
res = input('cmd:').strip()
client.sendto(res.encode('gbk'),('127.0.0.1',3361))
data,adds = client.recvfrom(1024)
print("from server:",data,adds)

  5、测试UDP通信是否粘包

    服务端.py   

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong from socket import * server = socket(AF_INET,SOCK_DGRAM) server.bind(('127.0.0.1',3321)) data1 = server.recvfrom(1024)
print('data1>>:',data1) # data1>>: (b'this is data1', ('127.0.0.1', 62204))
data2 = server.recvfrom(1024)
print('data2>>:',data2) # data2>>: (b'this is data2', ('127.0.0.1', 62204))

    客户端.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong from socket import * client = socket(AF_INET,SOCK_DGRAM) mes = input('>>:').strip()
client.sendto(mes.encode('gbk'),('127.0.0.1',3321))
mes = input('>>:').strip()
client.sendto(mes.encode('gbk'),('127.0.0.1',3321))
client.close() '''
UDP协议不会发生粘包现象,不需要建立链接,所以发送信息时需要指定IP和端口,
接收信息时会返回发送方的IP和端口。
'''

网络编程基础之粘包现象与UDP协议的更多相关文章

  1. 网络编程基础:粘包现象、基于UDP协议的套接字

    粘包现象: 如上篇博客中最后的示例,客户端有个 phone.recv(2014) , 当服务端发送给客户端的数据大于1024个字节时, 多于1024的数据就会残留在管道中,下次客户端再给服务端发命令时 ...

  2. 网络编程 - socket通信/粘包/文件传输/udp - 总结

    socket通信 1.简单的套接字通信 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bin ...

  3. 网络编程基础socket 重要中:TCP/UDP/七层协议

    计算机网络的发展及基础网络概念 问题:网络到底是什么?计算机之间是如何通信的? 早期 : 联机 以太网 : 局域网与交换机 广播 主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无 ...

  4. python 网络编程 缓冲和粘包

    tcp:属于长连接,与一个客户端进行连接了以后,其他的客户端要等待,要连接另外一个,必须优雅的断开前面这个客户端的连接. 允许地址重用:在bind IP地址和端口之前加上,# server.setso ...

  5. (网络编程)基于tcp(粘包问题) udp协议的套接字通信

    import   socket 1.通信套接字(1人1句)服务端和1个客户端 2.通信循环(1人多句)服务端和1个客户端 3.通信循环(多人(串行)多句)多个客户端(服务端服务死:1个客户端---&g ...

  6. UNIX网络编程——Socket/TCP粘包、多包和少包, 断包

    为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个数据包不完整.为什么会这样吗,我们用mina这样通信框架,还会出现这种问题? TCP(transport cont ...

  7. 网络编程 基础 基于socket的tcp和udp连接

    网络开发的框架 C/S B/S 架构 程序都是bs架构的程序(b指浏览器,s指服务器) c(client)是客户端,s(server)是服务器 bs架构是cs架构的一种 未来的趋势, pc端bs架构, ...

  8. java网络编程(2)InetAddress 类及udp协议

    InetAddress 类 JDK中为开发网络应用程序提供了java.net包,该包下的类和接口差点儿都是为网络编程服务的. InetAddress:用于描写叙述IP地址的对象 InetAddress ...

  9. 【网络编程2】网络编程基础-发送ICMP包(Ping程序)

    IP协议 网络地址和主机协议 位于网络层的协议,主要目的是使得网络能够互相通信,该协议使用逻辑地址跨网络通信,目前有两个版本IPV4,IPV6. 在IPV4协议中IP地址是一个32位的数备,采用点分四 ...

随机推荐

  1. hadoop常见错误总结三

    问题导读:1.... could only be replicated to 0 nodes, instead of 1 ...可能的原因是什么?2.Error: java.lang.NullPoin ...

  2. 使用 openresty 修改请求内容

    1. 目的    动态修改 html 页面内容   2. 使用方式    openresty  在 header_filter 阶段 以及body_filter 阶段进行数据修改   3. 源码  此 ...

  3. android 自己定义checkbox 背景图无效的问题

    http://blog.csdn.net/zuolongsnail/article/details/7106586  正常的定义能够參考这个网址  可是我參考它以后发现我执行时候 根本不工作嘛  结果 ...

  4. webpack wepack-dev-server 对应版本

    webpack wepack-dev-server 对应版本 事情起因是使用 extract-text-webpack-plugin 对 css 和 js 打包进出现 Tapable.plugin i ...

  5. Spring整合Quartz定时器

    1.添加jar #此处省略spring核心jar包 <dependency> <groupId>org.quartz-scheduler</groupId> < ...

  6. 使用Maven运行Solr(翻译)

    Solr是一个使用开源的搜索服务器,它采用Lucene Core的索引和搜索功能构建,它可以用于几乎所有的编程语言实现可扩展的搜索引擎. Solr的虽然有很多优点,建立开发环境是不是其中之一.此博客条 ...

  7. Bash命令查找本机公网IP

    用Bash命令查找本机公网IP wget -qO - http://ipecho.net/plain; echo

  8. nginx和php-fpm通信的两种方式 unix socket和TCP

    nginx和fastcgi的通信方式有两种,一种是TCP 一种是unix socket TCP使用的是 127.0.0.1:9000端口,将fastcgi_pass参数修改为127.0.0.1:900 ...

  9. POJ 2674 Linear world(弹性碰撞)

    Linear world Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 4426   Accepted: 1006 Desc ...

  10. ApacheOFBiz的相关介绍以及使用总结(一)

    由于最近一段时间在给一个创业的公司做客户关系管理CRM系统,限于人力要求(其实是没有多少人力),只能看能否有稳定,开源的半成品进行改造,而且最好不需要前端(js)相关开发人员的支援就可以把事情做成,经 ...