粘包现象:

如上篇博客中最后的示例,客户端有个 phone.recv(2014) , 当服务端发送给客户端的数据大于1024个字节时, 多于1024的数据就会残留在管道中,下次客户端再给服务端发命令时,残留在管道的数据会先发送给客户端,新命令产生的数据会排在上次命令残留数据的后面发送到客户端,即两次结果的数据粘在一起了, 这个就是粘包现象。

粘包现象的原理分析:

# 运行一个软件或程序需要的硬件:CPU、内存、硬盘
# CPU负责执行;CPU执行需要数据,而数据从内存中取,最后可以把数据存到硬盘(内存速度会比硬盘快很多)
# 操作系统所占的内存空间和应用程序所占的内存空间互相隔离 # 客户端的 phone.send(数据) send是应用软件的代码,是发送给操作系统的命令,应用软件先把要发送的数据copy给操作系统的内存然后让操作系统把该数据发送出去
# 应用软件把数据复制给操作系统后,操作系统怎么发这个数据应用软件控制不了
# recv的完成需要2步:1.recv对应的操作系统等待接收对方传过来的数据;(耗时长);2.recv的操作系统将接收的数据复制给应用软件(耗时短,因为是本地copy)
# send的完成只需要1步: 将数据从自己(应用软件)的内存空间复制到操作系统的内存空间。 # send和recv对比
# 1. 不管是recv还是send都不是在直接接收对方的数据,而是在操作自己的操作系统内存---> so,不是一个send就要对应一个recv
# 2. recv:
# wait data 耗时非常长
# copy data
# send:
# copy data
# 3. TCP协议的特点:发送端为了将多个发往接收端的包能有效的发到对方,会将多次间隔较小且数据量小的数据,合并成一个大的数据包,然后进行封装;这样接收端就难以分辨出来了,即面向流的通信是无消息保护边界的。

粘包解决方法普通版(制作自己的“报头”):

补充知识点struct模块:

import struct

# 制作报头
res = struct.pack("i",123498654) # 输出结果是bytes格式 # i 代表整型,如果是整型,res这个bytes就是固定长度4(跟后面整数具体的大小无关); #有两个参数:第一个是格式("i"代表整型),第二个是值
print(res,type(res),len(res))
"""
struct.pack可用于制作报头,把描述信息传入第二个参数value
"""
# 打印结果
# b'\x9ep\\\x07' <class 'bytes'> 4 # 解析报头
unpack_res = struct.unpack("i",res) # 对res这个bytes格式的字符串进行解包 # 解包结果为元祖形式 # 也有两个参数: 1. 格式("i") 2. bytes格式的字符串
print(unpack_res) # 打印结果
# (123498654,)

粘包解决方法普通版客户端代码如下:

import socket
import struct client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",9901))
while True:
# 1. 发命令
cmd = input(">>>").strip()
if not cmd:continue
client.send(cmd.encode("utf-8")) # 2. 拿到命令结果并打印
# 第一步:先收“报头”
head = client.recv(4) # head为bytes格式; # 由于报头的长度固定为4,所以recv应该为4
# 第二步:从报头解析出对真实数据的描述信息(真实数据的长度)
total_size = struct.unpack("i",head)[0] # 对head这个报头解析 # struct.unpack()的结果是元祖的形式 # 第三步:接收真实的数据
"""开始循环接收服务端发来的真实数据"""
recv_size = 0 # 用于计算接收到的bytes数
total_recv_res = b"" # 设置一个bytes格式的空字符串,用于拼接、接收服务端发来的真实数据
while recv_size < total_size: # 已经接收的bytes数小于服务端发送的全部字节数
recv_res = client.recv(1024)
total_recv_res += recv_res # 把每次从服务端接收到的真实数据添加到total_recv_res 里面
recv_size += len(recv_res) # 每次从服务端接收到的数据的bytes数加到 recv_size里面; 不要用 recv_size += 1024,这种方法不能准确计算出bytes数
print(total_recv_res.decode("gbk")) phone.close()

服务端代码:

