29、粘包现象(struct模块)

时间:2023-01-24 19:22:30

昨天我们所做的套接字是有漏洞的,它会出现粘包现象,没有发现这个问题的我们今天会进行演示。今天也会稍微讲解一下基于udp的套接字。

本篇导航:

一、基于udp的套接字

udp是无链接的,先启动哪一端都不会报错

udp服务端:

ss = socket()   #创建一个服务器的套接字
ss.bind() #绑定服务器套接字
while True : #服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close() # 关闭服务器套接字

udp客户端:

cs = socket()   # 创建客户套接字
while True : # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字

1、udp套接字简单实例

服务端:

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind(('127.0.0.1',8080)) while True:
msg,addr=udp_ss.recvfrom(1024)
print(msg,addr)
udp_ss.sendto(msg.upper(),addr)

客户端:

from socket import *

udp_cs=socket(AF_INET,SOCK_DGRAM)

while True:
msg=input('>>: ').strip()
if not msg:continue
udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
msg,addr=udp_cs.recvfrom(1024)
print(msg.decode('utf-8'),addr)

2、模拟聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

服务端:

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind(('127.0.0.1',8081)) while True:
msg,addr=udp_ss.recvfrom(1024)
print('来自[%s]的一条消息:%s' %(addr,msg.decode('utf-8')))
msg_b=input('回复消息: ').strip()
udp_ss.sendto(msg_b.encode('utf-8'),addr)

客户端1:

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
msg = input('请输入消息,回车发送: ').strip()
if msg == 'quit' : break
if not msg : continue
udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081)) back_msg,addr = udp_cs.recvfrom(1024)
print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8'))) udp_cs.close()

客户端2:

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
msg = input('请输入消息,回车发送: ').strip()
if msg == 'quit' : break
if not msg : continue
udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081)) back_msg,addr = udp_cs.recvfrom(1024)
print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8'))) udp_cs.close()

客户端3:

... ...

因为不同的客户端是向同一个服务端发送信息所以客户端的代码都相同,如果兴趣的可以用几台电脑来测试(电脑需要联网)


二、粘包现象

先做粘包现象:

服务端:

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,client_addr=phone.accept() data1=conn.recv(1024)
print('data1: ',data1)
data2=conn.recv(1024)
print('data2:',data2)

客户端:

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8'))
phone.send('world'.encode('utf-8'))

我们再将上个随笔里的ssh例子拿出来(先执行 ipconfig /all 再执行 dir 看结果)

客户端:

from socket import *
import subprocess
cs=socket(AF_INET,SOCK_STREAM)
cs.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
cs.bind(('127.0.0.1',8082))
cs.listen(5) print('starting...')
while True:
conn,addr=cs.accept()
print('-------->',conn,addr) while True:
try:
cmd=conn.recv(1024)
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout=res.stdout.read()
stderr=res.stderr.read() #发送命令的结果
conn.send(stdout+stderr)
except Exception:
break
conn.close() #挂电话
cs.close() #关机

服务端:

from socket import *
ss=socket(AF_INET,SOCK_STREAM) #买手机
ss.connect(('127.0.0.1',8082)) #绑定手机卡 #发,收消息
while True:
cmd=input('>>: ').strip()
if not cmd:continue
ss.send(cmd.encode('utf-8'))
cmd_res=ss.recv(1024)
print(cmd_res.decode('gbk'))
ss.close()

注意:

subprocess模块的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码


三、粘包

注意:只有TCP有粘包现象,UDP永远不会粘包,首先需要掌握一个socket收发消息的原理

29、粘包现象(struct模块)

应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

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

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

两种情况会粘包:

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

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

拆包的发生情况:

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。


四、解决粘包方法

粘包现象中第一个现象解决:

解决一:(需要知道每次发过来的数据大小 不现实)

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,client_addr=phone.accept() data1=conn.recv(10)
print('data1: ',data1)
data2=conn.recv(4)
print('data2:',data2)

服务端

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) phone.send('helloworld'.encode('utf-8'))
phone.send('egon'.encode('utf-8'))

客户端

解决二:

from socket import *
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
conn,client_addr=phone.accept() data1=conn.recv(1024)
print('data1: ',data1)
data2=conn.recv(1024)
print('data2:',data2)

服务端

from socket import *
import time
phone=socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8'))
time.sleep(5)
phone.send('world'.encode('utf-8'))

客户端

ssh例子问题解决:

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

from socket import *
import subprocess
import struct
ss=socket(AF_INET,SOCK_STREAM)
ss.bind(('127.0.0.1',8082))
ss.listen(5) print('starting...')
while True:
conn,addr=ss.accept()
print('-------->',conn,addr) while True:
try:
cmd=conn.recv(1024)
res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout=res.stdout.read()
stderr=res.stderr.read() #先发报头(转成固定长度的bytes类型)
header = struct.pack('i',len(stdout)+len(stderr))
conn.send(header)
#再发送命令的结果
conn.send(stdout)
conn.send(stderr)
except Exception:
break
conn.close()
ss.close()

服务端

from socket import *
import struct
cs=socket(AF_INET,SOCK_STREAM)
cs.connect(('127.0.0.1',8082)) while True:
cmd=input('>>: ').strip()
if not cmd:continue cs.send(cmd.encode('utf-8'))
#先收报头
header_struct=cs.recv(4)
unpack_res = struct.unpack('i', header_struct)
total_size=unpack_res[0] #再收数据
recv_size=0 #10241=10240+1
total_data=b''
while recv_size < total_size:
recv_data=cs.recv(1024)
recv_size+=len(recv_data)
total_data+=recv_data
print(total_data.decode('gbk'))
cs.close()

