黏包:


同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

只有TCP协议中才会产生黏包,UDP协议中不会有黏包(udp协议中数据会直接丢失,俗称丢包)

#面试
#首先只有在TCP协议中才有黏包现象,是因为TCP协议是面向流的协议,
#在发送的数据传输的过程中还有缓存机制来避免数据丢失
#因此在连续发送小数据的时候,以及接收大小不符的时候,都容易产生黏包现象
#本质是不知道客户端发送的数据长度

面试中解释黏包

#连续send两个小数据
#两个recv,第一个recv特别小 #面试
#首先只有在TCP协议中才有黏包现象,是因为TCP协议是面向流的协议,
#在发送的数据传输的过程中还有缓存机制来避免数据丢失
#因此在连续发送小数据的时候,以及接收大小不符的时候,都容易产生黏包现象
#本质是不知道客户端发送的数据长度 #服务端发送数据,经过系统内存,客户端接收,在TCP协议中,如果服务端发送数据过大,或者客户端接收数据过少,超过的数据就会滞纳在内存中
#从而产生了黏包。但是在UDP过程中则直接丢包了
#本质问题,不知道客户端发送的数据长度 #在TCP中 如果发送端发送了3次,3次总数据大小为10,接收端如果只接收一次,接收大小>10,那么这3次会合起来一起被接收
#以为TCP中的优化算法,当几次数据又小又连续,会合并发送(需要又小又连续)
#因为一次性接收,只需要经历一次网络延时,提高接收效率,
#并且只要接收方足够大,它会一次性把这三次数据都给合并接收,即使此处有3次接收
#比如此处 ret1和ret2接收都为空,连续的小数据被ret接收了
# ret = conn.recv(1024)
# print(ret)
# ret1 = conn.recv(1024) #此时ret1和ret2接收到的空,是因为数据都被ret接收了,本来是阻塞的,等待消息传入。
# ret2 = conn.recv(1024) #但是此时客户端close了,会默认发送空消息过来(根据windows版本不同,也可能直接报错),刚好被ret1和ret2接收了。
# print(ret1,ret2) #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
#注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行)

黏包问题总结

#UDP不会黏包,但是udp会丢包
#tcp会黏包,但是不会丢包 # 黏包成因:
# TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
# 收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,
# 使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
# 这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。 # 当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
# MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。
# 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,
# 这样会产生很多数据包碎片,增加丢包率,降低网络速度。 # 1.是因为tcp的拆包机制,使得消息没有边界
# 2.当发送端缓冲区的长度大于网卡的MTU时,产生了数据包碎片

黏包成因-蛮看看就好

黏包的发现:


socket模块中,TCP协议的黏包问题发现:(用到了subprocess模块)

import socket
import subprocess
ip_port = ('127.0.0.1',8898)
buffer_size = 10240 sk = socket.socket()
sk.bind(ip_port)
sk.listen() conn,addr = sk.accept()
while True:
cmd = input('>>>>> ')
if cmd == 'q':
break
conn.send(cmd.encode('utf-8'))
ret = conn.recv(buffer_size).decode('utf-8')
# ret2 = conn.recv(buffer_size).decode('utf-8')
print(ret)
# print(ret2) conn.close()
sk.close()

serve端-黏包

import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8898)) while True:
cmd = sk.recv(1024).decode('gbk')
ret = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
std_out = 'stdout: '+(ret.stdout.read()).decode('gbk')
std_err = 'stderr: '+(ret.stderr.read()).decode('gbk')
print(std_out)
print(std_err)
sk.send(std_out.encode('utf-8'))
sk.send(std_err.encode('utf-8'))
sk.close()

client端-黏包

黏包自己的小分析:

