位置: IT常识 - 正文

支持插件的消息中间件【msg broker with plugin】 知然 博客园(插件不支持是什么意思)

编辑:rootadmin
支持插件的消息中间件【msg broker with plugin】 - 知然 - 博客园支持插件的消息中间件【msg broker with plugin】支持插件的消息中间件msg broker 支持插件的消息中间件【msg broker with plugin】

推荐整理分享支持插件的消息中间件【msg broker with plugin】 知然 博客园(插件不支持是什么意思),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:插件信息,支持插件的浏览器,支持插件的消息中间件是什么,插件信息,插件允许,支持插件的消息软件,支持插件的消息怎么设置,支持插件的消息怎么设置,内容如对您有帮助,希望把文章链接给更多的朋友!

支持插件的消息中间件

msg broker with plugin

Msg Broker概念:

msg broker是实现application 之间互通讯的组件。通常为实现application之间的解耦,消息都是通过msg broker完成转发。application只需知道其他applicatipn的逻辑名称,而不需要知道对方的具体位置。Broker中维护一个查找表,记录着哪个application注册在此逻辑名称之下,所以消息总是会被正确的投递到目的地。

msg broker不限于1-1的转发,也支持1-N的模式。其主要功能有:

实现多个application的互通讯,而隐藏彼此的位置实现消息个格式的转换,如json to bin安全控制,msg broker可以再转发消息前进行一定程度的安全验证增大系统的可伸缩性,由于application通讯的目标变成了逻辑结点,而该逻辑结点可以对应多个物理结点,理论上可以动态的增加物理结点,来扩展该逻辑结点的吞吐量。msg broker可以用来集成服务,并且可以暴楼服务的部分接口msg broker 具有的缺点是:增加了复杂性,多了一层转发可维护性降低,需要理清msg broker和各个application和服务的关系。降低性能,主要是实时性能下降了,消息需要多转发一边,单次请求的延时大大增加了。当前流行的Broker的特点和缺点:

Msg Broker的结构:

流行的Broker中间件介绍:RabbitMQ

项目地址:http://www.rabbitmq.com/

支持插件的消息中间件【msg broker with plugin】  知然  博客园(插件不支持是什么意思)

RabbitMQ是由Erlang开发的以高效、健壮以及高度伸缩性的消息服务器。其所包含的概念有Producer、Consumer、Exchange、Queue。RabbitMQ基于QMQP协议,支持的语言也非常丰富,文档也非常清晰。使用RabbitMQ可以实现订阅发布模型、RPC模型、路由模型等,参见RabbitMQ的例子:http://www.rabbitmq.com/getstarted.html。

但是它有如下局限性:

RabbitMQ 没有针对连接做控制,它是为高效而生,它对外来的请求是信任的,不存在安全验证,如任何一个client都可以创建消息队列,所以RabbitMQ一定是放在内网的。使用RabbitMQ ,我们是通过Client远程操作RabbitMQ,不能定制RabbitMQ的功能。ZeroMQ

项目地址:http://www.zeromq.org/

ZeroMQ是一个Socket封装库,号称是最快的消息内核。ZeroMQ可以支持TCP、UDP、IPC等多种通讯协议。ZeroMQ可以实现的通讯模型就更多了,几乎涵盖了消息通讯的所有模式,参见官网介绍http://www.zeromq.org/intro:read-the-manual 。

其局限性为:

ZeroMQ虽然封装了消息传输的复杂性,但是它也隐藏了连接的建立、断开等过程。ZeroMQ传输消息更像是udp数据报,使用者不能知道对方何时连接建立、何时连接断开。我们需要一个不一样的Broker应用场景介绍

在网络游戏中,cliet和服务器是通过tcp长连接的。相对于HTTP+WebServer的不同在于:

client连接到服务器,需要进行身份验证,通常是client第一个消息包含身份验证数据如用户名密码等,而验证通过后该连接为可信任连接。client 任意时间都可以向服务器发送请求,而不需要服务器立即返回,同样,服务器是在任意时间(当然会有实时性等约束)都可以像client推送消息。client断开连接时,服务器必须捕获该事件,以便完成一些数据清理操作。client对应的一般是个集群,但是client无从得知细节,因为它只连接最外层的一个,给他取个名字“MsgBroker”。Msg Broker 不许有一定的安全控制,如心跳、网络包频率限制等,防范某些可能的攻击。Msg Broker需要高度可定制。不同的游戏主要是逻辑不同,而MsgBroker大多大同小异。当然MsgBroker总是会根据需求稍作修改。Msg Broker 主要瓶颈是IO操作,因为它涉及大量的网络连接、断开、心跳、广播消息等。而它具有的领域逻辑则非常非常少。所以Msg Broker的逻辑可以使用动态脚本实现,其实时性、效率都能满足要求。需要的broker具有的功能:能够捕获client连接事件能够捕获client断开事件具有网络心跳功能方便的消息发送接口broker可以以client的角色连接到其他Server,因为从其他逻辑角度看,Broker可能是其他服务的使用者。Broker 提供消息收发框架,逻辑层通过插件实现。实现插件的方式有

动态链接库,可以将逻辑层封装到so链接库中python脚本,逻辑层可以有python脚本实现,Broker封装了载入python、调用python,封装消息发送接口到PyhtonLua脚本,逻辑层也可以又Lua脚本实现,Broker封装了载入lua、调用lua、封装消息接口给lua。Msg Broker 结构图

Msg Broker 的安装使用:安装依赖库:

由于msg broker支持Python和lua作为插件,那么必须确保linux下安装了相应的头文件。示例中的插件均只实现了echo功能。

确保Linux系统安装了Python,推荐python2.6确保安装了Python-devel,如果是centos,直接yum即可。确保安装了Lua-5.1.4, 其他版本没有测试过下载Msg Broker最新源码,目前处于0.1版本

svn co https://ffown.googlecode.com/svn/trunk/

编译源码:

cd trunk/example/plugin_msg_broker/make编译动态连接库插件

cd plugin/plugin_echo_dll/sh gen_dll.sh运行示例插件:运行动态链接库

./msg_broker_server 127.0.0.1 10241 plugin/plugin_echo_dll/libechoso另开终端,telent 127.0.01 10241, 收入5 回车,再输入5个字符,通讯协议是body长度加回车加body,如图:

运行Python 脚本示例程序

./msg_broker_server 127.0.0.1 10241 plugin/plugin_echo_py/echo.py同样使用telnet 测试echo功能运行Lua脚本示例程序

./msg_broker_server 127.0.0.1 10241 plugin/plugin_echo_lua/lua.py同样使用telnet 测试echo功能插件层设计分析:插件接口:#ifndef _PLUGIN_H_#define _PLUGIN_H_#include "channel.h"#include "message.h"class plugin_i{public: virtual ~plugin_i(){} virtual int start() = 0; virtual int stop() = 0; virtual int handle_broken(channel_ptr_t channel_) = 0; virtual int handle_msg(const message_t& msg_, channel_ptr_t channel_) = 0;};typedef plugin_i* plugin_ptr_t;typedef int (*handle_channel_msg_func_t)(const message_t& msg_, channel_ptr_t);typedef int (*handle_channel_broken_func_t)(channel_ptr_t);#define HANDLE_CHANNEL_MSG "handle_channel_msg"#define HANDLE_CHANNEL_BROKEN "handle_channel_broken"#endif

各个接口作用如下:

start 实现插件载入,环境初始化stop实现优雅的退出handle msg 为消息到来通知handle_broken 为对方连接关闭Channel 设计

channel 用来表示一个连接,可以理解成socket的抽象,也可直接理解成远程client。