客户端


五、struct模块(了解)

该模块可以把一个类型,如数字,转成固定长度的bytes

struct.pack('i',11111111)
#struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

29、粘包现象(struct模块)

struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型)。它的函数原型为:struct.unpack(fmt, string)。

struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组

29、粘包现象(struct模块)的更多相关文章

  1. python笔记8 socket&lpar;TCP&rpar; subprocess模块 粘包现象 struct模块 基于UDP的套接字协议

    socket 基于tcp协议socket 服务端 import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买 ...

  2. 8&period;7 day28 网络编程 socket套接字 半连接池 通信循环 粘包问题 struct模块

    前置知识:不同计算机程序之间的数据传输 应用程序中的数据都是从程序所在计算机内存中读取的. 内存中的数据是从硬盘读取或者网络传输过来的 不同计算机程序数据传输需要经过七层协议物理连接介质才能到达目标程 ...

  3. python网络编程-socket套接字通信循环-粘包问题-struct模块-02

    前置知识 不同计算机程序之间数据的传输 应用程序中的数据都是从程序所在计算机内存中读取的. 内存中的数据是从硬盘读取或者网络传输过来的 不同计算机程序数据传输需要经过七层协议物理连接介质才能到达目标程 ...

  4. &lpar;day27&rpar;subprocess模块&plus;粘包问题&plus;struct模块&plus; UDP协议&plus;socketserver

    目录 昨日回顾 软件开发架构 C/S架构 B/S架构 网络编程 互联网协议 socket套接字 今日内容 一.subprocess模块 二.粘包问题 三.struct模块 四.UDP 五.QQ聊天室 ...

  5. 2、粘包现象(struct模块)

    昨天我们所做的套接字是有漏洞的,它会出现粘包现象,没有发现这个问题的我们今天会进行演示.今天也会稍微讲解一下基于udp的套接字. 一.基于udp的套接字 udp是无链接的,先启动哪一端都不会报错 ud ...

  6. Python网络编程(2)-粘包现象及socketserver模块实现TCP并发

    1. 基于Tcp的远程调用命令实现 很多人应该都使用过Xshell工具,这是一个远程连接工具,通过上面的知识,就可以模拟出Xshell远程连接服务器并调用命令的功能. Tcp服务端代码如下: impo ...

  7. socket模块粘包现象理解以及解决思路

    粘包现象: 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的 ...

  8. Python之网路编程之粘包现象

    一.什么是粘包 须知:只有TCP有粘包现象,UDP永远不会粘包 粘包不一定会发生 如果发生了:1.可能是在客户端已经粘了 2.客户端没有粘,可能是在服务端粘了 首先需要掌握一个socket收发消息的原 ...

  9. python3全栈开发-什么是粘包、粘包现象、如何解决粘包

    一.粘包现象 让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig) 注意注意注意: res=subprocess.Popen(cmd.decode( ...

随机推荐

  1. A trip through the Graphics Pipeline 2011&lowbar;06&lowbar;&lpar;Triangle&rpar; rasterization and setup

    Welcome back. This time we’re actually gonna see triangles being rasterized – finally! But before we ...

  2. WebSocket 服务器4

    Java Websocket实例 Websocket   2015-04-11 14:11:54 发布 您的评价:       4.4   收藏     6收藏 介绍 现很多网站为了实现即时通讯,所用 ...

  3. Android Volley源码分析

    今天来顺手分析一下谷歌的volley http通信框架.首先从github上 下载volley的源码, 然后新建你自己的工程以后 选择import module 然后选择volley. 最后还需要更改 ...

  4. Chrome 控制台不完全指南(转)

    Chrome的开发者工具已经强大到没朋友的地步了,特别是其功能丰富界面友好的console,使用得当可以有如下功效: 更高「逼格」更快「开发调试」更强「进阶级的Frontender」 Bug无处遁形「 ...

  5. 第2章 熟悉Eclipse开发工具---- System&period;out&period;println&lpar;&quot&semi;sum&equals;&quot&semi;&plus;&lpar;a&plus;b&rpar;&rpar;&semi;

  6. IE提示&OpenCurlyDoubleQuote;Internet Explorer已限制此网页运行脚本或ActiveX控件”的解决办法

    在页面html开始标签和head开始标签中间新增一行,添加以下代码: <!-- saved from url=(0014)about:internet --> 或者 直接设置IE浏览器 工 ...

  7. Sprite&lpar;雪碧图&rpar;的应用

    雪碧图是根据CSS sprite音译过来的,是将很多很多的小图标放在一张图片上. 使用雪碧图的目的:有时为了美观,我们会使用一张图片来代替一些小图标,但是一个网页可能有很多的小图标,浏览器在显示页面的 ...

  8. RestSharp发送请求得到Json数据

    NUGET安装:RestSharp code: public string Post(string url, string content) { string contentType = " ...

  9. 学习DRF之前须知知识点

    在学习DRF之前~我们要先复习一些知识点~~ FBV和CBV 学习Django的时候~我们已经学习过了CBV以及FBV~~我们来复习一下~~ 什么是FBV和CBV呢~~ FBV 基于函数的视图 CBV ...

  10. 前端项目,引入PingFang SC字体

    在仿苹果官网"垃圾桶"时, 设计出的UI使用PingFang SC 字体,在网上查了很久,特记录.如果你有更好的方法,欢迎评论留言~ 实现原理,使用@font-face将字体下载在 ...