服务端系统里的 Agent Runtime

最近顺着 AI Agent 这条线做了一轮梳理。

起点是 ReAct 论文,后面一路看到了 CoT、Tool Calling、MCP、Skill、LangChain、LangGraph、多 Agent 架构,以及最近常被提到的 AgentOps。

这篇不是教程,也不是路线图,更像是一份面向服务端开发者的读书笔记。

我想回答的是一个更朴素的问题:

如果一个服务端开发者想理解 Agent 开发,大概可以从哪些概念开始看?这些概念和已有的工程经验之间,能不能建立一些对应关系?

看下来以后,我的感受是:Agent 开发里确实有很多新东西,但也没有完全脱离传统软件工程。

比如函数注册、协议设计、状态管理、异常处理、权限控制、日志追踪、服务治理,这些词换一个上下文,仍然能解释 Agent 系统里的很多问题。

区别在于,系统里多了一个会推理、会选择工具、但输出不完全稳定的组件。

这篇文章就沿着这个观察,把几个常见概念串起来。

1. ReAct 不是前端 React

这里的 ReAct 不是前端框架 React,而是 Reason + Act。

它来自 LLM Agent 领域,核心思想是:不要让模型一次性回答问题,而是让模型在“思考、行动、观察、再思考”的闭环里解决问题。

一个典型的 ReAct 循环大概是:

1
2
3
4
5
6
7
Thought: 我需要先查什么?
Action: Search[...]
Observation: 外部环境返回了什么?
Thought: 基于新信息,我下一步该做什么?
Action: Lookup[...]
Observation: 又得到一条信息
Final Answer: 给出最终答案

它的关键价值不是代码多复杂,而是把 Agent 的基本工作循环标准化了:

1
Thought -> Action -> Observation -> Thought -> ... -> Final Answer
图 1:ReAct 最小闭环

模型不再只是一次性生成文本,而是在外部环境反馈中逐步逼近答案。

论文里的几个实验场景也很典型。

HotpotQA 和 FEVER 偏知识推理、检索、事实验证。ALFWorld 和 WebShop 偏环境交互、决策、行动。

这说明 ReAct 想解决的不是“让模型回答得更像人”,而是让模型具备一种最小的行动闭环:先判断,再动作,拿到反馈后继续判断。

2. CoT 是想,ReAct 是边想边做

CoT,也就是 Chain-of-Thought,解决的是“让模型产生中间推理过程”的问题。

原始 CoT 是一种 prompting 技术:在 prompt 里给模型几个带推理过程的示例,让模型模仿这种推理格式。

后来 reasoning model 出现后,CoT 更像模型内部的计算过程。用户不一定能看到完整推理链,但模型会在内部做更多中间计算。

ReAct 和 CoT 的关系可以这样看:

1
2
CoT: Thought -> Thought -> Thought -> Answer
ReAct: Thought -> Action -> Observation -> Thought -> Action -> Observation -> Answer

CoT 让模型想清楚。

ReAct 让模型边想边做,并把外部世界的反馈纳入下一步判断。

这一步很重要。

因为现实业务里的很多问题,不是靠模型“脑内想一想”就能解决的。它必须查数据库、调接口、看日志、读文档、比对状态、执行动作。

Agent 之所以成为 Agent,不是因为它会说话,而是因为它能在一个受控环境里行动。

3. Agent 调工具,可以拆成普通调用链路

第一次看到 Agent 调工具,很多人会觉得神奇:

模型怎么知道要调用哪个函数?

又怎么知道参数应该怎么传?

但从服务端视角拆开看,它仍然是一条可以理解的调用链路。

模型本身不会真正执行工具。它只是根据你提供的工具描述和参数 schema,生成一个结构化意图:

1
2
我要调用 query_order
参数是 {"order_id": "123"}

实际执行函数的是 Agent Runtime,也就是你的代码或框架。

完整链路大概是:

1
2
3
4
5
6
7
用户问题
-> 模型生成 tool call
-> Runtime 解析工具名和参数
-> 从 Tool Registry 找到真实函数
-> 执行函数
-> 把函数结果作为 Observation 写回上下文
-> 模型继续推理或给出最终答案
图 2:Agent 调工具的运行时结构

伪代码也很直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while True:
output = llm(messages, tools=tool_schemas)

if output.final_answer:
return output.final_answer

