跨平台PHP调试器设计及使用方法——通信
首先引用《跨平台PHP调试器设计及使用方法——探索和设计》中的结构图(转载请指明出于breaksoftware的csdn博客)
本文要介绍的是我们逻辑和pydbgp通信的实现(图中红框内内容)。
设计通信之前,我需要先设计一种通信协议,其实就是一个数据打包和解包的协议。因为我们的数据非常简单,所以只是用“”长度+数据“”的结构。我们规定一个包的前8个字节表示数据的总长度(包括这个8个字节的长度),然后跟着的就是数据。
class socket_protocol:def __init__(self):self.response = ""passdef pack_request(self, request):request_len = len(request) + 8package = '{:0>8}'.format(request_len)package += requestreturn packagedef input_response(self, data):self.response += datadef data_valid(self):if len(self.response) < 8:return Falselength = self.response[:8]if int(length) == len(self.response):return Trueelse:return Falsedef clear(self):self.response = ""def get_response(self):if False == self.data_valid():return ""return self.response[8:]
pack_request用于将数据打包组装;input_response是为了让数据接收方可以一直接收数据(因为不是每次调用input_response就可以把所有数据都读取,这在数据量很大是比较常见);data_valid用于检测接收到的数据是否已经接收完毕,因为数据接收并非一次性完成,所以我们需要一个逻辑判断是否还需要接收数据。因为我们的数据有严密的结构,我们可以通过接收的数据长度判断数据是否接收完毕。get_response则是在数据接收完毕后,接收方调用获取完整的数据。
因为服务端和客户端都存在数据打包发送和解包的工作,所以socket_protocol将是整个通信数据的基础类。
我们再看下稍微简单点的服务器代码
import os
import time
import socket
import threading
from socket_protocol import socket_protocoldef deal_data(data):send_data = "recv" + datareturn send_dataclass socket_server:def __init__(self, deal_func = None):self._stop_event = threading.Event()self._communicate_thread = Noneif deal_func:self.deal_func = deal_funcelse:self.deal_func = deal_datapassdef __del__(self):self.Stop()passdef Start(self):if self._communicate_thread:returnself._communicate_thread = threading.Thread(target=self._worker)self._communicate_thread.start()def _worker(self):self._stop_event.clear()s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(("localhost", 9001))s.listen(1)con,addr = s.accept()s_data = socket_protocol()while False == self._stop_event.wait(0.1):data = con.recv(1024)s_data.input_response(data)if s_data.data_valid():send_data = self.deal_func(s_data.get_response())con.sendall(s_data.pack_request(send_data))s_data.clear()con.close()def Stop(self):self._stop_event.set()while self._communicate_thread.is_alive():time.sleep(0.01)self._communicate_thread = None
socket_server类在构造函数中暴露了一个参数,用于指定处理接收到数据的函数入口地址。这样我们就让服务器通信这块逻辑和数据处理业务解耦。而全局deal_data方法,则是在用户没有传入处理数据的函数指针时的一个替代品,它没有任何作用,只是为了保证代码的严谨性。在Start函数中,我们启动了一个用于接收和处理数据的线程。相应的Stop方法则是终止该线程执行。这个类的核心是线程函数_worker的实现。它在本地绑定了9001端口,然后不停的从该端口读取数据。如果协议类socket_protocol的对象判断本次读取数据已经完毕,它就会调用构造函数中传入的方法处理获取的数据,然后将该方法返回的数据打包后发给请求方。
客户端的实现则稍微复杂点
import os
import time
import socket
import threading
from socket_protocol import socket_protocolclass socket_client:def __init__(self):self._response_ready = threading.Event()self._stop_event = threading.Event()self._lock_excute = threading.Lock()self._communicate_thread = Noneself._cmd = ""self._result = ""passdef __del__(self):self.Stop()passdef Query(self,cmd):self._lock_excute.acquire()self._cmd = cmdself._response_ready.clear()self._response_ready.wait()result = self._resultself._lock_excute.release()return resultdef _worker(self):self._stop_event.clear()s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(("localhost", 9001))s_data = socket_protocol()while False == self._stop_event.wait(0.1):query = ""if len(self._cmd) == 0:continuequery = self._cmd self._cmd = ""send_data = s_data.pack_request(query)s.sendall(send_data)while False == self._stop_event.wait(0.1):data = s.recv(1024)s_data.input_response(data)if s_data.data_valid():self._result = s_data.get_response()s_data.clear()self._response_ready.set()breaks.close()def Start(self):if self._communicate_thread:returnself._communicate_thread = threading.Thread(target=self._worker)self._communicate_thread.start()def Stop(self):self._stop_event.set()while self._communicate_thread.is_alive():time.sleep(0.01)self._communicate_thread = None
Start和Stop函数分别用于启动和关闭一个发送请求接收数据的线程。线程函数_worker也和socket_server中类似,只是它要不停判断self._cmd是否有数据,也就是是否有请求进来。如果有请求进来,它就将该请求通过socket_protocol打包发送给服务端,然后在从服务端取回结果并解包,把解包后的结果放入self._result中。通过self._response_ready.set()设置事件告知业务方请求完毕,可以来拿结果了。Query函数就是业务方调用的入口,它使用锁操作保证每次只能有一次查询行为。然后通过设置self._cmd告知线程要向服务器发送该指令,然后通过等待线程设置self._response_ready事件来等待请求返回,并把结果返回给调用方。客户端设计的比较复杂的一个重要原因是我们这个模型要求请求是有序的。因为在测试过程中,我发现pydbgp是非常脆弱的,往往因为一些不合常理的查询顺序导致整个程序都死掉。所以我把“有序”的特性设计在了客户端基础类中。
看完基础类,我们再来看看我们需要控制的pydbgp是怎么被调用的。在《跨平台PHP调试器设计及使用方法——探索和设计》一文中,我说明过我只是想把pydbgp当成一个工具来使用,而尽量不要对其源码有任何改动——除非有bug。因为pydbgp不能像API一样使用,所以我只能模拟标准输入输出来达到和它的交互。而如果标准输入输出被改变,将影响整个程序,所以为了避开这种设计对我们自己的代码及其他第三方库的影响。我们需要将pydbgp作为一个独立的进程来执行。
我们需要重定向标准输入和输出,于是我设计了一个重定向之后的输入类input_redirection,其核心的就两个函数
def readlines(self, size=-1):while len(self._data) == 0:time.sleep(0.01)self._lock_excute.acquire()ret_data = self._dataself._data = ""self._lock_excute.release()return ret_datadef write(self, data):self._lock_excute.acquire()logging.debug("reqeust: " + data)self._data = dataself._lock_excute.release()
write函数用于从服务器中接收请求的内容,然后重定向之后的输入通过readlines读取内容。从而达到模拟请求的目的。
而重定向标准输出类则相对复杂点,因为它要牵扯到数据的内容。pydbgp在调试过程中分为两种状态,一种是调试某个session的阶段,就是下图中4的过程,以后我们称该阶段为session阶段;另外一种是不调试任何session的阶段,即除去4之外的其他阶段,之后我们称该阶段为no_session阶段。 因为pydbgp比较脆弱,不能在不同阶段调用另一个阶段的命令,轻则告知出错,重则整个程序都死掉。所以我们必须在执行每条指令后判断其所处于的阶段,而这种判断规则则和其返回的数据特征有关。
而作为命令发起方,在发起一个命令后,可以获取命令执行的结果。而对于当前pydbgp处于什么阶段,则也需要知道,否则不能保证发送的下条命令会不会把pydbgp搞挂掉。所以我就在返回结果中加入一些特征,使得命令发起方可以得知指令执行后的调试器阶段信息。具体的做法就是在数据后加入特征码,这个逻辑是在_send_data中实现的。
class output_redirction:_out = None_query_event = None_data = ""_response = ""def __init__(self, query_event):self._out = sys.stdoutself._query_event = query_eventdef write(self, output_stream):if re.match('^\[dbgp-', output_stream):self._send_data(True)elif re.match('^\[dbgp\]', output_stream):self._send_data(False)else:self._data += output_streamdef flush(self):passdef _send_data(self, is_seesion):if (is_seesion):end_ch = "@\n"else:end_ch = ":\n"data = base64.b64encode(self._data) + end_chlogging.debug("response:" + self._data + end_ch)self._response = dataself._query_event.set()self._out.write(data)self._data = "" self._out.flush()def get_reponse(self):return self._response
另一个问题就是我们如何判断当前pydbgp所处的阶段。我们发现如果处在session阶段,则返回的数据是以“[dbgp-”开头的;如果是no_session阶段,则是“[dbgp]”开头的。利用这个特征,我们在write函数中分析出所处阶段,并告知_send_data发送什么样的数据。
剩下的工作便是让整个程序的标准输入和输出被重定向,还有就是启动通信的服务端。
query_event = threading.Event()
out_r = output_redirction(query_event)
in_r = input_redirection()
sys.stdin = in_r
sys.stdout = out_r
sys.stderr = out_rdef Query(cmd):logging.debug("query " + cmd)query_event.clear()in_r.write(cmd)query_event.wait()return out_r.get_reponse()if __name__ == "__main__":cmd_server = socket_server(Query)cmd_server.Start()sys.exit(main([0]))
和服务端相对应的,则存在一个与其交互的客户端。它便是本文最开始结构图中的pydbgpd_stub模块,之所以取名为stub是为了让调用pydbgp像直接调用一样。在pydbgpd_stub中,它明确了pydbgp处于不同阶段可以调用的不同的命令——分别保存在_session_cmd和_no_session_cmd连个数组中。由于命令比较长,这儿就不列出来了。
def __init__(self):self._exc_cmd = "python pydbgpd_proxy.py"self._lock_excute = threading.Lock()self._cmd_client = socket_client()def _is_cmd_valid(self, cmd, cmd_list):for item in cmd_list:if cmd.startswith(item):return Truereturn Falsedef start(self):if (self._exc_cmd == None):raise NameError("exc_cmd is none")if "Windows" == platform.system():self._process = subprocess.Popen(self._exc_cmd, shell = False)else:self._process = subprocess.Popen(self._exc_cmd, shell = True, preexec_fn = os.setpgrp)time.sleep(2)self._cmd_client.Start()
pydbgpd_stub在启动服务器进程时,区分了不同的操作系统。这也是没有办法的事,因为不同系统里,终止子进程和孙子进程的方法不能通用。
def stop(self):self._cmd_client.Stop()if not self._process:raise NameError("subprocess is none")else:if "Windows" != platform.system():pid = self._process.pidpgid = os.getpgid(pid)os.kill(-pgid, 9)self._process.terminate()self._process.kill()self._process = None
接下来我们要看其暴露的最主要的两个方法
def is_session(self):self._lock_excute.acquire()is_session = self._is_sessionself._lock_excute.release()return is_sessiondef query(self, query_cmd):data = ""if self._is_session:if not self._is_cmd_valid(query_cmd, self._session_cmd):return "invalid cmd"else:if not self._is_cmd_valid(query_cmd, self._no_session_cmd):return "invalid cmd"self._lock_excute.acquire()data = self._cmd_client.Query(query_cmd)if len(data) > 1:if data[-2] == "@":print "Switch to Session \n"self._is_session = Trueelif data[-2] == ":":self._is_session = Falseprint "Switch to No Session \n"data = base64.b64decode(data[:-2])self._lock_excute.release()return data
is_session方法用于告知调用方当前调试器处在什么阶段(我用的是阶段而非状态,状态我将用于session阶段中调试器的情况描述)。query方法则是请求服务端获取请求结果并更改调试器阶段信息。于是调用方只要调用query方法就可以发起调试命令,就像调用本地方法一样。
相关文章:

MVP模式的相关知识
MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Mod…
“数学不行,还能干点啥?”面试官+CTO:干啥都费劲!
关于数学与程序员的“暧昧”关系,先看看网友的看法:同时编程圈也流传着一个段子:一流程序员靠数学,二流程序员靠算法,末端程序员靠百度,低端看高端就是黑魔法。想一想,我们日常学习、求职、工作…

CentOS7 yum 源的配置与使用
YUM:Yellowdog Updater Modified Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器。基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖…

跨平台PHP调试器设计及使用方法——协议解析
在《跨平台PHP调试器设计及使用方法——探索和设计》一文中,我介绍了将使用pydbgp作为和Xdebug的通信库,并让pydbgp以(孙)子进程的方式存在。《跨平台PHP调试器设计及使用方法——通信》解决了和pydbgp通信的问题,本文…

测试客户端发图图
转载于:https://blog.51cto.com/ericsong/116942
搜狐、美团、小米都在用的Apache Doris有什么好? | BDTC 2019
【导读】12 月 5-7 日,由中国计算机学会主办,CCF 大数据专家委员会承办,CSDN、中科天玑协办的中国大数据技术大会(BDTC 2019)在北京长城饭店隆重举行。100 顶尖技术专家、1000 大数据从业者齐聚于此,以“大…

cacti邮件告警设置
功能说明对指定流量图形(指定接口)设置最高或最低流量阀值,当流量出现异常偏高或偏低触发阀值,系统自动将异常信息以邮件形式通知指定收件人。如果收件人邮箱是139邮箱,还可以增设短信通知功能。设置前准备设置该功能之…
跨平台PHP调试器设计及使用方法——高阶封装
在《跨平台PHP调试器设计及使用方法——协议解析》一文中介绍了如何将pydbgp返回的数据转换成我们需要的数据。我们使用该问中的接口已经可以构建一个简单的调试器。但是由于pydbgp存在的一些问题,以及调试器需要的一些高级功能,我们还需要对这些接口进行…

Oracle的口令文件(passwordfile)的讲解(摘录)
初学oracle,很多概念迷糊,今天看到这文章,让我有一个比较清晰的认识。转载[url]http://www.itpub.net/viewthread.php?tid906008&extra&page1[/url]1、os认证oracle安装之后默认情况下是启用了os认证的,这里提到的os认证…
如何优雅地使用pdpipe与Pandas构建管道?
作者 | Tirthajyoti Sarkar译者 | 清儿爸编辑 | 夕颜出品 | AI科技大本营(ID: rgznai100) 【导读】Pandas 是 Python 生态系统中的一个了不起的库,用于数据分析和机器学习。它在 Excel/CSV 文件和 SQL 表所在的数据世界与 Scikit-learn 或 Te…

第 十 天 : 添 加 硬 盘 和 分 区 挂 载 等
小Q:狼若回头,必有缘由,不是报恩,就是***; 事不三思必有败,人能百忍则无忧。今天的进度虽然慢了,但是学习状态还是一如往常,只不过今天遇到了不少新的知识点,需要好好想想…
从4个月到7天,Netflix开源Python框架Metaflow有何提升性能的魔法?
作者 | Rupert Thomas译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导语】Metaflow 是由 Netflix 开发,用在数据科学领域的 Python框架,于 2019 年 12 月正式对外开源。据介绍,Metaflow 解决…
SOA标准发展混乱 国内业务缺少经验
近年来,SOA已经成为国际及我国信息技术领域的重大热点之一。从2005年至今,SOA逐渐成为影响中国IT系统构建的主导思想。从2006年开始,SOA的建设方法已在我国部分行业信息化项目中开始得以越来越广泛的应用。 但热潮背后, SOA概念在…
跨平台PHP调试器设计及使用方法——界面设计和实现
一个优秀的交互设计往往会影响一个产品的命运。在设计这款调试器时,我一直在构思这款调试器该长什么样子。简单、好用是我设计的原则,于是在《跨平台PHP调试器设计及使用方法——立项》一文中,我给出了一个Demo。之后实现的效果也与之变化并不…

AJAX安全-Session做Token
个人思路,请大神看到了指点 个人理解token是防止扫号机或者恶意注册、恶意发表灌水,有些JS写的token算法,也会被抓出来被利用,个人感觉还是用会过期的Session做token更好,服务器存储,加载到客户端页面&…
跨平台PHP调试器设计及使用方法——使用
经过之前六篇博文的分析和介绍,大家应该对这套调试器有个初步的认识。本文我将讲解它的使用方法。(转载请指明出于breaksoftware的csdn博客) 上图是该软件界面的布局,我们之后的讲解也将围绕着这些功能展开。 文件夹管理 在查看一…
管理7k+工作流,月运行超10000万次,Lyft开源的Flyte平台意味着什么?
作者 | Allyson Gale译者 | 刘畅编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Flyte 平台可以更容易的创建并发,可伸缩和可维护的工作流,从而进行机器学习和数据处理。Flyte 已有三年多的训练模型和数据处理经…

Jmeter组件执行顺序与作用域
一、Jmeter重要组件: 1)配置元件---Config Element: 用于初始化默认值和变量,以便后续采样器使用。配置元件大其作用域的初始阶段处理,配置元件仅对其所在的测试树分支有效,如,在同一个作用域…
跨平台PHP调试器设计及使用方法——拾遗
之前七篇博文讲解了跨平台PHP调试器从立项到实现的整个过程,并讲解了其使用方法。但是它们并不能全部涵盖所有重要内容,所以新开一片博文,用来讲述其中一些杂项。(转载请指明出于breaksoftware的csdn博客) 触发调试的…
召唤超参调优开源新神器:集XGBoost、TensorFlow、PyTorch、MXNet等十大模块于一身...
整理 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】Optuna是一款为机器学习任务设计的自动超参数优化软件框架,是一款按运行定义(define-by-run) 原则设计的优化软件,允许用户动态地调整搜索空间&#…

