网络通信与并发编程(三)粘包现象解决方案、socketserver实现并发

news2024/10/19 4:27:56

粘包现象解决方案、socketserver实现并发

文章目录

  • 粘包现象解决方案、socketserver实现并发
  • 一、粘包现象解决方案
    • 1.发送数据大小
    • 2.发送数据信息
  • 二、socketserver实现并发
    • 1.tcp版的socketserver并发
    • 2.udp版的socketserver并发

一、粘包现象解决方案

1.发送数据大小

有了上一篇文章的分析,我们知道tcp协议之所以会出现粘包现象,是因为无法得知每次传输的字节数。如果我们人为的给传输的数据添加一个报头用来表示接收内容的字节数就可以解决这个问题了。

#服务端
from socket import *
import subprocess
import struct

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,client_addr=server.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd) == 0:break
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE
                             )

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #1、制作报头(固定长度)
            total_size=len(stdout) + len(stderr)
            #struct模块可以将一个数据类型转为固定长度的bytes
            header=struct.pack('i',total_size)
            #2、发送固定长度的报头
            conn.send(header)
            #3、发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()
#客户端
from socket import *
import struct

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1、接收固定长度的报头(bytes)
    header=client.recv(4)
    #从报头中解析接收内容的字节数
    total_size=struct.unpack('i',header)[0]
    #2、接收真实的数据
    #recv_size表示已接收字节数,total_size表示总的字节数
    recv_size=0
    res=b''
    while recv_size < total_size :
        data=client.recv(1024)
        res+=data
        recv_size+=len(data)

    print(res.decode('gbk'))

接收结果:
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 152 K
(接收内容过长,中间部分略去)
tasklist.exe 16076 Console 1 9,520 K
WmiPrvSE.exe 15416 Services 0 10,212 K

2.发送数据信息

在一般的文件传输中,我们除了可以看到文件的大小,还可以看见文件名称,创建日期当信息,为了将这些信息加入到报头中,我们可以将上面的代码进一步优化为如下形式:

#服务端
from socket import *
import subprocess
import struct
import json,time

server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,client_addr=server.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            if len(cmd) == 0:break
            # 运行系统命令
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE
                             )

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            #将报头信息制作成字典
            header_dic={
                'filename':'tasklist',
                'total_size':len(stdout) + len(stderr),
                'create_time':time.strftime('%Y-%m-%d %H')
            }
            #通过json格式将报头字典转为bytes
            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')
			
			#由于报头字典大小超出struct的限制,所以此处先发送4个字节标识报头字典的大小
            #1、发送报头字典大小
            conn.send(struct.pack('i',len(header_bytes)))
            #2、发送报头字典
            conn.send(header_bytes)
            #3、发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()
#客户端
from socket import *
import struct
import json

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1、收4个字节,这4个字节表示报头长度
    header_len=struct.unpack('i',client.recv(4))[0]
    #2、再接收报头
    header_bytes=client.recv(header_len)
    #通过json解析出报头字典
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']
    #3、接收真实的数据
    recv_size=0
    res=b''
    while recv_size < total_size :
        data=client.recv(1024)
        res+=data
        recv_size+=len(data)

    print(res.decode('gbk'))

可以看到结果中接收到了报头信息:
{‘filename’: ‘tasklist’, ‘total_size’: 17942, ‘create_time’: ‘2024-10-18 18’}
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 152 K
(接收内容过长,中间部分略去)
tasklist.exe 16076 Console 1 9,520 K
WmiPrvSE.exe 15416 Services 0 10,212 K

二、socketserver实现并发

1.tcp版的socketserver并发

#客户端
import socket
#表示当前开启的客户端序号
#并发通信时,可以开启斗个客户端与服务端通信
num=33
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) 

while True:
    msg = 'client%s'%num
    if len(msg) == 0:continue
    phone.send(msg.encode('utf-8'))
    data=phone.recv(1024)
    print(data)

phone.close()
#服务端
import socketserver
#MyHandler为自定义的通信函数
class MyHandler(socketserver.BaseRequestHandler):
	#MyHandler继承了abc类,必须复写handle函数
    def handle(self):
        #通信循环
        while True:
            try:
            	#self.request中存放这通信连接
            	#self.client_address中存放着客户端的ip和端口号
				#self.server中存放着套接字对象
                data=self.request.recv(1024)
                if len(data) == 0:break
                self.request.send(data.upper())
            except ConnectionResetError:
                break

