多浏览器同步测试工具的设计与实现

在做Web兼容测试时,测试人员往往需要在不同浏览器上重复执行相同的操作。

现有自动化录制手段,其实是后置的对比,效率与反馈都存在延迟,执行过程相对是黑盒的,过程中如果测试人员没细化到具体的校验点,即使是很明显的样式差异,脚本也很难发现。且如果是脚本或浏览器差异的问题,自动化运行的方式并不能够及时手动调整容错。

于是便思考有没有一种实时操作,而且可以便捷校验方案。

通过调研了browsersync、uirecorder等工具后,我设计了如下的同步兼容测试工具。

架构设计

系统主要由四个区块组成:

  • Web 前端:显示可用浏览器,触发同步操作任务,展示 VNC 连接。

  • 服务端:提供数据接口给前端,调用本地脚本,构建任务执行环境。

  • 同步驱动(自己起了个名字:yutu):下图中小兔子那部分,一个可单独调用的 npm 库,提供全局命令执行同步操作任务。

  • 浏览器池:我使用的是基于 selenoid 的本地容器管理系统,当然也可以换成别的,看自己需求。

总体来说前端和服务端的工作量是比较小的,只要关注任务创建和 vnc 连接展示即可。

系统的核心在于驱动层,它是每个任务同步操作、对比的中心。而浏览器池采用 selenium-gird 或 solenoid 都是可以的,选择适合维护的即可。

图片

功能点

  • 云浏览器版本管理池

  • 多浏览器操作同步

  • 操作脚本录制,日志记录

  • 元素图片相似度对比

实现效果

操作同步过程中,可以实时看到从浏览器执行情况,也可以通过列表状态颜色来判断。

图片

图片

同步出错的浏览器,执行过程中可以手动进去确认下是不是问题

图片

图片

图像相似度对比,可以自定义允许的差异值。主要是方便测试人员快速识别差异的位置,辅助人工判断。

图片

图片

驱动设计

开发背景

本系统的核心是同步驱动,这里我且称之为yutu, 它是在uirecorder项目上,经深度自定义开发而来,如果你查看它源码,不难发现很多 uirecorder 的影子。起初本打算结合browsersync的侵入式脚本实现操作同步功能,但在建设f2etest版本的浏览器云项目时遇到了它,官方版本主要用来做操作录制的,其中有个本地实时对比校验的附属功能,这正合我意,开始撸它源码。

起初在本地chrome上一切顺利,但接入selenoid浏览器池后,开始对接firefox时发现了问题。uirecorder的核心jwebdriver不支持最新的 W3C 协议,而且从钉钉群里官方反馈情况来看,这个项目 2 年没更新了,多半是夭折了。没办法自己从头撸吧,于是采用最新的WebdriverIO客户端结合自身的需求,对其进行了深度的改造,从而有了yutu,在此也感谢下前人努力与开源。

数据流程

图片

功能简介

它主要是一个命令行工具,通过 sudo npm i -g yutu-tools 全局安装到系统中。

其中它的图片对比能力是来自于graphicsmagick,因此还需要额外安装下
mac:brew install graphicsmagick

yutu 对外主要提供以下两个命令行功能:

  • yutu init:初始化任务目录及配置文件

  • yutu start:执行任务并输出日志

效果展示

所以其实从上面的设计图不难发现,它本身就是个独立的工具,可以不依赖于整体系统来使用,是否调用远程浏览器是可以通过config.json来配置的,如果是serverIp是127.0.0.1那么就会调用本地的chromedriver来操作(本地其它浏览器调用功能在开发设计中),开发过程中,本地调试时可以方便快速定位驱动问题。

图片

图片

主要改动点
命令行使用