for call in output.tool_calls:
tool_name = call.name
tool_args = call.arguments
tool_result = tool_registry[tool_name](**tool_args)

messages.append({
"role": "tool",
"name": tool_name,
"content": tool_result,
})

所以,一个最小 Agent Runtime 本质上就是几件事:

  • 函数注册表
  • 工具描述
  • Tool Call 解析器
  • 调用执行器
  • 上下文维护器

模型负责判断“下一步该调用什么”。

代码负责执行真实动作。

这就是 Agent 调工具的本质。

4. Function Calling 解决的是协议稳定性

早期 ReAct 常见的是文本协议:

1
2
Action: query_order
Action Input: {"order_id": "123"}

这种方式能跑,但有一个问题:不稳定。

模型可能多写一句解释,可能少写一个字段,可能输出不合法 JSON。只要格式漂移,解析器就会出问题。

Function Calling / Tool Calling 做的事情,是把这一步升级成模型 API 原生支持的结构化协议。

开发者用 JSON Schema 描述工具,模型返回结构化 tool call,而不是随便写一段文本。

它的核心价值不是让模型真的调用函数。

模型仍然不执行函数。真正执行函数的仍然是 Runtime。

Function Calling 的价值在于,让模型用稳定、机器可读、schema 约束的方式表达:

1
我现在要调用哪个工具,以及参数是什么。

所以 ReAct 和 Function Calling 不是替代关系。

它们解决的是两个层面的问题:

  • ReAct 解决“什么时候思考,什么时候行动”
  • Function Calling 解决“行动指令如何稳定表达”

一个是推理-行动范式,一个是工具调用接口协议。

图 3:ReAct 与 Function Calling 的层级关系

5. LangChain、LangGraph 和多 Agent

如果只是手写一个最小 ReAct Agent,100 到 200 行 Python 就能跑起来。

但一旦进入试生产,真正消耗时间的通常不是 agent loop,而是这些问题:

  • 工具粒度怎么设计
  • 工具 schema 怎么定义
  • Observation 怎么压缩
  • 失败后怎么重试
  • 执行多少步应该停止
  • 哪些操作需要人工确认
  • 每次运行怎么记录 trace
  • 如何构建评测集

这也是 LangChain 和 LangGraph 的价值所在。

LangChain 更像 AI 应用工具箱,提供模型集成、Prompt Template、Tool、Retriever、Memory、Output Parser 等组件。

LangGraph 更像有状态图执行引擎,适合复杂流程、多 Agent 协作、条件分支、循环推理、人工审批、中断恢复和长任务。

一句话概括:

1
2
LangChain 提供组件。
LangGraph 负责编排。

至于多 Agent,不应该一开始就上。

更稳妥的演进路线是:

1
2
3
4
单 Agent + 多 Tools
-> 复杂后拆 Specialist Agent
-> 需要协作时加 Supervisor / Router
-> 高频稳定流程沉淀为 Workflow / Graph
图 4:从单 Agent 到多 Agent / Workflow 的演进路线

Tool 是能力原子。

Agent 是任务专家。

Supervisor 是任务调度器。

Workflow 是确定性流程。

服务端开发者应该很熟悉这个判断:不是所有逻辑都应该拆成微服务,也不是所有流程都应该交给模型动态决策。

稳定、高频、规则明确的流程,更适合 workflow。

开放、不稳定、需要判断的问题,才适合交给 Agent。

6. Tool、MCP、Skill 怎么理解

这几个概念也很容易混在一起。

可以这样区分:

  • Tool:Agent 运行时可调用的函数、API 或动作
  • MCP:把外部工具、资源、Prompt 标准化暴露给 Agent 的协议
  • Skill:把某类任务的方法论、流程、规范、资源和脚本打包成能力包

一个粗略类比:

1
2
3
4
Tool 是可执行函数。
MCP 是接入协议。
Skill 是操作手册和能力包。
Agent 是读手册并决定怎么执行的人。

所以 Skill 不一定等于 Tool。

一个 Skill 可能只是告诉 Agent 应该按什么流程做事,也可能附带脚本、模板和资源。

对工程师来说,可以把 Tool 看作函数,把 MCP 看作标准化接口层,把 Skill 看作可复用的任务上下文包。

7. 从服务端开发视角看,需要补哪些拼图

如果从服务端开发的视角看 Agent 开发,有一部分能力是可以迁移的。