if __name__ == '__main__':
	#创建socketserver对象
    s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyHandler,bind_and_activate=True)
    #serve_forever函数在有客户端发送请求时创建一个线程
    #这个线程会根据建立的通信链接与客户端通信(创建MyHandler对象并调用handle方法)
    s.serve_forever()

2.udp版的socketserver并发

#客户端
import socket
num=33
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg='client%s'%num
    client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
    data,server_addr=client.recvfrom(1024)
    print(data)

client.close()
#服务端
import socketserver
class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # 通信循环
        #self.request[1]存放着套接字对象
        #self.client_address存放着客户端的信息
        #self.request[0]存放着客户端发送的信息
        data = self.request[0]
        self.request[1].sendto(data.upper(), self.client_address)


if __name__ == '__main__':
    s = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyHandler)
    #当客户端发送信息以后,serve_forever函数会创建一个线程与客户端进行通信
    s.serve_forever()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2218226.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2-126基于matlab希尔伯特黄变换(HHT)的图像三维重建

基于matlab希尔伯特黄变换&#xff08;HHT&#xff09;的图像三维重建&#xff0c;利用希尔伯特黄变换&#xff08;HHT&#xff09;的条纹图相位信息提取算法&#xff0c;对输入图片的变形条纹图相位信息进行提取&#xff0c;实现三维重建。程序已调通&#xff0c;可直接运行。…

Axure重要元件三——中继器时间排序

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器数据时间排序 课程内容&#xff1a;数据的升序、降序、重置排序 应用场景&#xff1a;表单数据的排序 案例展示&#xff1a; 步骤一&#xff…