Linux下的Silverlight:Moonlight 1.0 Beta 1发布了
Moonlight是微软Silverlight的一个开源实现,其目标平台是Linux与Unix/X11系统。自从2007年9月开始,Moonlight就在Mono项目下进行了开发,它是由Novell发起并资助的。现在,Moonlight 1.0 Beta 1已经向公众发布了。 Novell和Mono宣布…
在visual studio 2010中调用ffmpeg
转自:http://blog.sina.com.cn/s/blog_4178f4bf01018wqh.html 最近几天一直在折腾ffmpeg,在网上也查了许多资料,费了不少劲,现在在这里和大家分享一下。 一、准备工作本来是想自己在windows下编译ffmpeg生成lib、dll等库文件的&am…
无线路由器与无线AP的区别
摆脱线缆的羁绊,手捧一杯香醇的咖啡在家中的任何角落都可以无拘无束和网友谈天说地──这就是无线的魅力!在无线网络迅猛发展的今天,无线局域网(Wireless Local-Area Network,简称WLAN)已经成为许多SOHO家庭…

Simple Dynamic Strings(SDS)源码解析和使用说明一
SDS是Redis源码中一个独立的字符串管理库。它是由Redis作者Antirez设计和维护的。一开始,SDS只是Antirez为日常开发而实现的一套字符串库,它被使用在Redis、Disque和Hiredis等作者维护的项目中。但是作者觉得这块功能还是比较独立的,应该让其…
“不会Linux,到底有多危险?”骨灰级成程序员:基本等于自废武功!
说起程序员的必备技能,我想大家都可以说很多,比如:算法、数据结构、数学、编程语言等等。对于程序员来讲,这些底层能力固然重要,但是,工具同样也是如此,比如常被大家所忽视的:Linux。…

