跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

深入浅出ASN.1:数据描述界的“通用语言”

在计算机通信的浩瀚世界里,不同系统、不同编程语言编写的程序之间要想顺畅地交换数据,必须达成一个基本共识:数据以何种结构存在,又以何种形式在网络上传输。ASN.1正是为了解决这一核心问题而诞生的国际标准。它不依赖任何特定编程语言或硬件平台,却默默支撑着我们每天使用的网络通信、移动互联和安全加密体系。

一、什么是ASN.1?——不只是数据的“语法书”

ASN.1的全称是Abstract Syntax Notation One,即“抽象语法标记一”。从名字上可以拆解为三个关键词:

  • 抽象:它只描述数据的逻辑结构(比如“这个字段是整数,那个字段是字符串”),而不关心这些数据在具体某台计算机内存中如何存放(是大端还是小端,是4字节还是8字节)。
  • 语法:它提供了一套严谨的记号法,用来书写数据类型和结构定义,类似于编程语言中的类型声明。
  • 标记(Notation):它本身就是一种专门的描述语言,用来编写数据结构的“蓝图”。

因此,ASN.1本质上是一种数据描述语言,或者说是一套元语言——它被用来定义数据格式,而非处理数据。打个比方:如果数据是一栋建筑,那么ASN.1就是建筑师绘制的设计图纸;图纸本身不是房子,但所有施工方都必须按这张图纸来建造和验收。

ASN.1的诞生背景与标准化历程

ASN.1的历史可以追溯到1984年,由国际电信联盟(ITU-T)和国际标准化组织(ISO)联合制定为国际标准。其最初的驱动力来自于OSI(开放系统互连)参考模型的推动——那时不同厂商的网络设备和管理系统急需一种独立于平台的方式来表达和交换管理信息。随后几十年间,ASN.1持续演进,不断扩充新类型和功能,目前最新的标准主要涵盖在ITU-T X.680系列建议书中。

二、ASN.1的核心设计思想:两阶段模型

理解ASN.1最关键的入口,在于它采用了“抽象语法 + 传输语法”相分离的两层架构。这也正是它区别于JSON、XML等数据格式的根本之处。

1. 抽象语法层:定义数据的“逻辑结构”

在这一层,我们使用ASN.1的记法来定义数据类型和数据单元。ASN.1提供了丰富的内置基础类型:

  • 基本类型:BOOLEAN(布尔)、INTEGER(任意精度的整数)、OCTET STRING(字节串)、BIT STRING(比特串)、IA5String(ASCII字符串)、UTF8String(Unicode字符串)等。
  • 组合/结构类型:SEQUENCE(类似于C语言的结构体,由固定顺序的字段组成)、SEQUENCE OF(类似于数组或列表,由同一类型的多个元素组成)、CHOICE(类似于C语言的联合体,从多个候选中选择其一)。
  • 其他高级类型:ENUMERATED(枚举)、SET(无序集合,类似SEQUENCE但顺序不重要)、SET OF(无序集合列表)等。

此外,ASN.1还允许通过SUB-TYPE进行值范围约束,比如定义“一个介于1到100之间的整数”,大大增强了描述的精确性。

举例:假设我们要描述一个人的基本信息,可以用ASN.1这样定义:

PersonInfo ::= SEQUENCE {
    name      UTF8String (SIZE(1..64)),
    age       INTEGER (0..150),
    gender    ENUMERATED { male(0), female(1), other(2) },
    emails    SEQUENCE OF UTF8String
}

这段定义清晰阐述了:一个人包含姓名(长度1-64的UTF-8字符串)、年龄(0到150的整数)、性别(枚举值)以及一个由多个电子邮件字符串组成的列表。

2. 传输语法层:编码规则决定“字节形式”

定义好抽象结构后,下一步是将实际数据值转换成0和1组成的字节序列,以便存储或网络传输。这个转换过程由编码规则(Encoding Rules) 完成。ASN.1的一个独特优势在于:它不绑定死某一种编码方式,而是允许同一份抽象定义使用多种不同的编码规则,以适应不同场景需求。

