一次性搞懂

ooowl
  • 代码之外
About 10 min

一次性搞懂

常见Web请求方式

FunctionCall SSE 长连接 websocket HTTP长连接 短连接 HTTP/1.1、HTTP/2、HTTP/3、WebSocket、TLS RPC GraphQL SOAP MQTT WebRTC

文本传输协议

XML

该入土的老东西早该爆金币了(
凑活看mozilla的文档open in new window吧,下面有其他文档的引用。 XML 树结构 | 菜鸟教程open in new window 有且仅有一个根节点,大小写敏感,第一行必是版本和编码,保证严格有序,元素可以重名解析的时候视为列表,xml以LF存储换行。
特殊符号< &lt > &gt & &amp ' &apos " &quot 代替
所有的节点名,InnerText,Attributes都是字符串类型,IO时的类型转换都由程序来做。
虽然格式很自由但是建议所有人用一个风格。

<?xml version="1.0" encoding="UTF-8"?> <!-- 第一行固定必须写 -->
<message>
    <Friend name="小明" age='8'>我的朋友</Friend><!-- 节点可以带属性 -->
    <Fater name="爸爸" age="50" /><!-- 没有子节点可以和html一样关闭 -->
    <warning>
         Hello World
    </warning>
</message>
神秘古老且恶心的CDATA;XSD;DTD;SOAP

CDATA(CharacterData):被这东西包裹的内容不会被转义,会被原样读取出来

<query><![CDATA[
SELECT * FROM user WHERE age > 18;
]]>惺惺相惜</query><!--可以和一般的混着用-->

XSD(XMLSchemaDefinition):新生代用来替代DTD校验XML的东西,纯规则没有数据。

<?xml version="1.0"?>
<!-- 根元素:声明这是一个 XML Schema 文件 -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <!-- xs:element:定义一个元素 -->
  <xs:element name="user">

    <!-- xs:complexType:复杂类型,元素可以有子元素或属性 -->
    <xs:complexType>

      <!-- xs:sequence:子元素必须按顺序出现 -->
      <xs:sequence>

        <!-- 普通元素,直接指定类型 -->
        <xs:element name="name" type="xs:string"/>

        <!-- 元素内部定义简单类型限制 -->
        <xs:element name="age">

          <!-- xs:simpleType:简单类型,可以在里面加 restriction 限制 -->
          <xs:simpleType>

            <!-- xs:restriction:对基础类型加限制 -->
            <xs:restriction base="xs:int">

              <!-- xs:minInclusive / xs:maxInclusive:最小/最大值 -->
              <xs:minInclusive value="0"/>
              <xs:maxInclusive value="120"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>

        <xs:element name="gender">
          <xs:simpleType>
            <xs:restriction base="xs:string">

              <!-- xs:enumeration:指定可选枚举值 -->
              <xs:enumeration value="男"/>
              <xs:enumeration value="女"/>
              <xs:enumeration value="未知"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>

      </xs:sequence>

      <!-- xs:attribute:为元素添加属性 -->
      <xs:attribute name="id" type="xs:int" use="required"/>

    </xs:complexType>
  </xs:element>

</xs:schema>

DTD(DocumentTypeDefinition): 上世纪80-90留下的历史债,规定了数据的格式类型顺序等等,不仅是规则还混合了一些数据。比较恶心用的也很少了,形式类似于这种<!ELEMENT user (name, age)> <!ATTLIST user id ID #REQUIRED>(看不懂就对了。
SOAP(SimpleObjectAccessProtocol): 使用纯XML通信的web协议,格式类似于下面这种,有时候还会在header中带东西,也比较古老且恶心。

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      ...
   </s:Body>
</s:Envelope>

JSON

文档在这里open in new window json标准东西不多
json的类型支持 字符串(string)、数值(number)、truefalsenull、字典(object)或者数组(array)。这些结构可以嵌套。
在 JSON 传输过程中,列表是有序的,但字典(对象)不是有序的。JSON本身没有明确的长度限制,但是也不会写的巨长,序列化的时候一般会放内存容易崩

Protobuf

中文open in new window翻译文档和英文原版open in new window文档。
这个博客open in new window不错,给出了最佳实践和必要的编码原理 google开发的,和json相比体积小速度快,适合IM这种延迟敏感型应用,proto文件是跨语言的,定义好之后可以用(谷歌提供的)工具反向生成各种语言的代码,再结合proto的包去使用,所以说一开始格式就已经被确定了。

📝Note

proto有2和3两个版本是不兼容的,现在绝大部分用的是proto3,如果碰到奇怪的文件请注意proto版本的可能性
注意文档中的协议特性和不适用的地方!!!!!
1 ~ 15:单字节编码,16 ~ 2047:双字节编码,使用频率高的变量最好设置为1~15,编号一旦指定不能修改,所以为了扩展保留一些 1~15 的 编号

Protobuf在消息大小上没有明确限制,但通常建议保持小于2MB(就是不要太大,并不一定非得2MB)。Protobuf的字段顺序在定义时是固定的,但在序列化时不保证顺序
C#代码中可以使用protogen,是针对C#的实现,大致看一眼,用到细节再去查。mac上brew install protobuf 安装,使用protoc --version查看版本,一般是3.

特性protocprotogen
生成的代码标准的 Protocol Buffers C# 类生成的 C# 类更符合 .NET 的风格和最佳实践
gRPC 支持支持 gRPC(通过 grpc_csharp_plugin)提供更丰富的 gRPC 支持,优化了与 .NET 集成的方式
定制化能力基本的 C# 类生成,较少定制化提供更多的扩展点,允许开发者对代码进行自定义
.NET 特性提供基础的 C# 支持更好支持 .NET 特性,如依赖注入、日志系统等
集成工具与原生 Protobuf 代码生成集成与 .NET 工具链更深度集成,比如 MSBuild 插件
使用场景适用于跨平台和多语言项目更适合 .NET 核心或大型 C# 项目,尤其是 gRPC 服务
一个基本的protobuf文件的定义如下
syntax = "proto3";

package example; //  包可以划分不同的命名空间

// import "user.proto"; //  可以引入其他的包
// 定义一个用户类型的枚举
enum UserType {
  UNKNOWN = 0;
  ADMIN = 1;
  MEMBER = 2;
}

// 地址信息消息
message Address {
  string street = 1;
  string city = 2;
  string state = 3;
  sint32 zip_code = 4; // 适合表示带负数的
}

// 用户信息消息,嵌套Address
message User {
  int32 id = 1; //  比较适合正数
  string name = 2;
  string email = 3;
  repeated Address addresses = 4; // 嵌套的Address消息类型
  UserType type = 5;          // 使用枚举类型UserType
}

proto文件消息类型C++ 类型说明
doubledouble双精度浮点型
floatfloat单精度浮点型
int32int32使用可变长编码方式,负数时不够高效,应该使用sint32
int64int64使用可变长编码方式,负数时不够高效,应该使用sint32
uint32uint32使用可变长编码方式
uint64uint64使用可变长编码方式
sint32int32使用可变长编码方式,有符号的整型值,负数编码时比通常的int32高效
sint64sint64使用可变长编码方式,有符号的整型值,负数编码时比通常的int64高效
fixed32uint32总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32高效
fixed64uint64总是8个字节,如果数值总是比2^56大的话,这个类型会比uint64高效
sfixed32int32总是4个字节
sfixed64int64总是8个字节
boolbool布尔类型
stringstring一个字符串必须是utf-8编码或者7-bit的ascii编码的文本
bytesstring可能包含任意顺序的字节数据,不是C#中的字节数组

在 Protocol Buffers (Protobuf) 中,生成的代码会根据目标语言的命名约定来调整字段名。在 C# 中,protoc会将消息和字段名转换为首字母大写,以符合 C# 的命名约定。

  1. 消息名
    • message hello_world_request 会变为 HelloWorldRequest
  2. 字段名
    • helloworld 会变为 Helloworld
    • 如果字段是用下划线(_)分隔的,例如 hello_world,会变成 HelloWorld

这是 Protobuf 的 protoc 编译器针对 C# 的正常行为,不会影响字段实际存储或序列化的键值,只是对代码接口做了语言风格适配。

通义 - proto需要补充的open in new window

MessagePack

跨语言,定义自带类型,支持动态解析,比json快很多,纯2进制传输,相比protobuf稍大但灵活,速度比proto慢一点。
常用于对延迟和体积有要求的地方比如rpc等,感觉需要速度的时候都可以被proto替代,不需要速度的时候被json替代。可能碰到恰好的场景才会发挥作用。

  • key不能重复或者跳号,可以用字符串当key但是体积大;
  • 字段增加用新的 Key,旧 Key 保留或废弃,否则重用可能导致历史数据错误;
  • 兼容了特殊字符;
  • 反序列化的时候可能报错要注意处理
  • 对引用类型可以兼容null,值类型必须标记可空才行否则报错

用到的时候再去搞细节吧,C#世界里有MemoryPack上位替代

在C#中的示例

[MessagePackObject]
public class User
{
    [Key(0)] public int? Id { get; set; } // 值类型可空标记
    [Key(1)] public string Name { get; set; }
    [Key(2)] public string Email { get; set; }
    [Key(3)] public Animal Pet { get; set; } // 使用继承类的时候一定要注意
}

[Union(0, typeof(Dog))]
[Union(1, typeof(Cat))]
[MessagePackObject]
public abstract class Animal 
{ [Key(0)] public string Name { get; set; } }

[MessagePackObject]
public class Dog : Animal
{ [Key(1)] public bool HasTail { get; set; } }

[MessagePackObject]
public class Cat : Animal
{ [Key(1)] public int Lives { get; set; } }
class Program
{
    static void Main(string[] args)
    {
        User user = new User { Id = 1001, Name = "Jack", Email = "jack@example.com" ,Pet = new Dog { Name = "Buddy", HasTail = true }}; // 创建对象,注意看是怎么多态指定Pat的类型
        // 序列化到字节数组
        byte[] bytes = MessagePackSerializer.Serialize(user);
        Console.WriteLine("序列化字节长度: " + bytes.Length);
        // 反序列化
        User deserializedUser = MessagePackSerializer.Deserialize<User>(bytes);
        Console.WriteLine($"反序列化结果: Id={deserializedUser.Id}, Name={deserializedUser.Name}, Email={deserializedUser.Email}");
        // 序列化到文件
        string filePath = "user.dat";
        File.WriteAllBytes(filePath, bytes);
        Console.WriteLine("已写入文件: " + filePath);
        // 从文件读取并反序列化
        byte[] readBytes = File.ReadAllBytes(filePath);
        User userFromFile = MessagePackSerializer.Deserialize<User>(readBytes);
        Console.WriteLine($"从文件读取: Id={userFromFile.Id}, Name={userFromFile.Name}, Email={userFromFile.Email}");
    }
}

编码的发展

有时间再整理。 彻底摆脱乱码的困惑-技术圈open in new window
乱码对照表_evasnowind的博客-CSDN博客_乱码对照表open in new window
ASCII Table - ASCII Character Codes, HTML, Octal, Hex, Decimalopen in new window
字符串和编码 - 廖雪峰的官方网站open in new window

一个汉字占多少

ASCII 要记住什么

Unicode中文乱码表

乱码示例特点产生原因
古文码鐢辨湀瑕佸ソ濂藉涔犲ぉ澶╁悜涓?大都为不认识的古文,并加杂日韩文以 GBK 方式读取 UTF-8 编码的中文
口字码����Ҫ�¨2�ѧϰ������大部分字符为小方块以 UTF-8 的方式读取 GBK 编码的中文
符号码由月è|å¥½å¥½å-|ä1 天天向上大部分字符为各种符号以 ISO8859-1 方式读取 UTF-8 编码的中文
拼音码óéÔÂòaoÃoÃѧϰììììÏòéÏ大部分字符为头顶带有各种类似声调符号的字母以 ISO8859-1 方式读取 GBK 编码的中文
问句码由月要好好学习天天向??字符串长度为偶数时正确,长度为奇数时最后的字符变为问号以 GBK 方式读取 UTF-8 编码的中文,然后又用 UTF-8 的格式再次读取
锟拷码锟斤拷锟斤拷要锟矫猴拷学习锟斤拷锟斤拷锟斤拷全中文字符,且大部分字符为“锟斤拷”这几个字符以 UTF-8 方式读取 GBK 编码的中文,然后又用 GBK 的格式再次读取
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫字符显示为“烫烫烫”这几个字符VC Debug 模式下,栈内存未初始化
屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯屯字符显示为“屯屯屯”这几个字符VC Debug 模式下,堆内存未初始化

推荐插件 File Encoding Converter 安装完之后右键直接转换好使的一笔。 #TODO 字符集open in new window

时间格式和时区



Last Edit: 2025-09-16 00:06:52
Loading...