import subprocess
import struct
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1",9901))
phone.listen(5) while True:
conn,client_addr = phone.accept() while True:
try:
# 1. 收命令
cmd = conn.recv(1024) # 2. 执行命令,拿到结果
obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read() # 3. 把命令结果返回给客户端
# 第一步:制定固定长度的“报头”(报头一定需要是固定长度)
head = struct.pack("i",len(stdout)+len(stderr)) # head 是bytes格式,长度固定为4 # 第二步: 把报头发送给客户端
conn.send(head) # 第三步:再发送真实的结果数据
# conn.send(stdout+stderr) 解决的方法如下所示:
conn.send(stdout)
conn.send(stderr) # 不需要再用“+”,因为这种形式的发送TCP协议就会把数据粘在一起
except ConnectionResetError:
break conn.close()
phone.close()

粘包解决最终版(利用字典制作自己的报头)

服务端代码:

import socket
import subprocess
import json
import struct server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",8080))
server.listen(5) while True:
conn,addr = server.accept() while True:
try:
# 1. 收命令
cmd = conn.recv(1024) # 2. 处理命令
obj = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = obj.stdout.read() # bytes格式
stderr = obj.stderr.read() # 3. 把处理结果发送给客户端
"""
报头应该包含多种信息,而不只是只包含真实信息的长度,所以考虑利用字典去制定报头
"""
# 3.1 用字典形式制作报头
header_dict = {
"filename":"cmd处理",
"md5":"xxxxxx",
"total_size":len(stdout)+len(stderr)
} # 字典不能用于send(),只有bytes才可以
header_json = json.dumps(header_dict) # 将报头字典转化成json格式的字符串
header_bytes = header_json.encode("utf-8") # 将json形式的字符串转化成bytes格式
"""
字典是报头,报头转化成bytes之后你并不能确定bytes的个数,但报头又需要是固定长度,
所以先把head_bytes(报头的bytes格式)的长度利用struct模块打包成固定长度发送给客户端,然后再把header_bytes发送给客户端,
对应的,客户端先收报头长度,然后再接收报头长度个数的bytes,那么客户端第二次接收的bytes就是报头的完整信息
"""
# 3.2 发送报头bytes的长度
header_length = struct.pack("i",len(header_bytes)) # 把报头bytes的个数利用struct.pack()打包、制定成固定长度(4)
conn.send(header_length)
# 3.3 发送报头bytes的真实信息
conn.send(header_bytes)
# 3.4 发送处理结果的真实数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close()

客户端代码:

import socket
import json
import struct client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8080)) while True:
cmd = input(">>>").strip()
if not cmd: continue
client.send(cmd.encode("utf-8")) # 1. 接收报头的长度
obj_contain_length = client.recv(4) # 因为报头bytes的长度已经经过struct.pack()的打包,长度固定为4
header_bytes_length = struct.unpack("i",obj_contain_length)[0] # 报头bytes的长度
# 2. 接收报头的数据
header_bytes = client.recv(header_bytes_length) # 接收报头bytes个数的bytes数,就是完整的报头bytes信息
header_json = header_bytes.decode("utf-8") # 将bytes格式解码成json字符串格式
header_dict = json.loads(header_json) # 将报头的json字符串格式反序列化得到报头的字典形式
total_size = header_dict.get("total_size") # 得到报头字典里的处理结果的bytes数
# 3. 接收处理结果的数据
recv_size = 0
total_recv_bytes = b""
while recv_size < total_size:
recv_bytes = client.recv(1024)
total_recv_bytes += recv_bytes
recv_size += len(recv_bytes) print(total_recv_bytes.decode("gbk")) client.close()

文件传输功能:

文件的目录结构如下:

服务端代码:

import socket
import json
import struct
import os
import sys BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR) server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",8089))
server.listen(5) while True:
conn,addr = server.accept() while True:
try:
# 1. 收命令
res = conn.recv(1024) # b"get a.txt" # 2. 解析命令,提取相应命令参数
cmd,file_name = res.decode("utf-8").split() # ["get","a.txt"] # 3. 以读的模式打开文件,读取文件内容发送给客户端
# 第一步:用字典形式制作报头
file = os.path.join(BASE_DIR,"share",file_name)
header_dict = {
"filename":file_name,
"md5":"xxxxxx",
"total_size": os.path.getsize(file)
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode("utf-8") # 第二步:发送报头bytes的长度
header_length = struct.pack("i",len(header_bytes))
conn.send(header_length)
# 第三步:发送报头bytes的真实信息
conn.send(header_bytes) # 4. 发送处理结果的真实数据
with open(file,"rb") as f:
for line in f:
conn.send(line) # 单行发送跟一下全部发送效果上没有区别,因为单行发送也是粘在一起 except ConnectionResetError:
break
conn.close()
server.close()

客户端代码:

import socket
import json
import struct
import os,sys BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR) client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8089)) while True:
# 1. 发命令
cmd = input(">>>").strip()
if not cmd: continue
client.send(cmd.encode("utf-8")) # 2. 以写的模式打开一个新文件,把服务端发来的文件内容写入新文件
# 第一步: 接收报头的长度
obj_contain_length = client.recv(4)
header_bytes_length = struct.unpack("i",obj_contain_length)[0]
# 第二步:再收报头,从报头中解析出对真实信息的描述信息
header_bytes = client.recv(header_bytes_length)
header_json = header_bytes.decode("utf-8")
header_dict = json.loads(header_json)
total_size = header_dict.get("total_size")
file_name = header_dict["filename"]
# 3. 接收真实的数据
with open(os.path.join(BASE_DIR,"download",file_name),"wb") as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(1024)
f.write(line)
recv_size += len(line)
print("文件总大小:%s;已下载:%s;已下载比例:%s"%(total_size,recv_size,(recv_size/total_size))) client.close()

文件传输功能函数版:

客户端代码:

import socket
import json
import struct
import os,sys BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR) """下载功能:即从服务端接收文件"""
def get(client,cmd):
# 2. 以写的模式打开一个新文件,把服务端发来的文件内容写入新文件
# 第一步: 接收报头的长度
file_name = cmd[1]
obj_contain_length = client.recv(4)
header_bytes_length = struct.unpack("i", obj_contain_length)[0]
# 第二步:再收报头,从报头中解析出对真实信息的描述信息
header_bytes = client.recv(header_bytes_length)
header_json = header_bytes.decode("utf-8")
header_dict = json.loads(header_json)
total_size = header_dict.get("total_size")
# file_name = header_dict["filename"]
# 3. 接收真实的数据
with open(os.path.join(BASE_DIR, "download", file_name), "wb") as f:
recv_size = 0
while recv_size < total_size:
line = client.recv(1024)
f.write(line)
recv_size += len(line)
print("文件总大小:%s;已下载:%s;已下载比例:%s" % (total_size, recv_size, (recv_size / total_size))) """上传功能:即发送文件给服务端"""
def put(client,cmd):
file_name = cmd[1]
file = os.path.join(BASE_DIR,"download",file_name)
# 1. 制定报头
file_size = os.path.getsize(file)
head_dict ={
"filename":file_name,
"dm5": "xxxxxxx",
"total_size":file_size
}
head_json = json.dumps(head_dict)
head_bytes = head_json.encode("utf-8")
# 2. 发送报头
head_bytes_length = struct.pack("i",len(head_bytes))
client.send(head_bytes_length)
client.send(head_bytes)
# 3. 发送真实数据
with open(file,"rb") as f:
send_size = 0
for line in f:
client.send(line)
send_size += len(line)
print("文件总大小:%s;已上传:%s;上传比例:%s"%(file_size,send_size,(send_size/file_size))) def run():
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8089)) while True:
# 1. 发命令
cmd = input(">>>").strip()
if not cmd: continue
client.send(cmd.encode("utf-8"))
cmd_list = cmd.split()
cmd_dict = {
"get":get,
"put":put
}
for k,v in cmd_dict.items():
if cmd_list[0] == k:
v(client,cmd_list) client.close() if __name__ == "__main__":
run()