最核心的编码规则主要有以下几种:

  • BER(基本编码规则,Basic Encoding Rules):采用TLV(Type-Length-Value,类型-长度-值)三元组的自描述编码格式。每个数据单元都包含自己的类型标记、长度信息和实际值。优点是解码器不需要预先知道数据结构也能部分解析,通用性强;但相应的,冗余信息较多,编码后体积偏大。
  • DER(可辨别编码规则,Distinguished Encoding Rules):可以理解为BER的一个严格约束子集。它通过对长度、布尔值、字符串等细节的编码做出唯一性规定,使得任意一个数据值只有唯一一种合法的DER编码。这个特性在密码学应用中至关重要——因为数字签名和证书指纹要求对数据进行哈希运算,如果同一份数据可以有多种不同编码,哈希结果就会不一致,签名就会失效。因此,X.509数字证书、PKCS系列加密标准、JSON Web Token(JWT)的某些签名结构等都强制使用DER。
  • PER(压缩编码规则,Packed Encoding Rules):为了在窄带宽、低功耗场景(如移动通信、物联网)下高效传输而设计。PER会充分利用ASN.1定义中的约束信息(如值的上下限、是否可选等),对字段进行位级别的压缩,尽量省去冗余的类型和长度标记。编码结果极其紧凑,但解码器必须事先知道完整的ASN.1定义才能正确解析。3G/4G/5G核心网的控制面协议即大量采用PER。
  • 其他编码规则:还有适用于流式处理的XER(XML编码规则,将数据编码为XML文本)、适用于JSON的JER(JSON编码规则)等,但它们在实际工业应用中相对小众。

三、编码规则深度拆解:从字节到位

光讲概念是不够的。要真正理解编码规则,最直接的方式是拿一个具体的数据,一步步看它怎么变成二进制字节,甚至到比特级别。

我们从一个最简单的ASN.1定义开始:

UserID ::= INTEGER (0..65535)

然后我们要把数字 2026 这个值编码并传输。

3.1 BER(基本编码规则)——最标准的TLV方式

BER的核心就是 TLV(Type-Length-Value) ,每个数据都变成三段:

  • T(Type):告诉你“这是什么类型”
  • L(Length):告诉你“Value部分有多长”
  • V(Value):真正的数据

对 2026 编码的完整过程

第1步:确定Type(类型标识)

ASN.1的INTEGER类型,通用标签是 0x02(一个字节)。

第2步:确定Length(长度)

2026 转成十六进制是多少?

  • 2026 ÷ 16 = 126 余 10(A)
  • 126 ÷ 16 = 7 余 14(E)
  • 7 ÷ 16 = 0 余 7
    所以十六进制是 0x07EA,占 2个字节。

Length字段就写 0x02。

第3步:确定Value(实际值)

就是大端字节序的两个字节:0x07EA。

最终BER编码就是三个字节拼起来:

02 02 07 EA
字节含义
02类型 = INTEGER
02长度 = 2字节
07 EA实际值 = 2026

这就是BER最基础的编码方式。任何接收方只要读第一个字节就知道后面是什么类型,读第二个字节就知道要取多少字节作为值,完全不依赖上下文。

注意:如果是长度超过127字节,Length字段会用“长格式”——第一个字节最高位设为1,低7位表示后续几个字节用来表示长度。例如长度是300字节,会编码为 82 01 2C(0x82表示用2个字节表示长度,0x012C=300)。但上面例子长度只有2,所以用短格式直接写 02。

3.2 DER(可辨别编码规则)——BER的“唯一化”版本

DER的编码结果看起来和BER几乎一样,但它在很多细节上做了强制规定,确保同一个值只有一种合法的DER编码。

依然用 2026 和同一个定义 INTEGER (0..65535):

同样编码为 02 02 07 EA

那它和BER区别在哪?区别不在这个简单例子,而在以下情况:

场景BER允许DER强制要求
长度表示长度小于128时,可用短格式也可用长格式必须用短格式
整数前导零00 07 EA 或 07 EA 都合法必须最短形式,即去掉前导零
BOOLEAN真值任何非零字节都算真必须 0xFF

所以BER可能把 2026 编码成 02 03 00 07 EA(加了个前导零),DER则只允许 02 02 07 EA。