#ifndef _CHANNEL_H_#define _CHANNEL_H_#include "socket_i.h"class channel_t{public: channel_t(socket_ptr_t sock_); ~channel_t(); void set_data(void* p); void* get_data() const; template<typename T> T* get_data() const { return (T*)this->get_data(); } void async_send(const string& buff_); void close();private: socket_ptr_t m_socket; void* m_data;};typedef channel_t* channel_ptr_t;#endif各个接口作用如下:构造,channel必须绑定一个socketset_data get_data用来操作channel私有数据,如我们可以在channel私有数据中存放该channel对应的uid,这样每个channel之需验证一次,以后自然知道到来的消息属于哪个channel。async_send 异步发送消息close 关闭连接动态链接库插件:流程如下:载入动态库获取动态库的接口,记录函数指针地址若有msg到来,调用动态链接库的handle_msg若连接关闭,调用动态链接库的handl_brokenint plugin_dll_t::start(){ m_dll_handler = ::dlopen(m_dll_name.c_str(), RTLD_NOW|RTLD_GLOBAL); if (NULL == m_dll_handler) { logerror((PLUGIN_IMPL, "plugin_dll_t::start dlopen failed:<%s>", dlerror())); return -1; } m_msg_cb = (handle_channel_msg_func_t)::dlsym(m_dll_handler, HANDLE_CHANNEL_MSG); m_broken_cb = (handle_channel_broken_func_t)::dlsym(m_dll_handler, HANDLE_CHANNEL_BROKEN); if (NULL == m_msg_cb) { logerror((PLUGIN_IMPL, "plugin_dll_t::start dlopen failed:<%s> not exist", HANDLE_CHANNEL_MSG)); return -1; } if (NULL == m_broken_cb) { logerror((PLUGIN_IMPL, "plugin_dll_t::start dlopen failed:<%s> not exist", HANDLE_CHANNEL_BROKEN)); return -1; } return 0;}int plugin_dll_t::stop(){ ::dlclose(m_dll_handler); return 0;}int plugin_dll_t::handle_broken(channel_ptr_t channel_){ return m_broken_cb(channel_);}int plugin_dll_t::handle_msg(const message_t& msg_, channel_ptr_t channel_){ return m_msg_cb(msg_, channel_);}Python 插件其工作流程如下:初始化Python解释权,将封装的发送消息接口注册到Python虚拟机中设置PythonPath载入python文件若msg到来,调用python全局函数handle_msg若channel断开,调用Python 全局handle_broken 函数#include "plugin_impl/plugin_python.h"#include "plugin_impl/pyext.h"#include "log_module.h"plugin_python_t::plugin_python_t(const string& name_): m_py_mod(NULL){ string pythonpath = "./"; int pos = name_.find_last_of('/'); if (-1 == pos) { m_py_name = name_; } else { m_py_name = name_.substr(pos+1); pythonpath = name_.substr(0, pos+1); } pos = m_py_name.find_first_of('.'); m_py_name = m_py_name.substr(0, pos); Py_InitializeEx(0); Py_SetPythonHome((char*)pythonpath.c_str()); initpyext(this); PyRun_SimpleString("import channel;import sys;sys.path.append('./plugin/plugin_echo_py/')");}plugin_python_t::~plugin_python_t(){ Py_Finalize();}int plugin_python_t::start(){ if(load_py_mod()) { return -1; } return 0;}int plugin_python_t::stop(){ return 0;}int plugin_python_t::load_py_mod(){ PyObject *pName, *pModule; pName = PyString_FromString(m_py_name.c_str()); pModule = PyImport_Import(pName); if (!pModule ) { Py_DECREF(pName); logerror((PLUGIN_IMPL, "can't find %s.py\n", m_py_name.c_str())); if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); return -1; } return -1; } m_py_mod = PyModule_GetDict(pModule); Py_DECREF(pName); Py_DECREF(pModule); return 0;}int plugin_python_t::handle_broken(channel_ptr_t channel_){ m_channel_mgr.erase(long(channel_)); delete channel_; return call_py_handle_broken(long(channel_));}int plugin_python_t::handle_msg(const message_t& msg_, channel_ptr_t channel_){ m_channel_mgr.insert(make_pair((long)channel_, channel_)); return call_py_handle_msg((long)channel_, msg_.get_body().c_str());}int plugin_python_t::call_py_handle_msg(long val, const char* msg){ PyObject *pDict = m_py_mod; const char* func_name = "handle_msg"; PyObject *pFunc, *arglist, *pRetVal; pFunc = PyDict_GetItemString(pDict, func_name); if (!pFunc || !PyCallable_Check(pFunc)) { logerror((PLUGIN_IMPL, "can't find function [%s]\n", func_name)); return -1; } arglist = Py_BuildValue("ls", val, msg); pRetVal = PyObject_CallObject(pFunc, arglist); Py_DECREF(arglist); if (pRetVal) { Py_DECREF(pRetVal); } if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); return -1; } return 0;}int plugin_python_t::call_py_handle_broken(long val){ PyObject *pDict = m_py_mod; const char* func_name = "handle_broken"; PyObject *pFunc, *arglist, *pRetVal; pFunc = PyDict_GetItemString(pDict, func_name); if (!pFunc || !PyCallable_Check(pFunc)) { logerror((PLUGIN_IMPL, "can't find function [%s]\n", func_name)); return -1; } arglist = Py_BuildValue("l", val); pRetVal = PyObject_CallObject(pFunc, arglist); Py_DECREF(arglist); if (pRetVal) { Py_DECREF(pRetVal); } if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); return -1; } return 0;}channel_ptr_t plugin_python_t::get_channel(long p){ map<long, channel_ptr_t>::iterator it = m_channel_mgr.find(p); if (it != m_channel_mgr.end()) { return it->second; } return NULL;}Lua 插件:工作流程如下:初始化lua虚拟机注册发送消息接口给lua载入Lua脚本有msg到来,调用lua的hanle_msg接口有channel断开,调用lua的handle_broken接口static plugin_lua_t* g_plugin_lua_obj = NULL;static int channel_send_msg(lua_State* ls_){ long ptr = (long)luaL_checknumber(ls_, 1); size_t len = 0; const char* msg = luaL_checklstring(ls_, 2, &len); channel_ptr_t c = g_plugin_lua_obj->get_channel(ptr); if (c) { c->async_send(msg); } return 0;}plugin_lua_t::plugin_lua_t(const string& name_): m_ls(NULL){ g_plugin_lua_obj = this; string luapath = "./"; int pos = name_.find_last_of('/'); if (-1 == pos) { m_lua_name = name_; } else { m_lua_name = name_.substr(pos+1); luapath = name_.substr(0, pos+1); } pos = m_lua_name.find_first_of('.'); m_lua_name = m_lua_name.substr(0, pos); m_ls = lua_open(); lua_checkstack(m_ls, 20); lua_pushcfunction(m_ls, channel_send_msg); lua_setglobal(m_ls, "_tmp_func_"); luaL_dostring(m_ls, "channel = {} channel.send = _tmp_func_ _tmp_func_ = nil"); string lua_str = "package.path = package.path .. \"" + luapath + "?.lua\""; luaL_openlibs(m_ls); if (luaL_dostring(m_ls, lua_str.c_str())) { lua_pop(m_ls, 1); } m_lua_name = name_;}plugin_lua_t::~plugin_lua_t(){}int plugin_lua_t::start(){ if (load_lua_mod()) { logerror((PLUGIN_IMPL, "can't find %s.lua\n", m_lua_name.c_str())); return -1; } return 0;}int plugin_lua_t::stop(){ return 0;}int plugin_lua_t::handle_broken(channel_ptr_t channel_){ m_channel_mgr.erase(long(channel_)); delete channel_; return call_lua_handle_broken(long(channel_));}int plugin_lua_t::handle_msg(const message_t& msg_, channel_ptr_t channel_){ m_channel_mgr.insert(make_pair((long)channel_, channel_)); return call_lua_handle_msg((long)channel_, msg_.get_body());}int plugin_lua_t::load_lua_mod(){ if (luaL_dofile(m_ls, m_lua_name.c_str())) { lua_pop(m_ls, 1); return -1; } return 0;}int plugin_lua_t::call_lua_handle_msg(long val, const string& msg){ lua_checkstack(m_ls, 20); lua_getglobal(m_ls, "handle_msg"); lua_pushnumber(m_ls, val); lua_pushlstring(m_ls, msg.c_str(), msg.size()); if (lua_pcall(m_ls, 2, 0, 0) != 0) { lua_pop(m_ls, 1); return -1; } return 0;}int plugin_lua_t::call_lua_handle_broken(long val){ lua_checkstack(m_ls, 20); lua_getglobal(m_ls, "handle_broken"); lua_pushnumber(m_ls, val); if (lua_pcall(m_ls, 1, 0, 0) != 0) { lua_pop(m_ls, 1); return -1; } return 0;}channel_ptr_t plugin_lua_t::get_channel(long p){ map<long, channel_ptr_t>::iterator it = m_channel_mgr.find(p); if (it != m_channel_mgr.end()) { return it->second; } return NULL;}msg_broker 待完善的地方:心跳层还未加入插件层报错不够友好Python 中封装的channel使用long型,调用send接口时需要从long转化到channel,需要优化一下,直接封装一个channel对象到PythonLua中channel的封装暂时也是使用long来表示,具有和上面一样的性能损耗问题
本文链接地址:https://www.jiuchutong.com/zhishi/304521.html 转载请保留说明!