服务端代码:

import socket
import json
import struct
import os
import sys BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR) """下载功能:即发送数据给客户端"""
def get(conn,cmd):
file_name = cmd[1]
file = os.path.join(BASE_DIR, "share", file_name)
header_dict = {
"filename": file_name,
"md5": "xxxxxx",
"total_size": os.path.getsize(file)
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode("utf-8") header_length = struct.pack("i", len(header_bytes))
conn.send(header_length)
conn.send(header_bytes) # 发送处理结果的真实数据
with open(file, "rb") as f:
for line in f:
conn.send(line) """上传功能:即接收客户端发来的数据"""
def put(conn,cmd):
file_name = cmd[1]
# 1. 先接收报头长度
bytes_header_length = conn.recv(4)
header_length = struct.unpack("i",bytes_header_length)[0]
# 2. 接收报头数据
header_bytes = conn.recv(header_length) # bytes格式的字符串
header_json = header_bytes.decode("utf-8")
header = json.loads(header_json)
total_size = header["total_size"] # 要上传数据的总大小
# 3. 接收真实的数据
with open(os.path.join(BASE_DIR,"share",file_name),"wb") as f:
recv_size = 0
while recv_size < total_size:
recv_bytes = conn.recv(1024)
f.write(recv_bytes)
recv_size += len(recv_bytes) def run():
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",8089))
server.listen(5) while True:
conn,addr = server.accept() while True:
try:
# 1. 收命令
res = conn.recv(1024) # b"get a.txt"
# 2. 解析命令,提取相应命令参数
cmd = res.decode("utf-8").split() # ["get","a.txt"] cmd_dict = {
"get": get,
"put": put
}
for k,v in cmd_dict.items():
if cmd[0] == k:
v(conn,cmd) except ConnectionResetError:
break
conn.close()
server.close() if __name__ == "__main__":
run()

基于UDP协议的套接字:

客户端代码:

from socket import *

client = socket(AF_INET,SOCK_DGRAM)