#服务端发送数据,经过系统内存,客户端接收,在TCP协议中,如果服务端发送数据过大,或者客户端接收数据过少,超过的数据就会滞纳在内存中(内核态用户态)
#从而产生了黏包。但是在UDP过程中则直接丢包了
#本质问题,不知道客户端发送的数据长度 import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen() conn,addr = sk.accept() #在TCP中 如果发送端发送了3次,3次总数据大小为10,接收端如果只接收一次,接收大小>10,那么这3次会合起来一起被接收
#以为TCP中的优化算法,当几次数据又小又连续,会合并发送(需要又小又连续)
#因为一次性接收,只需要经历一次网络延时,提高接收效率,
#并且只要接收方足够大,它会一次性把这三次数据都给合并接收,即使此处有3次接收
#比如此处 ret1和ret2接收都为空,连续的小数据被ret接收了
ret = conn.recv(1024)
print(ret)
ret1 = conn.recv(1024) #此时ret1和ret2接收到的空,是因为数据都被ret接收了,本来是阻塞的,等待消息传入。
ret2 = conn.recv(1024) #但是此时客户端close了,会默认发送空消息过来(根据windows版本不同,也可能直接报错),刚好被ret1和ret2接收了。
print(ret1,ret2) conn.close()
sk.close() #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
#注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行)

server-黏包问题分析

import socket,time
sk = socket.socket()
sk.connect(('127.0.0.1',8898)) sk.send(b'hello')
time.sleep(0.01) #此时 hello会单独发送,下面两句才一起发送
sk.send(b'he22')
sk.send(b'33llo') #多个send小的数据连在一起,会发生黏包现象,是TCP内部优化算法造成的
#注意这几个send必须又短又连续且发送间隔非常短暂(0.01秒都不行) # import time
# time.sleep(5)
sk.close()

client-黏包问题分析

黏包的解决


有两种方式:

1.比较low的方法:及优劣点分析

import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen()
conn,addr = sk.accept() while True:
cmd = input('>>>>> ')
if cmd == 'q':
break
conn.send(cmd.encode('gbk'))
len_num = conn.recv(1024).decode('utf-8')
conn.send(b'ok')
len_num = int(len_num)
ret = conn.recv(len_num).decode('gbk')
print(ret) conn.close()
sk.close() #好处:确定了我到底要接收多大的数据
#要在文件中配置了一个配置项:就是每一次recv的大小 buffer = 4096
#当我们要发送大数据的时候,要明确的告诉接收方要发送多大的数据,以便接收方能够准确的接收到所有数据
#多用在文件传输的过程中
#大文件的传输 一定是按照字节读 每一次读固定的字节
#传输的过程中 发送端一边读一边传,接收端一边收一边写
#send这个大文件之前,35672字节 send(4096)-....-send(4096)---->0
#resv这个大文件之前,35672字节 send(4096)-....-send(4096)---->0 #不好的地方:
#多了一次交互
#send sendto 在超过一定范围的时候,都会报错
#程序的内存管理

server端-方法的好处和坏处

import socket
import subprocess sk = socket.socket()
sk.connect(('127.0.0.1',8898)) while True:
cmd = sk.recv(1024).decode('gbk')
if cmd == 'q':
break
ret = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
std_out = ret.stdout.read() #注意read本身就是bytes类型。
std_err = ret.stderr.read() #此处read完后,类似从list中pop,管道中的值就取出来没了,类似队列
len_num = str(len(std_out) + len(std_err))
sk.send(len_num.encode('utf-8'))
sk.recv(1024) sk.send(std_out)
sk.send(std_err)
sk.close()

client端

2.用struct模块解决黏包问题,及struct模块初识

import socket
import subprocess
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',8898))
sk.listen()
conn,addr = sk.accept() while True:
cmd = input('>>>>> ')
if cmd == 'q':
break
conn.send(cmd.encode('gbk'))
len_byte = conn.recv(4) #通过struct模块,确定知道传过来的int为4个字节,这里只收取4个字节,保证不黏包
len_num = struct.unpack('i',len_byte)[0] #获取到传过来的数字,也就是接下来要接收数据的大小
ret = conn.recv(len_num).decode('gbk') #通过len,知道接下来要接收多大的数据,从而不管发送端发了几次,这边可以一次性接收
print(ret) conn.close()
sk.close()

server端-struct