比如:

  • API 设计
  • 异步任务
  • 状态管理
  • 权限控制
  • 日志追踪
  • 错误处理
  • 数据建模
  • 服务治理
  • 工程部署

但也有一些新问题需要单独补课。

我目前看到的主要是这些:

  • 怎么把业务任务拆成 Agent 可以执行的结构
  • 怎么设计稳定的 Tool schema
  • 怎么控制上下文和 Observation
  • 怎么建设 eval set
  • 怎么记录 trace,并用 trace 调试行为
  • 怎么判断哪些流程该 workflow 化,哪些可以交给 Agent 判断
  • 怎么设计权限、审批、幂等和回滚
  • 怎么控制成本、延迟和模型 fallback

这些问题不一定都要在第一天解决。

但如果一个 Agent 真要进入业务系统,它们迟早会出现。

这也是我觉得服务端开发经验有用的地方。

服务端工程本来就经常要处理不稳定的外部依赖、复杂的业务状态和不可控的用户输入。

只不过这一次,被接入系统的外部能力变成了 LLM 和一组可被模型选择的工具。

8. AgentOps:我目前看到的生产化问题

看 Agent 系统的生产化讨论时,经常会遇到 AgentOps 这个词。

它还没有像 DevOps、MLOps 那样完全标准化。我的理解是,它现在更像一组正在成型的工程实践。

当 Agent 不只是回答问题,而是开始检索数据、调用工具、执行动作、影响业务系统时,你就不能再把它当成一个普通接口。

你需要知道它每一步做了什么,为什么这么做,调用了什么工具,花了多少钱,哪里失败了,能不能回滚,下次怎么避免同类错误。

所以我会先把 AgentOps 理解成:

面向生产 Agent 的运行、观测、评测和治理能力。

图 5:AgentOps 六层控制面

它不是单个工具,也不是简单的日志平台,而是一组围绕 Agent 生命周期的工程能力。

第一层是配置控制。

Prompt、Tool、Skill、模型选择、路由策略、权限策略,都不应该散落在代码里。

它们应该可配置、可版本化、可灰度、可回滚。

否则每一次改 prompt、改工具描述、换模型、调路由规则,都要重新发版,迭代会非常重。

第二层是运行观测。

传统服务只看日志、指标、链路追踪还不够。

Agent 需要的是 Agent Observability。

一次完整运行里,至少要记录这些东西:

  • 用户输入
  • 被选中的 prompt 和版本
  • 模型请求和响应
  • 每一次工具调用
  • 工具入参和返回
  • Observation 如何进入上下文
  • 中间状态如何变化
  • 最终输出
  • 人工修正
  • 业务结果
  • token、成本、延迟和错误

普通日志只能告诉你“系统有没有报错”。

Agent trace 要回答的是:“这个 Agent 为什么会这样做”。

第三层是评测体系。

没有 eval,就没有 Agent 迭代。

因为 Agent 的失败经常不是代码异常,而是“看起来成功,实际上做错了”。

比如工具调用成功了,但选错了工具;回答生成了,但漏掉关键约束;流程跑完了,但业务结果不合格。

这类问题不能只靠线上反馈,也不能靠人工偶尔看几条日志。

你需要把真实任务沉淀成 golden tasks,把失败案例沉淀成 regression set。每次改 prompt、换模型、改 tool schema、调整 workflow,都要跑一遍评测。

对 Agent 来说,eval 就是 CI。

第四层是发布治理。

传统代码发布有灰度、回滚、监控、告警。

Agent 也需要类似机制,而且要更细。

因为 Agent 的变化不只来自代码,还来自 prompt、模型版本、工具返回、知识库内容、上下文策略和路由策略。

成熟一点的发布流程应该包括:

  • shadow test:先旁路运行,不影响真实结果
  • canary:只放给小比例流量
  • A/B test:比较不同 prompt、模型或 workflow
  • rollback:质量下降时快速切回上一版本
  • freeze:出现风险时冻结高危工具或写操作
  • human approval:敏感动作必须人工确认
图 6:AgentOps 生命周期

第五层是权限和安全。

Agent 一旦能调用工具,就不只是“生成文本的模型”。

它变成了一个有行动能力的系统参与者。

所以工具权限必须最小化,写操作必须受控,高危动作必须审批,敏感数据必须脱敏,所有关键动作必须可审计。