为什么DER这么苛刻?
因为数字签名是计算数据的哈希值。如果同一份数据可能有多种合法编码,那么签名方和验签方得到的哈希就可能不同,签名就失败了。DER的唯一性保证了“数据”和“字节串”一一对应,这是PKI证书、SSL/TLS等安全协议的基石。

3.3 PER(压缩编码规则)——位级别的极致压缩

PER的目标是省带宽。它不会傻傻地发 02 02 07 EA 这么“啰嗦”的字节,而是利用定义中的约束信息直接压缩。

我们的定义带了约束:INTEGER (0..65535)。

PER的编码逻辑

第1步:确定值域范围
0..65535 一共有 65536 个取值,正好需要 16个比特(2的16次方 = 65536)来表示。

第2步:直接对齐到字节边界进行编码
2026 的二进制是 00000111 11101010,PER直接把它打包成2个字节:

07 EA

等等,那Type和Length去哪了?

全被省略了!

  • 没有Type字段:因为PER解码器必须在编译时就知道这个位置是INTEGER类型(由ASN.1定义提供)。
  • 没有Length字段:因为约束 0..65535 已经固定了值占16位,解码器直接读16位就好,不需要额外告知长度。

同样的约束,BER需要3字节,PER只需要2字节——省了33%。

3.4 同一数据、五种编码的全景对比

为了让读者能一目了然地看出不同编码规则的区别,下面用 UserID = 2026 这个简单例子做一个完整对比:

编码规则编码结果占用字节一句话解释
BER02 02 07 EA3类型-长度-值,自描述
DER02 02 07 EA3与BER在此例相同,但这是唯一合法格式
PER(UNALIGNED)07 EA2利用约束直接省掉类型和长度
XER(XML)<UserID>2026</UserID>约17人类可读的XML文本
JER(JSON){"UserID":2026}约16人类可读的JSON文本

这个表的作用:读者用5秒钟就能建立起“不同编码规则有何区别”的直观认知。

3.5 复杂结构的编码:SEQUENCE + OCTET STRING

为了让编码拆解更贴近真实场景,我们看一个包含结构体的例子:

定义:

Person ::= SEQUENCE {
    id   INTEGER (0..255),
    name OCTET STRING (SIZE(4))
}

实际数据:id = 30,name = "Alex"(ASCII码 41 6C 65 78)

BER/DER编码(两者在此一致)

编码成一个嵌套的TLV结构,整体为:

30 09 02 01 1E 04 04 41 6C 65 78

逐字节拆解如下:

字节位置BER字节含义
第1字节30SEQUENCE类型标记
第2字节09SEQUENCE总长度=9字节
第3字节02INTEGER类型标记
第4字节01INTEGER长度=1字节
第5字节1E值30(0x1E)
第6字节04OCTET STRING类型标记
第7字节04字符串长度=4字节
第8-11字节41 6C 65 78ASCII “Alex”

PER编码

约束 id (0..255) 占8比特,name固定长度4字节,直接顺序打包:

1E 41 6C 65 78

1E 是id(30),后面直接跟4字节name。没有类型标记,没有长度标记,总共 5字节,而BER需要 9字节——压缩了约44%。

3.6 PER进阶:CHOICE和SEQUENCE OF的逐位编码

当数据结构包含CHOICE或SEQUENCE OF时,PER的编码会更复杂一些,但也更能体现其压缩设计的精妙。这部分内容属于进阶阅读,供对二进制细节感兴趣的读者参考。

3.6.1 CHOICE的PER编码——永远多一个“选择位”

定义:

Result ::= CHOICE {
    success  INTEGER (0..255),
    error    ENUMERATED { low(1), high(2), fatal(3) }
}

CHOICE的关键问题是:解码器怎么知道当前传的是success还是error?

PER的解决方案:在值之前,先编码一个选择索引(choice index)。

  • 该CHOICE有2个选项,索引范围 0~1,需要 ceil(log2(2)) = 1 个比特。
  • 定义中第一个选项success索引为0,第二个error索引为1。

场景A:传 success = 30

编码步骤:

  1. 选择索引 = 0,占1个比特 → 0
  2. success的INTEGER约束0..255占8个比特 → 00011110(30的二进制)
  3. 拼接:0 + 00011110 = 00011110(1位+8位=9位)