import socket
import subprocess
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8898)) while True:
cmd = sk.recv(1024).decode('gbk')
if cmd == 'q':
break
ret = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
std_out = ret.stdout.read() #注意read本身就是bytes类型。
std_err = ret.stderr.read() #此处read完后,类似从list中pop,管道中的值就取出来没了,类似队列
len_num = len(std_out) + len(std_err)
len_byt = struct.pack('i',len_num)
sk.send(len_byt)
sk.send(std_out)
sk.send(std_err)
sk.close()

client端-struct

import struct
#模块可以把一个数字 转换为 一个bytes类型 (二进制) 然后再转回数字类型
#以int为例 int转换后的bytes类型长度为 4字节,以元组的方式返回 (int,)
#除了int 还有 float double long 等等
#其中,int将在以后很长的时间里都只使用int num_byte = struct.pack('i',4096)
print(num_byte) #b'\x00\x10\x00\x00'
print(len(num_byte)) #长度为4 num_tup = struct.unpack('i',num_byte)
print(num_tup) #(4096,)
print(type(num_tup[0])) #<class 'int'> #通过struct模块,我们就将int转换为长度为4的bytes,就可以通过先recv(4),获取到一个int类型
# 这个int类型表示接下来要接收的数据大小
#和黏包问题解决中比较low的方法比较,省去了一个交互步骤,不用我收到len大小后,再send(b'ok')来避免 len 被黏包

struct模块初识

练习:


大文件的上传和下载

import os
import json
import socket
import struct
bufer = 1024
ip_port = ('127.0.0.1',8898)
sk = socket.socket()
sk.bind(ip_port)
sk.listen()
conn,addr = sk.accept() len_bytes = conn.recv(4) #!!!!!这里怎么可以出错,struct传过来的int 就是4字节
len_head = struct.unpack('i',len_bytes)[0]
head_json = conn.recv(len_head).decode('utf-8')
head = json.loads(head_json)
filesize = head['filesize']
with open(head['file_name'],'wb') as f:
while filesize:
print(filesize)
if filesize >= bufer:
file_write = conn.recv(bufer)
f.write(file_write)
filesize -= bufer
else:
file_write = conn.recv(filesize)
f.write(file_write)
break
conn.close()
sk.close()

server

import os
import json
import socket
import struct
bufer = 1024
ip_port = ('127.0.0.1',8898)
sk = socket.socket()
sk.connect(ip_port) head = {'file_path':r'D:\python-全栈九期\day33',
'file_name':'04 python fullstack s9day33 ftp作业分析.mp4',
'filesize':None}
file_path = os.path.join(head['file_path'],head['file_name']) #join的用法写错了 !!!
filesize = os.path.getsize(file_path)
head['filesize'] = filesize
head_json = json.dumps(head)
head_bytes = head_json.encode('utf-8') #开始用struct转换报头长度
len_head = len(head_bytes)
head_pack = struct.pack('i',len_head)
sk.send(head_pack)
sk.send(head_bytes)
with open(file_path,'rb') as f: #!!!这里rb写成了wb
while filesize:
print(filesize)
if filesize >= bufer:
content = f.read(bufer)
sk.send(content)
filesize -= bufer
else:
content = f.read(filesize)
sk.send(content)
break
sk.close()

client