这部分不能靠 prompt 里的“请你谨慎操作”解决。

它应该由系统权限、工具网关、审批流和审计日志来保证。

第六层是多模型和成本控制。

生产里的模型供应商、价格、延迟、稳定性都会变化。

成熟系统最好抽象出 Model Gateway / Model Router,支持 primary model、fallback model、timeout fallback、error fallback、cost-based routing、task-based routing、A/B test、shadow test 和灰度切换。

不是所有任务都需要最强模型。

简单分类、格式转换、信息抽取,可以走便宜模型;复杂推理、关键决策、长上下文分析,再走更强模型。

这不是省钱小技巧,而是生产系统的稳定性设计。

所以我不太把 AgentOps 当成一个新名词看。

它更像是在提醒我们:Agent 不是一次性交付的功能,而是一套需要持续观察、评估、约束、回滚和迭代的概率系统。

如果说 ReAct 解决的是 Agent 的最小行动闭环,那么 AgentOps 关注的是 Agent 进入生产之后的长期运行问题。

9. 总结

如果用工程师能理解的话说,Agent 不一定要被理解成一个全新的工程门类。

它更像是在传统业务系统里接入了一个会推理、会选择工具、但输出不完全稳定的决策组件。

围绕这个组件,服务端开发者可以先关注这些问题:

  • 用 Tool schema 约束动作
  • 用 Runtime 执行真实函数
  • 用 Workflow 固化稳定流程
  • 用 Trace 记录运行过程
  • 用 Eval 衡量效果
  • 用权限和审批控制风险
  • 用 fallback 管住成本和稳定性

这也是我目前理解 AI Agent 开发的一条线索:

先理解 ReAct 的思考-行动闭环,再理解工具调用的运行时机制,最后把它落到工程系统的稳定性、可控性和可持续迭代能力上。

报警响起,但是…?

日常我们的KA会上传菜单,其中自然包含很多菜品的图片 ,某天,后天检测到大量的图片拉取失败。

随便找到一张图片的URL,在服务器上进行请求,出现如下错误。

1
curl: (60) SSL certificate problem: certificate has expired

然而使用 MacBook 本地 curl 是成功的,在chrome浏览器也实验成功。

观察可以发现,在服务器上 ping 对方 cdn 域名,和在mac 本地 ping 对方 cdn 域名,返回的 IP 是不同的,难道是 cdn 问题?

image.png

image.png

那假如我们在 MacBook 修改 Hosts文件强行将解析指向 IP 呢? 也是成功的。

image.png

有的同学认为,SSL证书验证,和客户端没有关系,一定是服务端的问题,这种观点正确吗 ?

回顾 https ,也许是因为它?

我们想象一下,如果我们自己去设计 https ,在验证一个我们不认识的站点的证书时,大概是要依赖一个权威机构的给我们的”名单”,一旦这个”名单”不再准确时,就会发生误判。而事实也的确如此。

SSL证书验证的流程是 :

image.png

在3.过程中,客户端验证服务器的证书,检查:

  • 证书是否由受信任的 CA 签发
  • 证书是否在有效期内
  • 证书上的公钥是否与服务器的主机名匹配

而为了完成这些验证,客户端需要有一个预安装的受信任的根证书列表。这些根证书由证书颁发机构(CA)签发,CA 是负责验证和颁发数字证书的机构。

由于不同的操作系统和浏览器在其信任存储中维护各自的根证书列表,我们在使用不同的客户端访问相同的https资源时,会得到不同的验证结果。

举个例子:如果我们手头有事多年前的Chrome浏览器,它大概率是不能访问目前很多合法的https网络服务的,因为他的根证书列表太老了,不包含目前很多流行的根证书。

image.png

难道说,我们这次也遇到了这个问题?

快速止血

快速恢复的方案大概有三个:

  1. 使用 InsecureSkipVerify ,跳过 SSL 验证;
  2. 更新系统的CA证书列表;
  3. 不更新系统的CA证书列表而是在 golang 代码中指定 CA 证书列表。

对比方案优劣,第二个方案被我们首先放弃,更新系统CA证书是一个系统级操作,其影响面是更大的,且运维操作的步骤也非常繁琐。

使用方案三,如果我们可以找到可信的CA证书列表,方案三更适宜作为长期方案,但寻找可信CA证书列表以及进行代码开发部署都不是短时间内最好的选择。