由于UNALIGNED PER是按位打包的,9位可能不占整字节。如果前面有其他字段凑在一起,这9位就紧挨着放,不补0。

场景B:传 error = high(2)

  1. 选择索引 = 1,占1个比特 → 1
  2. error是ENUMERATED,值域{1,2,3},用2个比特表示(00→1,01→2,10→3)。high对应索引1 → 01
  3. 拼接:1 + 01 = 101(3个比特)

关键点:选择索引永远放在最前面,解码器先读这1个比特,就知道后面如何解析。

3.6.2 SEQUENCE OF的PER编码——先告诉你有几个元素

定义:

ScoreList ::= SEQUENCE OF INTEGER (0..100)

SEQUENCE OF是变长数组,解码器必须知道数组长度。

PER的解决方案:先编码一个长度前缀(length determinant)。

  • 0..100 的值域有101个取值,需要 ceil(log2(101)) = 7 个比特。
  • 如果数组有5个元素,长度前缀 = 4(因为PER的索引从0开始,表示“4个元素” = 实际5个)。

具体例子:传 [30, 45, 78](3个元素)

  1. 实际元素数=3,长度前缀 = 2,占7个比特 → 0000010
  2. 然后依次编码每个INTEGER(每个占7个比特,因为0~100需要7位):
  • 30 → 0011110
  • 45 → 0101101
  • 78 → 1001110
  1. 整体比特流:0000010 0011110 0101101 1001110(共 7 + 3×7 = 28 个比特)

四、XER和JER:当ASN.1遇到文本格式

文本格式的编码规则虽然体积大,但在调试、日志记录和Web API对接等场景中有着不可替代的价值。

4.1 XER(XML编码规则)——把ASN.1变成XML

XER的目标很明确:让人能看懂,让XML解析器能处理。

沿用之前的 UserID 例子,值为 2026:

XER编码结果(默认格式):

<UserID>2026</UserID>

类型名作为XML标签,值作为文本内容。

对于复杂结构 Person(id=30,name=”Alex”):

<Person>
    <id>30</id>
    <name>QVhleA==</name>
</Person>

注意到name变成了QVhleA==——这是因为OCTET STRING在XER默认模式下会被Base64编码,保证原始字节(不一定都是可打印字符)在XML文本中的合法性。

XER的变体:

  • BASIC-XER:默认格式,如上所示。
  • EXTENDED-XER:增加标准化命名空间和属性。
  • CUSTOM-XER:允许开发者自定义标签名。

4.2 JER(JSON编码规则)——ASN.1跟JSON握手

JER是较新的标准(ITU-T X.697),核心思想:把ASN.1数据转成JSON,让JavaScript/Web生态无障碍消费。

UserID = 2026 的JER编码:

{"UserID": 2026}

Person 的JER编码:

{
    "Person": {
        "id": 30,
        "name": "QWxleA=="
    }
}

JER的关键映射规则:

ASN.1类型JSON表示
SEQUENCEJSON对象
SEQUENCE OFJSON数组
CHOICEJSON对象带选择器字段
OPTIONAL字段未设置时在JSON对象中省略该键
OCTET STRING/BIT STRINGBase64编码字符串
超大INTEGER(>2^53)以字符串形式表示

五、EXTENSIBILITY(扩展性)——如何在不破坏兼容性的前提下演进协议

ASN.1的扩展性机制是它能在通信协议中存活几十年的核心原因。它的设计目标是:老版本解码器能安全跳过它不认识的新字段,新版本解码器能兼容老版本发来的旧格式消息。

5.1 扩展标记(...)

在 SEQUENCE 或 CHOICE 的末尾加上 ...,就表示“这里预留扩展点”。

Message ::= SEQUENCE {
    msgId      INTEGER (0..31),
    payload    CHOICE {
        ack       NULL,
        data      OCTET STRING (SIZE(1..8)),
        ...  -- 这里允许未来增加新的payload类型
    },
    ...  -- 这里允许未来增加新的SEQUENCE字段
}

5.2 扩展在PER中的编码行为

PER在处理带扩展的CHOICE时,会改变选择索引的编码方式:

场景选择索引编码
CHOICE 无扩展索引从0开始,用最少比特表示所有选项
CHOICE 有扩展将选项分为“根选项”和“扩展选项”,在最高位加一个 1-bit扩展标志