"""
UTP不需要建链接(通道)。所以不需要 connect
""" while True:
msg = input(">>>").strip()
client.sendto(msg.encode("utf-8"),("127.0.0.1",8080)) # sendto传入两个参数:数据和接收端的IP和端口 # msg也是bytes格式 data = client.recvfrom(1024)
print(data) client.close() # 运行结果:
# (b'HELLO', ('127.0.0.1', 8080)) """
UTP协议不会粘包
UTP协议能够发送空消息,所以不需要写 if not msg: continue
UTP协议一定是一个sendto对应一个 recvfrom
对于recvfrom(1024),在Windows上,如果接收的数据大于1024个bytes,会报错;在Linux上,如果接收的数据大于1024个bytes,程序只接收1024个,多余的数据就丢失了
"""

客户端代码:

from socket import *  # 导入socket模块时可以利用 import *

server = socket(AF_INET,SOCK_DGRAM)  # DGRAM 是UDP协议,即“数据报协议”
server.bind(("127.0.0.1",8080)) # UDP协议也需要bind """
UTP协议没有 listen和accept;因为UTP不需要建通道,而TCP中的listen和accept是为了建通道
"""
while True:
data,addr = server.recvfrom(1) # 接收数据;收到的也是bytes格式 # 接收到的数据是元祖形式:第一个元素是数据信息,第二个是发送端的IP和端口 # 数据信息也是bytes格式
print(data,addr) server.sendto(data.upper(),addr) # recvfrom中包含发送端的IP和端口,还通过这个IP端口发发送端回数据
server.close() # 运行结果:
# b'hello' ('127.0.0.1', 53729)

网络编程基础:粘包现象、基于UDP协议的套接字的更多相关文章

  1. 网络编程----socket介绍、基于tcp协议的套接字实现、基于udp协议的套接字实现

    一.客户端/服务器架构(C/S架构)                                                即C/S架构,包括: 1.硬件C/S架构(打印机) 2.软件C/S架 ...

  2. 网络编程(基于udp协议的套接字/socketserver模块/进程简介)

    一.基于UDP协议的套接字 TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就 ...

  3. 基于udp协议的套接字及udp协议粘包问题

    udp协议的套接字 udp协议传输  服务端和客户端没有建立连接一说. import socket # 总结一下基础工作流程:服务端生成套接字并绑定ip_port,进入数据传输循环,服务端接受客户端发 ...

  4. 网络编程之基于UDP协议的套接字编程、基于socketserver实现并发的socket

    目录 基于UDP协议的套接字编程 UDP套接字简单示例 服务端 客户端 基于socketserver实现并发的socket 基于TCP协议 server类 request类 继承关系 服务端 客户端1 ...

  5. python 之 网络编程(基于UDP协议的套接字通信)

    8.5 基于UDP协议的套接字通信 UDP协议:数据报协议 特点:无连接,一发对应一收,先启动哪一端都不会报错 优点:发送效率高,但有效传输的数据量最多为500bytes 缺点:不可靠:发送数据,无需 ...

  6. 网络编程(四)--基于udp协议的套接字、socketserver模块

    一.UDP协议(数据报协议) 1.何为udp协议 不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包. 以太网头 ip头                  ...

  7. 网络编程[第二篇]基于udp协议的套接字编程

    udp协议下的套接字编程 一.udp是无链接的    不可靠的 而上篇的tcp协议是可靠的,会有反馈信息来确认信息交换的完成与否 基于udp协议写成的服务端与客户端,各司其职,不管对方是否接收到信息, ...

  8. 网络编程(四)——基于udp协议的套接字socket、socketserver模块的使用

    基于udp协议的套接字.socketserver模块 一.UDP协议(数据报协议) 1.何为udp协议 不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包. ...

  9. 基于UDP协议的套接字编程

    基于udp协议的套接字编程 UDP是无链接的,先启动那一端都不会报错 UDP协议是数据报协议,发空的时候也会自带报头,因此客户端输入空,服务端也能收到 一般不用与传输大数据 虽然没有粘包问题,但是不能 ...

随机推荐

  1. Android 线程池系列教程(5)与UI线程通信要用Handler

    Communicating with the UI Thread 上一课 下一课 1.This lesson teaches you to Define a Handler on the UI Thr ...

  2. java urlEncode 和urlDecode的用法

    前台进行http请求的时候 如果要对中问进行编码,要使用两次编码 String zhName=urlEncode.encode((urlEncode.encode("中文",&qu ...

  3. iOS--多线程之线程间通讯

    线程间通讯 一.NSThread 1.简单说明 ①线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 ②线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特 ...

  4. 基于C++11的call wrapper

    要在C++中应用AOP,不像在其他的基于解释器的语言中那么方便,作为一种静态语言,如果给函数或者类的方法造一个wrapper,在wrapper里面嵌入调用前的代码和调用后的代码,也能达到一定程度的代码 ...

  5. Sass的的使用二

    1.嵌套输出方式 nested Sass 提供了一种嵌套显示 CSS 文件的方式.例如 nav { ul { margin: 0; padding: 0; list-style: none; } li ...

  6. Harris角点检测原理详解

    http://blog.csdn.net/lwzkiller/article/details/54633670 关于角点的应用在图像处理上比较广泛,如图像匹配(FPM特征点匹配).相机标定等.网上也有 ...

  7. IMX6核心板系列解决方案-工业级|商业级|四核|双核|Plus核心板

    i.MX 6Quad四核商业级和工业级系列的应用处理器将可扩展平台与广泛的集成和高能效处理功能相结合,尤其适合多媒体应用.i.MX6 Quad处理器的特性包括: 满足操作系统和游戏的MIPS需求,增强 ...

  8. xxtea 文件加密与解密

    加密 cocos luacompile -s src -d dst_dir -e -b xxxxx -k xxxxx --disable-compile 解密 cocos luacompile -s ...

  9. Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

    Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>' 点开错误的文 ...

  10. mac 下安装python pil

    mac:sudo brew install freetype sudo pip install pillow ubuntu: sudo apt-get install libfreetype6-dev ...