幸运的是,之前的开发同学由于担忧第三方图床技术良莠不齐,已经在代码中实现了通过 apollo 配置控制指定域名的图床使用InsecureSkipVerify 的功能。通过修改配置我们实现快速止血。

回到现场

为了探究问题的根源,并且得到长期的解决方案,我们继续对这个图床进行研究。

(此处将使用与当时同样使用了Let’s Encrypt证书的另一网站继续演示)

image.png

image.png

如此巧合,我们要访问的网站,最近更新了SSL证书,使用了 Let’s Encrypt 颁发的证书,这家 CA 频发兼容性问题。

在Let’s Encrypt官网,一份公告说明最近确实发生了些什么。

image.png

使用Chrome查看该图床证书的详细信息,可以看到其证书CA确实是ISRG Root X1。

image.png

不同的软件会使用CA证书列表进行SSL验证,比如 curl 使用 nss, 而 wget 使用 openssl,我们运行在服务器上的 golang 服务 http.Client 默认使用系统 CA 证书来验证 SSL 证书。

那么我们的服务器到底对 ISRG Root X1 兼容行如何?

我们使用的 docker 镜像基于 CentOS7.9,实际是一个非常老的版本,其内置的 curl wget 以及 ca-certificates 版本都很陈旧,对于较新的CA机构,兼容性比较差。

下面的图片分别展示了curl wget ca-certificates 版本依赖的 nss openssl 版本。

image.png

image.png

image.png

在 Let’ Encrypt 官网的兼容性文档中,明确了支持 ISRG Root X1 的各种平台版本,其中 NSS > 3.26, 明显我们的镜像不满足,对于 RHEL >= 6.10, 7.4 (with updates applied), 8+ 这一项,我们进入后可以看到

Platforms that trust ISRG Root X1

openssl 1.0x版本不支持 ISRG Root X1 的 case,在 stackOverFlow medium askUbuntu Github 等技术网站也有无数的吐槽。

到此,可以得出结论,我们的服务器 golang 服务,wget curl,均不支持 ISRG Root X1。

引申阅读

苹果大幅缩短安全证书有效期引发众怒

突发事件!Apple Music等核心服务因SSL/TLS证书过期发生中断

总结

本篇通过一个案例,我们回顾了下 https 的验证流程,了解了工具的异同、软件版本的差异,带来的SSL验证结果的不同。

软件工程、网络工程发展至今,有了很多规范和广泛被应用的基础建设,然而这些都是人的创造物,没有100%的正确率。我们大概率不是 0 day 的发现者,但是一根绳可能以 n 种方式绊倒不同的人。

经验教训

  • 在使用第三方服务时,要充分考虑证书兼容性问题
  • 系统基础设施要保持更新,避免使用过于陈旧的版本
  • 在开发时预留降级和配置开关,方便应急处理

  • /vendor/es-client/elasticsearch/src/Elasticsearch/Connections/Connection.php:169
    此处将调用ringPHP的curlHandler

此处将调用ringPHP的curlHandler
https://ringphp.readthedocs.io/en/latest/client_handlers.html

根据ringPHP文档,我们可以在此处设法传递我们想要的curl配置
那么,在自己封装框架内,适合的地方开放一个可设置curl参数的接口即可为es定义一个某集群请求全局的超时控制

ringPHP

  • es-client/elasticsearch/src/Elasticsearch/Client.php:936
    Client.php
    Client.php
    Client.php
    Client.php

遇到的问题

编译安装 php7.2 时,configure 显示找不到 zlib

解决方法

1
installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /

执行这条命令后反馈安装失败,重新执行 configure 仍找不到 zlib

先执行

1
rm -f /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

再执行

1
xcode-select --install

再执行一次

1
installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /

成功

背景

同时使用 shadowsocks(r)/v2raycharles 时,charles map remote 功能工作不正常。

解决方案

  1. 手机/mac 关闭 shadowsocks(r)/v2ray, 相当于去掉这两个软件设置的系统代理

  2. 打开 charles, 确认 enable transparent HTTP proxying/ macOS proxy 功能打开

  3. 确认手机wifi-高级-代理 或mac 网络偏好设置-高级-代理 相关配置正常,如自动代理配置、网页代理、安全网页代理