“Uncaught TypeError: string is not a function”
http://www.cnblogs.com/haitao-fan/archive/2013/11/08/3414678.html 今天在js中写了一个方法叫做search(),然后点击按钮的时候提示: “Uncaught TypeError: string is not a function” 百思不得其解啊,我的js木有问题啊啊.... 后来才发现酱…

关于Nikon Ai AF 28mm F1.4D遮光罩的问题
-- 好不容易找到百变妖,确实比较妖!!遮光罩不好找,原厂推荐的HK-7基本属于古董中的古董。 爬文很久,终于找到一篇国外的介绍,说可以用HK-4代替,比HK-7效果更好,而且可以用85mm 1.4D-…

Simple Dynamic Strings(SDS)源码解析和使用说明二
在《Simple Dynamic Strings(SDS)源码解析和使用说明一》文中,我们分析了SDS库中数据的基本结构和创建、释放等方法。本文将介绍其一些其他方法及实现。(转载请指明出于breaksoftware的csdn博客) 字符串连接 SDS库提供下面两种方法进行字符串…
亚马逊机器学习服务:深入研究AWS SageMaker
作者 | Manish Manalath译者 | Shawn编辑 | Carol出品 | AI科技大本营(ID: rgznai100) 机器学习是一个从数据中发现模式的强大概念。但是,如果您尝试过从零开始构建机器模型,那么您一定知道设计一个可扩展的机器学习工作流是多大的…

Java Timer 定时器的使用
一、延时执行首先,我们定义一个类,给它取个名字叫TimeTask,我们的定时任务,就在这个类的main函数里执行。 代码如下:package test;import java.util.Timer;public class TimeTaskTest { public static void main(Str…