原先的工程采用的是本地控制台交互问答式的参数配置方式,这肯定不适合我们平台化嵌入,于是我扩展了命令行对于参数的支持:

 
  1. program

  2.    .version(pkg.version)

  3.    .option('-l, --lang <en|zh-cn|zh-tw>', 'change language' )

  4.    .option('--no-color', 'disable colors')

  5.    .option('-m --mobile', 'mobile mode')

  6.    .option('-d --debug', 'debug mode')

  7.    .option('-r --raw', 'save raw cmds')

  8.    .option('--default', 'open checker browser and set 1024x768 by default')

  9.    .option('--device [value]', 'set mobile device name')

  10.    .option('--sync [value]', 'set sync browsers')

  11.    .option('--hub_url [value]', 'hub server url')

  12.    .option('--hub_port [value]', 'hub server port')

  13.    .option('--server_ip [value]', 'sync server ip')

  14.    .option('--main_client [value]', 'main control browser')

  15.    .option('--browser_size [value]', 'browser screen size')

  16.    .option('--http_proxy [value]', 'docker container http proxy')

  17.    .option('--default_url [value]', 'default url')

使用示例:

$ yutu init --hub_url=192.168.0.101 --hub_port=4444 --server_ip=192.168.0.101 --main_client=chrome:100.0 --sync=internet_explorer:11
自定义驱动

在用WebdriverIO替换掉jwebdriver后,原先的很多 api 都改变了,这需要我们对 driver 对象进行深度的包装改造,于是我在本地增加了个 browser 对象,用来代理WebdriverIO的 driver 对象,在其中增加我们需要的 driver 扩展能力。