举例:

Color ::= CHOICE {
    red    NULL,
    blue   NULL,
    ...,
    yellow NULL,
    green  NULL
}
  • 根选项(扩展点之前):red(0), blue(1) → 共2个
  • 扩展选项(扩展点之后):yellow, green → 共2个

编码方式:

  • 如果是 red 或 blue:先输出 0(表示“不是扩展”),然后用1个比特表示根索引(0或1)。
  • 如果是 yellow 或 green:先输出 1(表示“是扩展”),然后用1个比特表示扩展索引(0或1)。

关键好处:如果未来增加了第3个扩展选项 purple,老解码器收到 purple 时,会看到扩展标志=1,然后读扩展索引,但它不需要知道 purple 具体是什么,只需要根据扩展项的编码规则跳过其值即可,不会报错。

5.3 SEQUENCE的扩展编码

Message ::= SEQUENCE {
    msgId      INTEGER (0..31),
    payload    CHOICE { ... },
    ...,
    %% 扩展字段从这里开始
    priority   INTEGER (0..7) OPTIONAL
}

PER编码SEQUENCE时,如果带扩展:

  1. 首先编码一个 1-bit extension flag:如果有任何一个扩展字段被设置,该位为1;否则为0。
  2. 如果扩展标志=1,后面会紧跟一个位图(bitmap),每个扩展字段占1位,表示该字段是否存在。
  3. 然后按顺序编码所有存在的扩展字段的值。

兼容性保证:

  • 老解码器(不认识priority)读到扩展标志=1,知道后面有扩展数据,直接跳过整个扩展块,继续解析后续根字段。
  • 新解码器收到老版本消息(扩展标志=0),正常解析根字段,扩展字段用默认值填充。

5.4 扩展性的代价

代价说明
编码效率下降增加了扩展标志和位图,PER的压缩率略微降低
解码复杂度上升解码器需要处理版本判断和跳过逻辑
设计时需要预留协议定稿后,扩展点只能往后加,不能在中间插入

六、ASN.1在实际世界中的关键应用

ASN.1并非藏于深闺的理论标准,相反,它广泛嵌入在我们每天依赖的基础技术设施之中。

1. 数字证书与公钥基础设施(PKI)

当我们访问任意一个HTTPS加密网站时,浏览器会验证服务器提供的X.509数字证书。这个证书正是用ASN.1来定义其标准结构的(定义于RFC 5280),并且在传输和签名时使用DER编码。证书中的主体名、颁发者、公钥信息、有效期、扩展字段等,均被组织为复杂的ASN.1 SEQUENCE和SET结构。

2. 移动通信网络(3G/4G/5G核心网)

移动通信网络中,基站与核心网网元之间频繁交换信令控制消息,这些消息的数据量巨大且对实时性要求极高。3GPP标准在这些场景中大量采用ASN.1来描述协议数据单元(PDU),并强制使用PER编码规则进行编码,以在有限的无线频谱资源上实现高吞吐、低延迟的传输。

3. 企业级网络管理协议(SNMP)

SNMP(简单网络管理协议)是网络设备(路由器、交换机、服务器)统一监控和管理的事实标准。其管理信息库(MIB)中的变量和消息结构,均使用ASN.1的SMI(管理信息结构)子集来定义;SNMP报文本身则使用BER编码进行传输。