php创建守护进程代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
// 一次fork
$pid = pcntl_fork();
if ( $pid < 0 ) {
exit( ' fork error. ' );
} else if( $pid > 0 ) {
exit( ' parent process. ' );
}
// 将当前子进程提升会会话组组长 这是至关重要的一步
if ( ! posix_setsid() ) {
exit( ' setsid error. ' );
}
// 二次fork
$pid = pcntl_fork();
if( $pid < 0 ){
exit( ' fork error. ' );
} else if( $pid > 0 ) {
exit( ' parent process. ' );
}

// 真正的逻辑代码们 下面仅仅写个循环以示例
for( $i = 1 ; $i <= 100 ; $i++ ){
sleep( 1 );
file_put_contents( 'daemon.log', $i, FILE_APPEND );
}
?>

两次fork的作用

第一次fork

确保当前进程不是 session leader, session leader 调用 setsid 不会产生新的 session,下面是 gnu 对 setsidcontrolling session 关系的解释,子进程在调用 setsid 之后成为了一个新 sessionleader 并且不再与之前的 controlling session 关联。

One of the attributes of a process is its controlling terminal. Child processes created with fork inherit the controlling terminal from their parent process. In this way, all the processes in a session inherit the controlling terminal from the session leader. A session leader that has control of a terminal is called the controlling process of that terminal.

You generally do not need to worry about the exact mechanism used to allocate a controlling terminal to a session, since it is done for you by the system when you log in.

An individual process disconnects from its controlling terminal when it calls setsid to become the leader of a new session. See Process Group Functions.

cmux 是什么

cmux is a generic Go library to multiplex connections based on their payload. Using cmux, you can serve gRPC, SSH, HTTPS, HTTP, Go RPC, and pretty much any other protocol on the same TCP listener.

cmux 使用过程中的问题

现象

我使用一个tcp链接,同时支持 websocket, http, grpc 协议,发现 websocket 服务总是握手失败,而且每次都会打一条 http 请求的日志。

分析:

websocket 协议握手过程客户端主要是在 http header 加入 Upgrade: websocket,服务端使用 cmux 也是通过匹配这条http header 实现。

出现问题的 golang 代码如下:

1
2
3
4
5
6
7
8
9
10
11
tcpm := cmux.New(l)
grpc1 := tcpm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldPrefixSendSettings("content-type", "application/grpc"))
http1 := tcpm.Match(cmux.HTTP1Fast())
http2 := tcpm.Match(cmux.HTTP2())
wsl := tcpm.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))

//开始服务
go serveGPRC(grpc1)
go serveWS(wsl)
go serveHTTP(http1)
go serveHTTP(http2)

对比 cmux 库自带的 example 没有很大的区别, 但是通过注释我们可以注意到, Match 的顺序很重要

仔细对比之后发现的确两段 Match 的顺序有所不同,无论是 grpc 请求还是 websocket 的握手请求,都只是特殊化的 http 请求,使用 cmux 时要注意 Match 的顺序,避免服务端将 grpc 请求和 websocket 的握手请求当做 http2 http1 请求处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
m := cmux.New(l)

// We first match the connection against HTTP2 fields. If matched, the
// connection will be sent through the "grpcl" listener.
grpcl := m.Match(cmux.HTTP2HeaderFieldPrefix("content-type", "application/grpc"))
//Otherwise, we match it againts a websocket upgrade request.
wsl := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))

// Otherwise, we match it againts HTTP1 methods. If matched,
// it is sent through the "httpl" listener.
httpl := m.Match(cmux.HTTP1Fast())
// If not matched by HTTP, we assume it is an RPC connection.
rpcl := m.Match(cmux.Any())

// Then we used the muxed listeners.
go serveGRPC(grpcl)
go serveWS(wsl)
go serveHTTP(httpl)
go serveRPC(rpcl)

mbp 外接显示器开启HiDPI

1. 首先,打开系统分区权限:

查看SIP状态

在终端中输入csrutil status,就可以看到是enabled还是disabled。

关闭SIP

  • 重启MAC,按住cmd+R直到屏幕上出现苹果的标志和进度条,进入Recovery模式;
  • 在屏幕最上方的工具栏找到实用工具(左数第3个),打开终端,输入:csrutil disable;
  • 关掉终端,重启mac;
  • 重启以后可以在终端中查看状态确认。
阅读全文 »
0%