这里需要用到 nodejs 的 Proxy 机制、链式调用、Promise 等写法。(感谢前端同事龙哥)

 
  1. class mBrowser {

  2. constructor(browser) {

  3. const handers = {

  4. get(obj, key){

  5. return key in obj?obj[key]: browser[key]

  6. }

  7. }

  8. return new Proxy(this, handers);

  9. }...

浏览器插件

yutu 能够将用户操作回传给 socket server 的关键是依靠一个浏览器插件,它只会在启动主控制浏览器时,通过goog:chromeOptions参数将插件以文件数据流传给 chrome 浏览器,因此我们的主控浏览器默认也必须是 chrome。这个插件本体还是uirecorder的,只做了对接yutu的适应的调整。

 
  1. var crxPath = path.resolve(__dirname, '../tool/uirecorder.crx');var extContent = fs.readFileSync(crxPath).toString('base64');capabilities["goog:chromeOptions"] = {

  2. args: ['--disable-bundled-ppapi-flash'],

  3. prefs: {

  4. 'plugins.plugins_disabled': ['Adobe Flash Player']

  5. },

  6. excludeSwitches: ['enable-automation'],

  7. extensions: [extContent],};

在我的需求里,插件中还有很多需要优化的地方,后面有空慢慢搞吧,目前改动的主要是插件启动页面接收参数、动态服务器 ip 等:

 
  1. if (mapParams.defaultUrl && txtUrl){

  2. txtUrl.value = decodeURIComponent(mapParams.defaultUrl);

  3. }

 
  1. function connectServer(data){

  2. const {ip, port} = data

  3. console.log('data', data);

  4. if(!wsSocket){

  5. wsSocket = new WebSocket('ws://'+ ip + ':' + port, "protocolOne");...

修改插件的 js 后,需要重新打包生成插件 crx。

$ ./buildcrx.sh

前端设计

这个系统前端部分主要是 2 个页面:

  • 浏览器列表

  • 同步操作页面(加几个弹层 Modal)

图片

因为考虑到客户实际使用的是以 windows 为主,为了保证测试结果的准确性,所以我们这里浏览器运行镜像主要是自定义封装的 windows 系统镜像(太痛苦了,此处包含泪水,详见下文解读)。

在代码方面,继续秉承组件化思想,结合 antd pro 的高阶组件,对多处进行了抽象复用。

import { ProCard, ProTable } from '@ant-design/pro-components';

操作部分,结合了较为小众但稳定可靠的react-vnc库,同时为了降低用户的浏览器资源消耗,操作页面在同步浏览器列表展开时才会进行 vnc 连接展示。

 
  1. <Sider width={'20%'} collapsible collapsed={collapsed} onCollapse={collapsed => this.setState({collapsed})}> <Card size="small" title={!collapsed? "同步浏览器列表": '同步'} >

  2. {syncBrowsers && syncBrowsers.length > 0? (

  3. syncBrowsers.map(item => ( <Card.Grid key={item.sessionId} className={styles.syncContainer}>

  4. {!collapsed ? ( <VncScreen

  5. url={item.vncUrl}

  6. rfbOptions={{

  7. credentials: {

  8. password: 'selenoid',

  9. },

  10. }}

  11. scaleViewport

  12. background='#000000'

  13. style={{

  14. height: '100%'

  15. }}

  16. />

  17. ): ( <div className={styles.browserName}><img src={`/${item?.browserName}.react.svg`} alt='' />{item.browserName}</div>

  18. )} <div ref={n => (this[`hover_${item.sessionId}`] = n)} className={styles.hoverContainer}> <SyncModal data={item} actions={this.state.syncActions[item.sessionId]} />

  19. </div>

  20. </Card.Grid>

  21. ))

  22. ): ( <Empty />

  23. )} </Card></Sider>

服务端设计

服务端主要是提供数据给前端展示,以及启动脚本调用系统命令的,接口部分千篇一律,增删改查而已,不展开介绍了。

这里有个小细节,yutu本身不会和数据库已经服务端进行交互的,所以它的任务运行状态,需要告知脚本是个麻烦事。我是通过脚本监控单个任务进程的控制台信息,来达成的,这样的成本最小,也不必让 2 个工具过度耦合。

 
  1. async def start_task(self):

  2. logger.info(f'Task {self.task_id} is starting...')

  3. run_cmd = f'yutu case.spec.js' \ f' --browser_size={self.task_info["screen"]}' \ f' --http_proxy={self.task_info["proxy"]}' \ f' --default_url={self.task_info["url"]}'

  4. logger.info(f'start cmd: {run_cmd} ')

  5. p = subprocess.Popen(

  6. run_cmd,

  7. shell=True,

  8. stdout=subprocess.PIPE,

  9. stderr=subprocess.STDOUT,

  10. encoding='utf-8',

  11. cwd=self.task_dir_path

  12. )

  13. for i in iter(p.stdout.readline, 'utf-8'):

  14. if 'consoleParams:' in i:

  15. try:

  16. data = json.loads(i.replace('consoleParams:', ''))

  17. singal = await self.update_task_info(data)

  18. print(data)

  19. if not singal:

  20. break

  21. except Exception as e:

  22. print(e)async def update_task_info(self, data):

  23. if not data:

  24. return

  25. row = BrcSyncTask.query.filter_by(id=self.task_id).first()

  26. if data['type'] == 'server':

  27. row.sync_server_ip = data['serverAddress']

  28. row.sync_server_port = data['serverPort']

  29. db.session.commit()

  30. elif data['type'] == 'main':

  31. row.main_session_id = data['sessionId']

  32. db.session.commit()

  33. elif data['type'] == 'sync':

  34. info = json.loads(row.sync_sessions) if row.sync_sessions else {}

  35. info[data['browserInfo']] = data['sessionId']

  36. row.sync_sessions = json.dumps(info)

  37. db.session.commit()

  38. elif data['type'] == 'signal':

  39. if data['status'] == 'ready':

  40. self.update_task_status(5) # 开始同步 if data['status'] == 'end':

  41. self.update_task_status(3) # 同步结束 db.session.flush()

  42. return False

  43. return True

自定义镜像封装

此处主要介绍本地封装 windows 版本的 selenoid 浏览器镜像的心得,懂得都懂,就不详细展开介绍了。
为什么要封装 windows 镜像,有 2 个原因。

  • 还原用户场景:用户用的是 windows 系统,官方提供的 liunx 版本浏览器镜像不能代表实际的浏览器使用场景。

  • 方便环境隔离:直接调用单个主机的 webdriver 的方式,无法实现同一时间不同用户的远程操作和代理隔离(f2etest 的 webdriver 云方案无法远程操作)。

关于 windows 封装的基础教程可以参考:windows-images

不过按照教程走下去后会发现,有可能你的容器能启动,但死活连不上浏览器 driver。

再通过反复试验后,我采用了 selenoid+selenoid 的方式,才让流程通起来。

容器内的selenoid服务

关键在于在浏览器和 driver 都安装后,再在 windows 里启动一个 selenoid 服务,让它来提供 4444 端口服务给外部的 selenoid hub 调用,由它来和容器内的浏览器 driver 进行交互。

为了方便复用,我在基础镜像中就加入这个基础工具包,文件目录如下:

图片

start.bat 是一个封装后的执行文件,参数可以根据自己设备性能调整,内容如下:

C:\selenoid-windows\selenoid_windows_386.exe -conf C:\selenoid-windows\browsers.json -disable-docker -limit 4 -service-startup-timeout 240s -session-attempt-timeout 240s -session-delete-timeout 240s -timeout 240s > C:\selenoid-windows\selenoid.log 2>&1

browsers.json 如下

 
  1. {

  2. "MicrosoftEdge":{

  3. "default":"18",

  4. "versions":{

  5. "18":{

  6. "image":["C:\\selenoid-windows\\webdrivers\\msedgedriver.exe","--host=127.0.0.1","--verbose"]

  7. }

  8. }

  9. }}

容器内的flask服务

看上面的工具包内容可以看到,我们还在里面起了个 flask 轻量服务,它的作用是接收外部传过来的配置参数,动态设置当前容器中的分辨率和系统代理。这个问题是 windows 镜像特有的,selenoid 官方团队说解决不了,为此也做过解释,
windows starts 1024x768 resolution even SCREEN_RESOLUTION changed to 1920x1080x24

我贡献的这个方法可以曲线解决这个问题,步骤也很简单,

  • 1.在基础镜像中安装个 python(2、3 随意)

  • 2.安装工具包中依赖(关键是 flask、pywin32、winproxy 这几个库)

  • 3.容器退出保存前,启动 flask、selenoid 服务

flask 中的内容如下:

 
  1. from flask import Flaskfrom flask import requestfrom flask import jsonifyimport win32apifrom winproxy import ProxySettingapp = Flask(__name__)def setProxy(host, port):

  2. proxy = ProxySetting()

  3. proxy.enable = True

  4. proxy.server = f"{host}:{port}"

  5. proxy.override = ["127.*","192.168.*","10.*"]

  6. proxy.registry_write()def setScreen(width, height):

  7. dm = win32api.EnumDisplaySettings(None, 0)

  8. dm.PelsWidth = int(width)

  9. dm.PelsHeight = int(height)

  10. dm.BitsPerPel = 32

  11. dm.DisplayFixedOutput = 0

  12. win32api.ChangeDisplaySettings(dm, 0)@app.route('/setDisplay', methods=['GET'])def index():

  13. height = request.args.get('height')

  14. width = request.args.get('width')

  15. host = request.args.get('host')

  16. port = request.args.get('port')

  17. setScreen(width, height)

  18. if host and port:

  19. setProxy(host, port)

  20. return jsonify({'width': width, 'height': height})if __name__ == '__main__':

  21. app.run(host='127.0.0.1', port=5000)

由于这个服务是起在容器里的,因此我们可以在 yutu 的 driver 建立后,通过固定的 url 来调用,而不必维护容器的 ip 和网络情况。

 
  1. if (configJson.webdriver.host !== '127.0.0.1'){

  2. const setScreenUrl = `http://127.0.0.1:5000/setDisplay?width=${width}&height=${height}&host=${hostname}&port=${port}`

  3. await driver.url(setScreenUrl);}

开发过程中的坑

项目真正开发到完成,投入 1.5 人/月左右,按照时间顺序来回顾这过程中的坑吧。

  • 同步浏览器从一个改成 N 个后,浏览器关闭异常。
    结合异步转同步的方式,改造关闭浏览器方法,保证执行有序

     
    1. async function closeBrowser (){if(syncCheckBrowserDrivers){

    2. for (var browserIndex in syncCheckBrowserDrivers){

    3. var browser = syncCheckBrowserDrivers[browserIndex];

    4. await new Promise((resolve) => {

    5. const {browserName, browserVersion } = browser;

    6. browser.close(() => {

    7. console.log(`${__('checker_browser_closed')}${browserName}${browserVersion}`.green);

    8. resolve();

    9. })

    10. })

    11. }}}

  • 原先的 webdriver 客户端不支持 w3c 协议,导致新浏览器无法正常同步操作
    这是我万万没想到的,虽然知道前端技术日新月异,用外部老的工具,肯定会有需要兼容的差异,但没想到 ali 老大哥们也摆烂了,也导致了我在中后期几乎重写了大部分的 yutu 中的同步操作方法。

  • windows 镜像的产出后,运行不起来
    一开始并没有明确定义 windows 支持对于我们系统的重要性,因此一直在以 liunx 版本的浏览器作为最小试验对象,但后期流程通后,要交付时才发现没有以用户实际的使用场景为目标,这样即使交付也没啥用处。
    因此开始硬啃,为了突破这个技术难点,我的工作机被刷成 ubuntu 系统,好不容易产出了 windows 镜像后,又发现镜像运行很艰难,我的小破机根本带不动。

    图片

终于在领导的关照下,搞个高配的开发机器,运行流畅。

  • windows 系统分辨率超出,操作界面显示不全的问题
    原先的 selenoid 系统,是通过传给 qemu 分辨率参数来设置系统环境变量的。但 windows 版本并没有这样的能力,老外给的方案是默认启动时就给最大的分辨率,在通过设置浏览器窗口大小来实现指定大小分辨率的测试执行,最起码这样显示内容是全的。但这对于我这种可能需要用户手动去 vnc 操作的方式来说,并不适用,用户一旦不小心点了最大化,那么同步浏览器的比例就完全乱套了。因此结合自己 python 脚本经验,深度挖掘了上述更为灵活的 flask server 方案。

  • windows 系统代理无法通过环境变量设置
    原先的 selenoid,我只要在启动容器时给个 env 变量就可以指定代理了,但 windows 版本这样不行。有了上面的分辨率解决经验,我通过 python 的 winproxy 库进行了处理。

  • 非主流浏览器借用的别人的内核,缺少浏览器驱动
    这里的非主流浏览器其实不光指的是我们的一众换皮国产浏览器,国外的 opera、yandex 等等其实也是换皮 chromium。这些家伙的兼容才是真的坑,我到现在还没填完


    下面就 qqbrowser 举个例子吧:


    它的内核是 chrome 94 的,在启动时要传的浏览器名称 “chrome”,而我们系统中本身也可能有 chrome 这个版本。因此要区分开,我是自定义一个版本区间给它,如:chrome 1~11,同样在 yutu 中要做别名区分,启动时传 chrome,记录时要记 qq。

     
    1. {"chrome":{

    2.    "default":"94",

    3.    "versions":{

    4.        "94":{

    5.            "image":"windows/chrome:80",

    6.            "port":"4444",

    7.            "path":"/wd/hub"

    8.        },

    9.        "11":{

    10.            "image":"windows/qq:11",

    11.            "port":"4444",

    12.            "path":"/wd/hub"

    13.        }

    14.    }}}

    ari': 'safari','internet explorer': 'internet explorer',}capabilities['browserName'] = browserNameToDriver[options.browserName]

结语

项目目标算是达成了,但还不够完美,我会持续的优化。

通过此次的开发经历,也使我感触良多,技术类需求的不确定性,是软件行业的特性。以后对接公司工作中技术需求,我也要引以为戒,做好风险管理。

同时看到一个个曾经的明星项目的沉寂,也是让我百感交集,他们本该能够成长的更好,但或是公司环境的变化,或是创作者乏力无奈,总之慢慢淡出人们的记忆,甚至连创作团队自己都忘记,而我们就在这不断创造与消亡中轮回。

最后,借用尼采的警言与各位共勉:所有美好的事物都是曲折地接近自己的目标,一切笔直都是骗人的,所有真理都是弯曲的,时间本身就是一个圆圈。

  感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

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

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

相关文章

Google Recaptcha V2 简单使用

最新的版本是v3&#xff0c;但是一直习惯用v2&#xff0c;就记录一下v2 的简单用法&#xff0c;以免将来忘记了 首先在这里注册你域名&#xff0c;如果是本机可以直接直接填 localhost 或127.0.0.1 https://www.google.com/recaptcha/about/ 这是列子 网站密钥&#xff1a;是…

【初识Linux】

寻不到花的折翼枯叶蝶&#xff0c;永远也看不见凋谢............................................................................. 文章目录 前言 一、【基本指令】 1、ls 2、pwd 3、cd 4. touch 5.mkdir 6.rmdir 7、rm 8.man 9.cp 10、mv 11、cat 12、tac 13、more 14、le…

操作系统知识要点

一.操作系统的特性 1.并发性 在多道程序环境下&#xff0c;并发性是指在一段时间内&#xff0c;宏观上有多个程序同时运行&#xff0c;但实际上在单CPU的运行环境&#xff0c;每一个时刻只有一个程序在执行。 因此&#xff0c;从微观上来说&#xff0c;各个程序是交替、轮流…

jenkins搭建及流水线配置

1.安装docker curl https://mirrors.aliyun.com/repo/Centos-7.repo >> CentOS-Base-Aliyun.repomv CentOS-Base-Aliyun.repo /etc/yum.repos.d/yum -y install yum-utils device-mapper-persistent-data lvm2yum-config-manager --add-repo http://mirrors.aliyun.com/…

混沌接口压测利器Fortio:从TCP/UDP到gRPC,全方位覆盖云原生应用性能测试

#作者&#xff1a; 西门吹雪 文章目录 Fortio 安装docker 安装:MacOS安装&#xff1a;linux安装:对于http负载生成最重要的标志:Fortio server 功能 TCPUDPgRPC负载测试gRPC 负载测试在k8s或者容器中使用fortio进行压测fortio 直接在docker中作为sidecar使用 Fortio是一个微服…

【笔记】数据结构与算法

参考链接&#xff1a;数据结构(全) 参考链接&#xff1a;数据结构与算法学习笔记 一些PPT的整理&#xff0c;思路很不错&#xff0c;主要是理解角度吧&#xff0c;自己干啃书的时候结合一下会比较不错 0.总论 1.数据 注&#xff1a;图是一种数据结构&#xff01;&#xff01;…

leetcode - 684. 冗余连接

684. 冗余连接 解决思路 大致上的思路就是将元素加入到 并查集 中&#xff0c;那么在遍历到边的时候先去判断的边的两个端点的 根节点 是否相等&#xff0c;如果相等&#xff0c;那么就代表此刻把这条边加上去就形成了环【可以这么理解&#xff0c;如果形成了环&#xff0c;那…

【力扣打卡系列】二叉树·灵活运用递归

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day16 相同的树 题目描述 解题思路 边界条件&#xff0c;其中一个节点为空&#xff0c;return 只有p和q均为空才返回true&#xff0c;因此可以简写为pqreturn&#xff0c;先判断节点值是否一样&…

创建一个基于SSM框架的药品商超管理系统

创建一个基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的药品商超管理系统是一个涉及多个步骤的过程。以下是一个详细的开发指南&#xff0c;包括项目结构、数据库设计、配置文件、Mapper接口、Service层、Controller层和前端页面的示例。 1. 需求分析 明…

二十七、Python基础语法(面向对象-上)

面向对象&#xff08;oop&#xff09;&#xff1a;是一种程序设计的方法&#xff0c;面向对象关注的是结果。 一、类和对象 类和对象&#xff1a;是面向对象编程中非常重要的两个概念。 类&#xff1a;具有相同特征或者行为的一类事物&#xff08;指多个&#xff09;的统称&…

UML图之对象图详解

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 零、什么是对象图 对象图&#xff08;Object Diagram&#xff09;是UML中一种重要的静态结构图&#xff0c;它用于表示在特定时间点上系统中的对…

同三维T80004EHH-4K30W 4K超清HDMI编解码器

1路HDMI输入1路3.5音频输入&#xff0c;1路HDMI输出1路3.5音频输出&#xff0c;1个USB1个TF卡槽&#xff0c;带RS485 支持4K30&#xff0c;支持2路解码2路转码&#xff0c;可选配WEBRTC/NDI协议&#xff0c;可选配硬件WEBRTC解码&#xff0c;编码、解码、转码、导播、录制多功…

设计一个灵活的RPC架构

RPC架构 RPC本质上就是一个远程调用&#xff0c;需要通过网络来传输数据。传输协议可以有多种选择&#xff0c;但考虑到可靠性&#xff0c;一般默认采用TCP协议。为了屏蔽网络传输的复杂性&#xff0c;需要封装一个单独的数据传输模块用来收发二进制数据&#xff0c;这个单独模…

网络安全入门学习路线 怎样科学的进行网络安全学习

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

“中信同业+”焕新升级 锚定数字金融新主线,做实金融“五篇大文章”

9月20日&#xff0c;“中信同业”升级发布会及生物多样性债券指数发布在京顺利举办&#xff0c;此次活动以“做强数字金融 服务实体经济”为主题&#xff0c;由中信金控指导&#xff0c;中信银行主办&#xff0c;中信各金融子公司联合承办。来自银行、证券、保险、基金等行业百…

ELK之路第四步——整合!打通任督二脉

ELK之路第四步——整合&#xff01;打通任督二脉 前言1.架构2.下载资源3.整合开始1.分别启动三个es2.启动kibana3.新建filebeat_logstash.yml配置文件4.修改logstash的启动配置文件5.启动logstash6.启动filebeat7.Kibana查看 4.结语 前言 在开始本篇之前&#xff0c;你需要用到…

[JAVAEE] 多线程的案例(四) - 定时器

目录 一. 什么是定时器? 二. java中的定时器类 三. 定时器的简单使用. 四. 模拟实现定时器 4.1 实现 MyTimerTask 4.2 实现 MyTimer 一. 什么是定时器? 定时器相当于闹钟, 时间到了就执行一些逻辑. 二. java中的定时器类 使用Timer类实例化一个定时器对象. Timer类中的…

江协科技STM32学习- P21 ADC模数转换器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

OpenAI放弃自制AI芯片!伦敦场开发者日Hugging Face工程师现场“拷问”Sam Altman

OpenAI 2024年开发者大会第二场&#xff08;伦敦场&#xff09;刚结束。 10月初在旧金山举办了第一场 OpenAI 开发者大会&#xff0c;但这次没有像上一场放出很多花活&#xff0c;这次开始走剧透局了&#xff01; 现场的OpenAI 产品主管 曝光了 GPT o1即将更新的功能&#xf…

浅谈网络安全

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 网络安全是确保信息系统、网络及数据免受未授权访问、使用、披露、篡改或破坏的重要领域。随着数字化进程的加速和互联网的普及&#xff0c;网络安全的重要性显著提升。今天&#xff0c;网络威胁呈现出多样化…