4. 其他领域

  • LDAP目录访问协议使用ASN.1定义目录项和操作。
  • 智能卡和密码令牌(如PKCS#11标准)中广泛使用ASN.1描述密钥和证书对象。
  • 航空航天和军事通信系统中的部分链路协议也依赖ASN.1,以保证不同武器平台间的数据互通。

七、ASN.1的现代生态:工具链与编程支持

虽然ASN.1本身是一门描述语言,但工程实践中几乎无人手工去编码/解码二进制数据。标准的工作流程是:

  1. 用ASN.1语法编写.asn定义文件。
  2. 使用专门的ASN.1编译器(如开源项目asn1c,商业工具OSS ASN.1,或各厂商自研的编译器)读取该定义文件,自动生成对应编程语言(C/C++、Java、Python、Go等)的编解码源代码。
  3. 开发者在自己的应用程序中调用这些生成好的函数,即可完成对ASN.1数据的序列化和反序列化。

当前主流的ASN.1工具链已经成熟,且对PER、DER等复杂编码规则提供完整支持。一些语言(如Python的pyasn1库)也提供动态解析能力,无需预编译即可处理ASN.1结构。

八、五种编码规则的全景对比与选型建议

编码规则输出格式大小可读性是否需要预知ASN.1定义典型场景
BER二进制(TLV)中等❌部分可(TLV自描述)SNMP、LDAP
DER二进制(TLV,唯一)中等❌部分可数字证书、签名、PKI
PER二进制(位压缩)最小❌完全需要(编译时已知)3G/4G/5G、物联网、卫星通信
XERXML文本最大✅不需要(标签自描述)调试、日志、XML生态对接
JERJSON文本较大✅不需要(键名自描述)RESTful API、微服务、Web前端

实战选型建议

  • 如果你在写网络协议栈,且带宽是硬约束 → 用PER(UNALIGNED),配合静态ASN.1编译器生成C代码。
  • 如果你在处理PKI、证书、加密 → 强制用DER,确保签名可验证。
  • 如果你在调试或做日志记录 → 用XER,人眼直接看。
  • 如果你在做REST API,前后端分离 → 用JER,前端JS天然消费JSON。
  • 如果你需要兼容老旧SNMP设备 → 只能选BER。

九、ASN.1的挑战与时代定位

ASN.1并非没有缺点。它的学习曲线相对陡峭,语法比起JSON或XML更为晦涩;其二进制编码(尤其是DER和PER)不具备可读性,调试时需借助专用解析工具;且在不同的ASN.1实现之间,偶尔存在对标准细节的诠释差异,导致互操作性问题。

然而,ASN.1并非试图取代JSON或XML这类轻量级文本格式,而是定位在不同的场景层次上。当数据规模庞大、传输资源受限、或安全性与确定性要求极高时,ASN.1尤其是PER/DER组合,仍具有不可替代的技术优势。它像一个沉默寡言的通信老手——低调、严谨、高效,在幕后为全球互联网和移动通信的稳健运行提供着坚实支撑。

在这个数据交换格式日新月异的时代,ASN.1已经走过了超过四十年的历程,至今仍在通信协议栈的底层和安全架构的核心层面发挥着关键作用。了解ASN.1,不仅有助于深入理解计算机网络协议的底层设计逻辑,也能让我们在面临高要求通信系统的技术选型时,多一份深思熟虑的底气。


延伸阅读与进阶方向:

如果你对ASN.1的工程实践还想继续深入,以下几个方向可以作为下一步的探索路径:

  1. ASN.1编译器实战:用开源asn1c生成C代码,编写真正的编解码demo。
  2. BER/DER的TLV嵌套深度解析:带SEQUENCE OF SEQUENCE的情况下,长度字段如何计算,如何避免解析漏洞。
  3. ASN.1与Protocol Buffers / FlatBuffers的横向对比:从编解码速度、数据大小、版本兼容性、工具链成熟度四个维度对比,辅助技术选型。
  4. ASN.1在5G NR中的实际应用:拿一个真实的5G RRC消息定义,拆解其PER编码后的二进制报文。
作者

老丹

关注我
其他文章
上一个

椭圆曲线密码学的两大支柱:Weierstrass曲线与Edwards曲线全面解析

下一个

ONVIF标准:构建IP安防产业的通用语言

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • Linux系统的安全基石:深入理解可插拔认证模块(PAM) 2026年7月27日
  • vsftpd 完全指南:从核心原理到Docker容器化部署 2026年7月27日
  • 互联网的”导航”安全卫士:深入解读DNSSEC 2026年7月27日
  • Ubuntu DNS 配置完全指南 2026年7月27日
  • 在 Ubuntu 中使用 Certbot 的操作指南 2026年7月27日

文章分类

  • C/C++开发 (13)
  • Docker容器 (3)
  • Linux工具包 (10)
  • Linux服务配置 (33)
  • Linux系统 (10)
  • OpenWrt路由 (2)
  • Shell脚本 (3)
  • 安防技术 (4)
  • 数据安全 (30)
  • 网络协议 (17)
  • 计算机理论 (22)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号