黏包-黏包的成因、解决方式及struct模块初识、文件的上传和下载的更多相关文章

  1. python 解决粘包问题的例子(ftp文件的上传与下载)简单版本

    服务端 ! /user/bin/env python3 -- coding:utf_8 -- """ Author:Markli # 2019/9/9,16:41 &qu ...

  2. Python_编写UDP通信编解码类、文件的上传、远程执行命令、黏包

    1.UDP通信编解码类 (1) 类 # ------------------UDP通信解码编码类------------------------ from socket import * class ...

  3. Java实现文件的上传下载(含源代码和jar包)

    1.需要使用的jar包 链接:https://pan.baidu.com/s/1IaxQRSwfzxDpe4w4JiaEKw 提取码:xwtz 2.如果想实现文件的下载,需要创建一张表,表的结构为 i ...

  4. nexus搭建maven私服及私服jar包上传和下载

    nexus搭建maven私服及私服jar包上传和下载 标签: nexus管理maven库snapshot 2017-06-28 13:02 844人阅读 评论(0) 收藏 举报 分类: Maven(1 ...

  5. springmvc和servlet在上传和下载文件(保持文件夹和存储数据库Blob两种方式)

    参与该项目的文件上传和下载.一旦struts2下完成,今天springmvc再来一遍.发现springmvc特别好包,基本上不具备的几行代码即可完成,下面的代码贴: FileUpAndDown.jsp ...

  6. Linux 如何使用压缩与解压缩的方式将Windows下的zip压缩包上传到Linux系统

    当我们无法使用xftp方式上传文件到Linux系统时,我们可以使用在Windows下压缩文件夹,然后到Linux系统下解压缩的方式,完成整个上传工作. 第一步:在Windows系统下,将整个文件夹压缩 ...

  7. mac下svn问题——“.a”(静态库)文件无法上传解决

    mac下svn问题——“.a”(静态库)文件无法上传解决    “.a”(静态库)文件无法上传(svn工具:Versions)          网上查询了一下,说是Xcode自带的svn和Versi ...

  8. ASP.NET Core文件上传与下载(多种上传方式)

    前言 前段时间项目上线,实在太忙,最近终于开始可以研究研究ASP.NET Core了. 打算写个系列,但是还没想好目录,今天先来一篇,后面在整理吧. ASP.NET Core 2.0 发展到现在,已经 ...

  9. java 下载文件的两种方式和java文件的上传

    一:以网络的方式下载文件 try { // path是指欲下载的文件的路径. File file = new File(path); // 以流的形式下载文件. InputStream fis = n ...

随机推荐

  1. 2018年工作终总结&规划

    收获满满的2018 收获总结: 1. 换了家有地区牌照的公司,薪酬涨了那么一点点,但是工作压力.强度下降不少,这样有更多时间来学习新知识. 2. 跟同事一起接了维护后台管理系统的私活,每个月多了一点点 ...

  2. 异常分类VS垃圾分类

    异常分类VS垃圾分类 容易快速判断出是什么业务异常,容易对不同的异常进行不同的处理,容易很快找到对应的解决方法

  3. Eclipse 在启动发生错误异常:An internal error occurred during: "Initializing Java Tooling". java.lang.NullPointerException的解决办法

    异常现象: 打开Eclipse,出现 An internal error occurred during: "Initializing Java Tooling". java.la ...

  4. 20155228 2017-11-19 实现mypwd(选做,加分)

    20155228 2017-11-19 实现mypwd(选做,加分) 题目和要求 学习pwd命令 研究pwd实现需要的系统调用(man -k; grep),写出伪代码 实现mypwd 测试mypwd ...

  5. Hdu2039 三角形

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2039 三角形 Time Limit: 2000/1000 MS (Java/Others)    Me ...

  6. CSS 边框样式

    CSS 边框样式 直线边框样式 <html> <body> <!-- border: 1px 边框像素为1.solid red 边框样式以及边框颜色 --> < ...

  7. openvpn 初步使用

    服务端:Centos 7.2 openvpn 2.4.3 客户端:Windows 10 安装包 openvpn的官网在国内访问不了,服务端通过yum安装,客户端在第三方网站下载的 一般的国内源应该都包 ...

  8. [C++ Primer Plus] 第3章、处理数据(二)课后习题

    1 . 编写一个小程序,要求用户使用一个整数输出自己的身高(单位为厘米),然后将身高转换为米和厘米.该程序使用下划线字符来指示输入位置.另外,使用一个 const 符号常量来表示转换因子. #incl ...

  9. zabbix/自动发现规则

      对于zabbix 我们并不陌生 他是开源的监控系统,现在的一部分企业都在用zabbix,今天给大家分享的是企业级监控zabbix的自动发现规则,有了它我们自定义健康的时候根据宏值可以让他自动发现对 ...

  10. 王之泰201771010131《面向对象程序设计(java)》第七周学习总结

    王之泰201771010131<面向对象程序设计(java)>第七周学习总结 第一部分:理论知识学习部分 第五章 第五章内容深度学习: 继承:如果两个类存在继承关系,则子类会自动继承父类的 ...