【数据分享】1901-2023年我国省市县三级逐月最低气温(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月最低气温栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff0c;该数据来源于国家青藏高原科学数据中心&#xff0c;很多小伙伴拿到数据后反馈栅格数据不太方便使用&#xff0c;问我们能不能把数据处理为更方便使用的Sh…

0基础学java之Day09(下午完整版)

六、数组 概念&#xff1a; 1.数组是引用数据类型 2.数组中的数据叫做元素 3.元素都有标号叫做索引/下标 4.下标从0开始 5.数组一旦初始化成功&#xff0c;长度不可变&#xff08;意味着数组没有添加和删除&#xff09; 6.数组中的元素在内存中是挨在一起的 声明&#xff1a; 数…

CTF(四)

导言&#xff1a; 本文主要讲述在CTF竞赛中&#xff0c;web类题目file_include。 靶场链接&#xff1a;攻防世界 (xctf.org.cn) 一&#xff0c;观察页面。 可以看到一段php代码。从则段代码中我们可以知道&#xff1a; 1&#xff0c;使用include引入check.php文件&#xff…

Excel制作工资表

需要用到的函数 函数要求如下&#xff1a; IFERROR 功能&#xff1a;处理公式中的错误&#xff0c;避免显示错误值。当公式产生错误时&#xff0c;使用自定义的值或表达式代替错误信息。 IFERROR(值, 错误值)SUM 功能&#xff1a;求和&#xff0c;计算一系列数字的总和。 语…

SSD |(七)FTL详解(中)

文章目录 &#x1f4da;垃圾回收&#x1f407;垃圾回收原理&#x1f407;写放大&#x1f407;垃圾回收实现&#x1f407;垃圾回收时机 &#x1f4da;解除映射关系&#x1f4da;磨损均衡 &#x1f4da;垃圾回收 &#x1f407;垃圾回收原理 ✋设定一个迷你SSD空间&#xff1a; 假…

Windows 和 Ubuntu通讯的网络设置

如果你是一个嵌入式工程师&#xff0c;因为工作需要&#xff0c;在linux下进行开发&#xff0c;一定会遇见配网问题。这篇文章解决Windows 和虚拟机Ubuntu通讯的网络设置的问题。 Windows的网络配置&#xff1a; 在配置网络前&#xff0c;先了解一下windows和ubuntu的网络构成…

CTFHUB技能树之SQL——过滤空格

开启靶场&#xff0c;打开链接&#xff1a; 既然是过滤空格&#xff0c;绕过空格的方法&#xff1a; 用/**/或%0a替代空格 &#xff08;1&#xff09;判断注入点 1 and 11# 会显示hacker 1/**/and/**/11# 有回显 1/**/and/**/12# 无回显&#xff0c;说明是整数型注入 &#…

嵌入式C++中内存分配基本实现方法

大家好,今天主要给大家分享一下,如何使用计算机中的内存空间进行分配,观察具体现象。 第一:C语言动态空间分配方式 第二:C++中动态内存分配方法 new 可以自动计算数据类型的大小 与 类型的转换 malloc 只能手动进行。 2.new 可以在分配空间的时候初始化 malloc 不行。 第三…

python采集汽车之家数据

python采集汽车之家数据 一、寻找数据接口二、发送请求获取响应三、解析数据四、完整代码一、寻找数据接口 如下图所示,在汽车之家首页点击报价图标: 在下图中选择价位,例如选择15-20万: 打开浏览器开发者工具,刷新页面,找到数据接口。接下来,通过翻页寻找接口url的变…

uni-app uni.setTabBarBadge 不生效

‘text’属性&#xff0c;类型必须是字符串&#xff0c;而接口返回的是数值&#xff0c;没有注意到&#xff0c;所以怎么都不生效&#xff0c;也不会有报错&#xff01;

渗透测试导论

渗透测试的定义和目的 渗透测试&#xff08;Penetration Testing&#xff09;是一项安全演习&#xff0c;网络安全专家尝试查找和利用计算机系统中的漏洞。 模拟攻击的目的是识别攻击者可以利用的系统防御中的薄弱环节。 这就像银行雇用别人假装盗匪&#xff0c;让他们试图闯…

day-68 使二进制数组全部等于 1 的最少操作次数 I

思路 关键&#xff1a;对同一个i至多操作一次&#xff0c;就可以做到最少的操作次数&#xff0c;且操作的顺序不重要&#xff0c;那么即可从左到右操作&#xff0c;结果一样的&#xff0c;遇到1不操作&#xff0c;遇到0则操作&#xff0c;用一个变量记录操作次数&#xff0c;最…

Go程序的一生——Go如何跑起来的?

​​​​​​​ 引入 我们从一个 Hello World 的例子开始&#xff1a; package mainimport "fmt"func main() {fmt.Println("hello world") }当我用我那价值 1800 元的 cherry 键盘潇洒地敲完上面的 hello world 代码时&#xff0c;保存在硬盘上的 hell…

青少年编程能力等级测评CPA C++一级试卷(1)

青少年编程能力等级测评CPA C一级试卷&#xff08;1&#xff09; 一、单项选择题&#xff08;共20题&#xff0c;每题3.5分&#xff0c;共70分&#xff09; CP1_1_1&#xff0e;在C中&#xff0c;下列变量名正确的是&#xff08; &#xff09;。 A&#xff0e;$123 B&#…

无人机之定高算法篇

一、无人机高度测量原理 无人机的高度测量通常依赖于多种传感器&#xff0c;其中主要包括&#xff1a; 气压计&#xff1a;通过测量大气压力的变化来确定高度。在大气中&#xff0c;随着高度的增加&#xff0c;气压会逐渐降低。无人机搭载的气压计会感知大气的压力变化&#…

当我们修复测试用例时,到底是修复的什么?

当我们运行了测试用例&#xff0c;发现其中一些测试用例未能通过。ok&#xff0c;这下要修复测试用例了&#xff01;但是&#xff0c;到底需要修复哪些内容呢&#xff1f; 其实从用例被加载到最终执行的过程中&#xff0c;有很多因素可能导致测试失败&#xff1a; 在测试构建过…

C语言中的文件操作:从基础到深入底层原理

文件操作是几乎所有应用程序的重要组成部分&#xff0c;特别是在系统级编程中。C语言因其高效、灵活以及接近硬件的特点&#xff0c;成为了文件操作的理想选择。本文将全面深入地探讨C语言中的文件操作&#xff0c;从文件系统的概念到具体的文件操作函数&#xff0c;再到底层的…

生成器和迭代器

迭代器 定义 迭代器是一个实现了选代协议的对象&#xff0c;它可以让我们遍历一个容器中的所有元素&#xff0c;而不需要知道容器的内部结构&#xff0c;迭代器可以被用于遍历列表、元组、字典、集合等容器类型。 工作原理 __iter__():方法返回迭代器对象本身&#xff0c;有…