上一篇:phpcms会员登录失败(phpcms v9用户手册)

下一篇:vue中动态添加style样式的几种写法总结(vue中动态添加表格)

  • 苹果12可以戴口罩解锁吗(苹果12可以戴口罩识别吗)

    苹果12可以戴口罩解锁吗(苹果12可以戴口罩识别吗)

  • 抖音怎么定位自己的店铺位置(抖音怎么定位自己的账号)

    抖音怎么定位自己的店铺位置(抖音怎么定位自己的账号)

  • 华硕飞行堡垒键盘灯怎么开(华硕飞行堡垒键盘灯光怎么关闭)

    华硕飞行堡垒键盘灯怎么开(华硕飞行堡垒键盘灯光怎么关闭)

  • 华为手表如何下载音乐(华为手表如何下载app软件)

    华为手表如何下载音乐(华为手表如何下载app软件)

  • 如何装双系统(手机如何装双系统)

    如何装双系统(手机如何装双系统)

  • 暴风影音中文字幕乱码(暴风影音中文字幕下载)

    暴风影音中文字幕乱码(暴风影音中文字幕下载)

  • 苹果型号a1586是6还是6s(苹果a1586是6还是6p)

    苹果型号a1586是6还是6s(苹果a1586是6还是6p)

  • 小米10青春版和小米10区别(小米10青春版和小米11青春版哪个更值得入手)

    小米10青春版和小米10区别(小米10青春版和小米11青春版哪个更值得入手)

  • 华为jeran10是什么型号(华为jeran10是什么意思)

    华为jeran10是什么型号(华为jeran10是什么意思)

  • 苹果下拉半屏有什么用(苹果手机往下拉半屏有什么用)

    苹果下拉半屏有什么用(苹果手机往下拉半屏有什么用)

  • vivo黑名单在哪里(vivo手机黑名单怎么拉出来?)

    vivo黑名单在哪里(vivo手机黑名单怎么拉出来?)

  • 广东移动加载失败的原因(为什么广东移动app老是加载失败)

    广东移动加载失败的原因(为什么广东移动app老是加载失败)

  • vivonex3是不是双扬声器(vivonex3是不是双卡双待)

    vivonex3是不是双扬声器(vivonex3是不是双卡双待)

  • 苹果手机为什么下载不了软件(苹果手机为什么连不上wifi)

    苹果手机为什么下载不了软件(苹果手机为什么连不上wifi)

  • oppo双卡的怎么有一个卡不能用了(oppo双卡怎么设置用哪个卡打电话)

    oppo双卡的怎么有一个卡不能用了(oppo双卡怎么设置用哪个卡打电话)

  • 第四代计算机是什么计算机(第四代计算机是采用什么作为逻辑元件的)

    第四代计算机是什么计算机(第四代计算机是采用什么作为逻辑元件的)

  • 手机的电筒有紫外线吗(手机手电筒能不能调出紫色的光)

    手机的电筒有紫外线吗(手机手电筒能不能调出紫色的光)

  • 网络bug是什么意思啊(网络bug是什么意思呀)

    网络bug是什么意思啊(网络bug是什么意思呀)

  • 苹果ipad屏幕点不动怎么办(苹果ipad屏幕点不动无法强制关机)

    苹果ipad屏幕点不动怎么办(苹果ipad屏幕点不动无法强制关机)

  • 路由器1200m和450m区别(路由器1200m和2100m区别)

    路由器1200m和450m区别(路由器1200m和2100m区别)

  • 华为手机自动锁屏怎么办(华为手机自动锁屏怎么解除)

    华为手机自动锁屏怎么办(华为手机自动锁屏怎么解除)

  • vivo手机root权限在哪(vivo手机root权限怎么设置)

    vivo手机root权限在哪(vivo手机root权限怎么设置)

  • 拼多多的那个猪怎么取消(拼多多的猪肉是真的吗)

    拼多多的那个猪怎么取消(拼多多的猪肉是真的吗)

  • xs max尺寸(苹果xs max尺寸)

    xs max尺寸(苹果xs max尺寸)

  • 荣耀7x导航信号弱怎么解决(荣耀7x不能导航了是怎么回事)

    荣耀7x导航信号弱怎么解决(荣耀7x不能导航了是怎么回事)

  • vivoy93怎么整全屏(vivoy93手机怎么打开全屏模式)

    vivoy93怎么整全屏(vivoy93手机怎么打开全屏模式)

  • 华为鸿蒙HarmonyOS JavaUI 框架官网文档内容更新:组件开发指南、补充组件开发说明(华为鸿蒙harmonyos刷机)

    华为鸿蒙HarmonyOS JavaUI 框架官网文档内容更新:组件开发指南、补充组件开发说明(华为鸿蒙harmonyos刷机)

  • Dedecms v5.3没有下载地址的解决方法(dedecms安装及配置)

    Dedecms v5.3没有下载地址的解决方法(dedecms安装及配置)

  • 小规模纳税人征税规定
  • 营业执照丢失公示几天才能注销
  • 未取得发票的费用,在汇算清缴中按利润计算吗
  • 个税申报错了并且已经缴款怎么修改
  • 季末资产总额填错了要紧吗
  • 实收资本印花税税率
  • 房地产开发企业预收款预缴增值税
  • 当月发票未收到怎么办
  • 赞助支出为什么不算广告费
  • 企业购买汽车时需要交哪些税?
  • 进项税额转出是什么意思
  • 汇算清缴补交的税怎么做凭证
  • 公司账户电子承兑
  • 补提以前年度税费应该计入
  • 企业可以采用在产品按固定成本计算法的有
  • 企业收到退款应该如何做会计处理?
  • 远程清卡失败怎么办
  • 未办土地有偿使用手续多少钱
  • 进项票怎么做
  • 如何在excel中链接图片对方能显示
  • 如何能屏蔽自动扣费服务
  • linux怎么翻译
  • 事业编党费如何核算
  • 费用已付款跨年怎么做账
  • bellzee.exe是什么
  • 辅助生产成本的交互分配法
  • PQIMountSvc.exe是什么进程 PQIMountSvc进程查询
  • php mysql数据库
  • php的运算符主要包括哪些?
  • PHP:mcrypt_list_modes()的用法_Mcrypt函数
  • 赞助收入税务处理
  • php二维数组求和
  • 母公司收取子公司利润交增值税吗
  • 公司注销后虚开能查吗
  • 新购车辆检测费计入原值吗
  • 库存商品转在建工程 增值税
  • react的高阶组件理解
  • php mysql pdo
  • php ajax 实现
  • 累计减除费用多还是少好
  • 前端必学课程
  • 存货毁损计入什么科目
  • 土地增值税扣除率怎样计算
  • 工程合同主要看什么
  • sql查看日志
  • 取得税务师证书申请社保补贴
  • 增量留抵税额怎么算
  • 销售货物的收入计入什么科目
  • 小规模纳税人购进商品会计分录
  • 股权转让的印花税税率是多少
  • 会计中的材料采购是什么意思
  • 公司人事怎么办公积金提取流程?
  • 中标违约保证金怎么退
  • 赔偿给别人的钱还能要回来吗
  • 增值税其他免税销售额
  • 施工方怎么开发票
  • 互联网代记账业务
  • 内部资金管理实施细则
  • sql server的基本概念
  • mysql5.6 创建用户
  • win8.1补丁包
  • 安装metpy
  • 联想lenovo小新pro16 2023版
  • usbmonit.exe - usbmonit是什么进程
  • 通过修改注册表修改edge主页
  • win10预览版绿屏重启解决
  • slee401.exe - slee401是什么进程 有什么用
  • shell中数组如何定义
  • css ul显示为表格
  • centos7如何分区
  • node.js网站
  • java list转set的方法
  • js 修改 css
  • js实现文字闪烁的方法
  • 公司在异地办公
  • 生产企业税点
  • 河南省教育厅纪检组举报电话
  • 国家税务总局公告2022年第9号
  • oecd国家是什么意思
  • 税率的分类有哪些
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设