3.进程间通信方案
3.1方案一:类UDP通信
3.1.1设计思路
由于工具将界面和任务处理进行了分离,因此,工具的整体框架可以看成是由客户端和服务器组成,客户端发送请求,服务器响应请求并返回结果。
客户端即界面,用户在界面上操作,并最终在界面上得到操作的结果。界面由主界面和子界面组成,每个界面是一类相同任务类型的集合,如Snmp报文测试界面可以执行向设备发送snmp报文的任务,连接类界面则实现了SSH、Netconf等登陆设备的方式。
服务器则由任务分发模块和任务处理模块组成,任务分发模块将请求的任务分发至对应的任务处理模块进行处理。任务处理模块的功能是执行具体的任务请求,每个模块均是一类任务类型的集合,简单来看任务处理模块和界面之间可以存在一一对应的关系,如Snmp报文测试界面存在对应的Snmp报文任务模块,连接类界面存在对应的连接类任务模块。不同的是,一类界面可能同时打开多个子界面,如连接类子界面0、连接类子界面1,而任务处理模块则只存在一个实例,处理所有子界面的任务请求。需要注意的是,实际界面和任务模块之间并不是绑定的关系,即界面的请求根据其任务类型,可以发送给多个不同的任务模块。所以服务器的任务模块需要包含所有界面中的任务请求处理程序,如果服务器收到未知的任务请求,会返回任务未定义错误信息。
图1 界面和服务器
综上所诉,进程间通信需要解决的问题是,将某一界面的任务请求数据(任务请求代码和附加数据)发送至对应的任务处理模块,并且将任务处理结果返回至该界面。为了解决该问题,采用UDP通信的思想,给每个界面和任务处理模块分配一个IP,数据封装在报文中,报文包含源IP地址和目的IP地址,转发模块根据IP转发报文,达到通信的目的。
图2 数据传输问题
3.1.2详细设计
图1 通信设计图
一、网络结构
1、硬件结构:
(1) 硬件结构由Pipe和Queue组成,所有的报文均是生产者写入Pipe或者Queue,由消费者从Pipe或者Queue中读取报文,以达到报文在进程间和线程间转发的目的;
2、转发模块:
(1) 转发模块主要负责定义数据包格式和地址格式;
(2) 解析接收到的报文的地址,选择路由进行转发;
3、应用层:
(1) 发送数据的接口;
二、报文结构
1、报文由首部和数据两部分组成,首部的主要目的是标记路由信息,数据的主要目的是携带任务执行所需要的必要信息。
图2 报文结构
2、如下图,首部包含源IP、目的IP、请求任务类型代码和报文编号。
图3 报文首部
(1)源IP:发送报文模块的IP地址;
(2)目的IP:报文的接收模块的IP地址;
(3)请求任务类型代码:任务类型大类,指定了由哪个任务处理模块处理本次任务请求,如指定连接类任务模块响应任务请求;
(4)报文编号:每条报文的唯一标识,可根据报文编号对应发送的报文和任务处理后的响应报文,报文编号由应用层自动生成;
3、数据部分由下图所示五部分组成:
图4 数据结构
(1)请求任务具体类型:指定了任务处理模块中的具体任务,如连接类模块中的建立SSH连接任务;
(2)附加数据:执行任务所需的数据,如建立SSH连接任务需要设备IP、用户名和密码等数据,附加数据的数据格式由各个任务自行定义,无统一规定;
(3)任务是否成功标识:此部分由任务处理模块在响应报文中添加,标记了此次任务请求是否执行成功;
(4)任务错误信息:此部分由任务处理模块在响应报文中添加,记录任务执行过程中出现的错误信息;
(5)任务结果附加信息:此部分由任务处理模块在响应报文中添加,记录任务执行的具体结果数据,如请求向设备发送命令任务得到的设备回显信息;
4、Python实现的报文是字典类型,如下所示:
package = {
'source_id': source_buffer_id,
'destination_id': destination_buffer_id,
'task_id': task_id,
'msg_id': msg_id,
'data': data
}
data = {
'task_type': task_type0,
'append_data': [],
'task_success': True/False,
task_type0: None,
'error_msg': error_msg
}
与报文的对应关系为:
图5 Python实现与报文的对应关系
三、程序设计
1、IP格式:
(1) IP由两位数字组成,数字之间用_分割,如1_32;
(2) IP在局域网中是唯一的,跨局域网可能存在相同的IP;
2、IP分配:
(1) 首先要确定哪些对象需要分配IP,本工具对于具体的功能类分配唯一的IP地址,功能类是一类功能实体的集合,在程序中表现为类,如各个子界面、任务处理模块;
(2) 每一类的功能模块有全局唯一的代码,作为IP的首位,此代码也是task_id的首位;
图6功能模块代码
(3) 一类界面的多个子界面IP的首位是相同的,比如两个连接类子界面的IP分别为5_0、5_1,即可以通过IP的首位确定是哪一类的功能模块;
(4) 将每个进程看做是一个局域网,进程启动时会分配到一个局域网IP。数据如需发送至其他进程,则报文中的目的IP需要包含局域网的IP,否则默认报文在当前局域网(进程)中转发;
(5) 子进程的IP由主进程在创建子进程时生成;
(6) 各个功能模块的IP由主线程在创建功能模块时生成;
3、路由学习:
(1) 工具的路由是静态的,主进程在创建子进程时,会将所有进程的IP信息都发送给子进程;
(2) 进程内,各个功能模块的代码存储在主线程中,供转发线程查询;
4、转发规则:
(1) 报文由报文转发线程进行转发,每个进程中只有一个转发线程,即界面程序设计方案2中的监听线程;
(2) 转发线程在接收到报文之后,首先分析目的地址:
① 目的地址只含有一个IP地址时,如果此IP地址与当前进程的IP地址相同,则分析task_id,根据task_id确定任务处理模块;
② 目的地址只含有一个IP地址时,如果此IP地址与当前进程的IP地址不同,则寻找路由进行转发;
③ 目的地址包含多个IP地址时,提取首位IP,如果首位IP与当前进程IP相同,则根据第二个IP进行转发,且转发前,删除首位IP,如8_0_9_0修改为9_0;
④ 目的地址包含多个IP地址时,如果首位IP与当前进程IP不同,则根据首位IP进行转发;
⑤ 如果找不到转发路由,则返回功能未定义错误;
(3) 如果报文需要转发到其他进程,且源IP的首位IP不是当前进程的IP,则将在'source_id'的头部加入当前进程的IP,标记报文经过了当前进程,如9_0转发后为8_0_9_0;
(4) 一个报文支持多目的地址转发,所有目的地址以list的形式存放在destination_id对应的值中;
(5) 功能处理模块接收到报文之后,如果报文中的task_type未定义处理方法,则返回功能未定义错误,如果task_type已定义,则返回处理后的结果;
(6) 功能处理模块处理完请求的任务后,将接收到报文中的source_id和destination_id交换再交由转发线程转发;
(7) 当程序包含多个子进程时,子进程间的通信经由主进程实现,即子进程0的报文先发送至主进程,再由主进程转发至子进程1,目的是不需要建设过多的物理线路,简化程序设计;
(8) 传输大数据时,数据先存放在文件中,报文中只发送文件的目录地址;
5、进程间转发:
图7进程间转发
(1) 某个功能模块将待发送的数据通过报文生成模块生成原始报文,如Trap界面(集成在了监听界面)发送请求启动Trap监听的报文,
其中:
9_0:监听界面的IP;
6_0:表示报文的目的地是任务中心进程:
1_2023...:请求由Trap功能模块执行任务;
0:执行的具体任务代码为0,即'start_listening';
{'source_id': '9_0', 'destination_id': '6_0', 'task_id': '1_20230321105253598910', 'msg_id': '20230321105253598564', 'data': {'task_type': 0, 'append_data': [{'port': '160', 'ipv4': True, 'ipv6': True}]}}
(2) 主进程的转发线程在接收到报文之后,由于需要转发的其他进程,在'source_id'的头部加入当前进程的IP,如上述报文处理后被转发:
{'source_id': '8_0_9_0', 'destination_id': '6_0', 'task_id': '1_20230321105253598910', 'msg_id': '20230321105253598564', 'data': {'task_type': 0, 'append_data': [{'port': '160', 'ipv4': True, 'ipv6': True}]}}
(3) 子进程接收到报文后,检查destination_id,如果目的地址就是当前进程,无下一跳地址,则分析task_id,根据task_id首位确定报文请求的服务应该发送至哪个功能模块进行处理,如上述报文将转发至Trap模块。如果功能模块未定义,则子进程返回未定义错误;
(4) 功能模块接收到报文之后,提取task_type,并在定义的服务列表中寻找该task_type,如果task_type已定义处理方法,则调用方法执行任务,处理完成后返回处理结果,如果task_type未定义,则返回未定义错误。上述报文在处理完成后返回的报文如下:
{'source_id': '6_0', 'destination_id': '8_0_9_0', 'task_id': '1_20230321105253598910', 'msg_id': '20230321105253598564', 'data': {'task_type': 0, 'append_data': [{'port': '160', 'ipv4': True, 'ipv6': True}], 'task_success': True, 'error_msg': ''}}
(5) 子进程转发线程将功能模块处理完成后的报文根据目的地址进行转发,如上述报文经过子进程转发后为:
{'source_id': '6_0', 'destination_id': '8_0_9_0', 'task_id': '1_20230321105253598910', 'msg_id': '20230321105253598564', 'data': {'task_type': 0, 'append_data': [{'port': '160', 'ipv4': True, 'ipv6': True}], 'task_success': True, 'error_msg': ''}}
(6) 主进程在接收到报文后,根据转发规则转发报文,如上述报文经过转发后为:
{'source_id': '8_0_6_0', 'destination_id': '9_0', 'task_id': '1_20230321105253598910', 'msg_id': '20230321105253598564', 'data': {'task_type': 0, 'append_data': [{'port': '160', 'ipv4': True, 'ipv6': True}], 'task_success': True, 'error_msg': ''}}
目的地址为9_0,报文将会转发到IP为9_0的监听界面。
(7) 最终,处理后的报文返回到发起服务请求的界面,完成了从发起服务请求,到处理服务请求,最后返回服务请求结果的完整流程。
6、进程内转发:
图8进程内转发
进程内转发与进程间转发的差异在于报文的目的地址首位IP不指向其他进程的IP,转发线程在接收到报文后,寻找路由进行转发。其他过程与进程间转发一致。
7、主动上报:
图9事件上报
功能模块在响应任务返回结果之后,后台可能依然会存在任务执行线程,该线程会记录任务对应的源IP等信息。如果线程在执行任务过程中出现异常事件,线程会将该事件根据记录的源IP上报给请求服务的模块。如trap模块在后台持续执行监听任务过程中,如果遇到异常退出监听的事件,监听任务线程会将事件上报给监听界面,告知用户后台的任务运行情况。
3.1.3方案有效性
此方案能够实现进程内和跨进程的通信功能,且不存在丢包的情况。
3.1.4程序示例
https://github.com/AlvinsFish/UiExample