-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-search.xml
More file actions
368 lines (174 loc) · 219 KB
/
local-search.xml
File metadata and controls
368 lines (174 loc) · 219 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Java 分层架构</title>
<link href="/2026/02/04/Java-%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84/"/>
<url>/2026/02/04/Java-%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84/</url>
<content type="html"><![CDATA[<h1 id="Java-分层架构"><a href="#Java-分层架构" class="headerlink" title="Java 分层架构"></a>Java 分层架构</h1><h2 id="一、Controller-Service-Dao"><a href="#一、Controller-Service-Dao" class="headerlink" title="一、Controller/ Service / Dao"></a>一、Controller/ Service / Dao</h2><blockquote><p>这是现代企业级应用(尤其是 Java Web 应用)中常见的 <strong>分层架构模式</strong>,目的是解耦、提高可维护性和可测试性。</p></blockquote><p>整体的架构图如下所示:</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs scss"><span class="hljs-selector-attr">[客户端]</span> <br> ↓ (HTTP 请求)<br><span class="hljs-keyword">@Controller</span>(控制器)<br> ↓ 调用<br><span class="hljs-keyword">@Service</span>(业务逻辑层)<br> ↓ 调用<br><span class="hljs-keyword">@Mapper</span> / <span class="hljs-keyword">@Repository</span>(数据访问层,即 DAO)<br> ↓<br>[数据库]<br></code></pre></td></tr></table></figure><h3 id="1-DAO(Data-Access-Object)——-数据访问层"><a href="#1-DAO(Data-Access-Object)——-数据访问层" class="headerlink" title="1. DAO(Data Access Object)—— 数据访问层"></a>1. DAO(Data Access Object)—— 数据访问层</h3><p>职责:</p><ul><li>封装对数据库的 CRUD 操作</li><li>只负责数据存取,不包含业务逻辑。</li></ul><p>实现可以是原生 JDBC -> <code>UserDao</code> 接口 + 实现类或者 MyBatis -> <code>@Mapper</code> 接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Mapper</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserMapper</span> {<br>User <span class="hljs-title function_">selectById</span><span class="hljs-params">(Long id)</span>;<br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">insert</span><span class="hljs-params">(User user)</span>;<br> List<User> <span class="hljs-title function_">findAll</span><span class="hljs-params">()</span>;<br>}<br></code></pre></td></tr></table></figure><blockquote><p>✅<strong>DAO = 数据的“搬运工”</strong>,只管“拿”和“存”。</p></blockquote><h3 id="2-Service(业务逻辑层)"><a href="#2-Service(业务逻辑层)" class="headerlink" title="2. Service(业务逻辑层)"></a>2. Service(业务逻辑层)</h3><p>职责:</p><ul><li><p>实现 <strong>核心业务逻辑</strong>(如注册、支付、订单处理)。</p></li><li><p><strong>协调多个 DAO 操作</strong>(例如:注册用户 + 发送邮件 + 记录日志)。</p></li></ul><p>无状态,可被多个 Controller 复用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Service</span><br><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> {<br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> UserMapper userMapper;<br><br> <span class="hljs-keyword">public</span> Long <span class="hljs-title function_">register</span><span class="hljs-params">(String name, String email)</span> {<br> <span class="hljs-comment">// 1. 校验逻辑(业务规则)</span><br> <span class="hljs-keyword">if</span> (userMapper.existsByEmail(email)) {<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(<span class="hljs-string">"邮箱已存在"</span>);<br> }<br> <span class="hljs-comment">// 2. 调用 DAO 保存数据</span><br> <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(name, email);<br> userMapper.insert(user);<br> <span class="hljs-keyword">return</span> user.getId();<br> }<br>}<br></code></pre></td></tr></table></figure><blockquote><p>✅<strong>Service = 业务的“大脑”</strong>,决定“做什么”和“怎么做”。</p></blockquote><h3 id="3-Controller(控制层)"><a href="#3-Controller(控制层)" class="headerlink" title="3. Controller(控制层)"></a>3. Controller(控制层)</h3><p>职责:</p><ul><li><p>接收 <strong>HTTP 请求</strong></p></li><li><p><strong>解析请求参数</strong>(JSON、路径变量、表单)</p></li><li><p>调用 <strong>Service</strong> 执行业务</p></li><li><p><strong>封装响应结果</strong>(返回 JSON、跳转页面)</p></li></ul><p>技术实现上一般是 Spring MVC 的 <code>@RestController</code> 或 <code>@Controller</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping("/api/users")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {<br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> UserService userService;<br><br> <span class="hljs-meta">@PostMapping</span><br> <span class="hljs-keyword">public</span> ResponseEntity<Long> <span class="hljs-title function_">register</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> RegisterRequest req)</span> {<br> <span class="hljs-type">Long</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> userService.register(req.getName(), req.getEmail());<br> <span class="hljs-keyword">return</span> ResponseEntity.ok(userId);<br> }<br>}<br></code></pre></td></tr></table></figure><blockquote><p>✅ <strong>Controller = 系统的“门面”</strong>,负责“接客”和“传话”。</p></blockquote><p>Ps. 传统 MVC (如早期 Spring MVC) ,Model 相当与这里的 Service + Dao + Entity, View 则是被独立出去,变成了前端(展示数据),Controller 上职责有一些变化(传统是协调 Model 和 View,而现在 Java web 中是负责接收 + 调用 Services)</p>]]></content>
<categories>
<category>Java</category>
</categories>
</entry>
<entry>
<title>JDBC</title>
<link href="/2026/02/04/JDBC/"/>
<url>/2026/02/04/JDBC/</url>
<content type="html"><![CDATA[<h1 id="JDBC"><a href="#JDBC" class="headerlink" title="JDBC"></a>JDBC</h1><blockquote><p>JDBC 是 Java 提供的一套用于连接和操作关系型数据库的标准 API。允许 Java 程序通过 SQL 语句与数据库进行交互,而无需关心底层数据库的具体实现(只要数据库提供对应的 JDBC 驱动)</p></blockquote><h2 id="一、核心接口"><a href="#一、核心接口" class="headerlink" title="一、核心接口"></a>一、核心接口</h2><h3 id="1-Connection(连接)"><a href="#1-Connection(连接)" class="headerlink" title="1. Connection(连接)"></a>1. Connection(连接)</h3><p>作用:</p><p>表示应用程序与数据库之间的会话连接。</p><ul><li>是所有数据库操作的起点:通过 <code>Connection</code> 可以创建 <code>Statement</code>,<code>PreparedStatement</code> 等对象。</li><li>负责管理事务(比如提交 <code>Commit</code>,回滚 <code>rollback()</code>)</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">sql::mysql::MySQL_Driver* driver = sql::mysql::<span class="hljs-built_in">get_mysql_driver_instance</span>();<br><span class="hljs-keyword">auto</span>* con = driver-><span class="hljs-built_in">connect</span>(url_, user_, pass_);<br></code></pre></td></tr></table></figure><h3 id="2-Statement(语句)"><a href="#2-Statement(语句)" class="headerlink" title="2. Statement(语句)"></a>2. Statement(语句)</h3><p>作用:用于执行静态 SQL 语句(即不带参数的 SQL),适用于执行 DDL(如 <code>CREATE TABLE</code>)或简单查询</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">std::unique_ptr<sql::Statement> <span class="hljs-title">stmt</span><span class="hljs-params">(con->_con->createStatement())</span></span>;<br>stmt-><span class="hljs-built_in">executeQuery</span>(<span class="hljs-string">"SELECT 1"</span>);<br></code></pre></td></tr></table></figure><p>该接口容易发生 SQL 注入,每次执行都需要重新编译 SQL(效率较低)</p><h3 id="3-PreparedStatement(预编译语句)"><a href="#3-PreparedStatement(预编译语句)" class="headerlink" title="3. PreparedStatement(预编译语句)"></a>3. PreparedStatement(预编译语句)</h3><p>继承自 <code>Statement</code>,作用:</p><ul><li>执行<strong>带参数的预编译 SQL 语句</strong>。</li><li>SQL 语句中使用 <code>?</code> 作为占位符,参数通过 <code>setXxx()</code> 方法设置</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 准备调用存储过程</span><br>unique_ptr <sql::PreparedStatement> <span class="hljs-built_in">stmt</span>(con-><span class="hljs-built_in">prepareStatement</span>(<span class="hljs-string">"CALL reg_user(?,?,?,@result)"</span>));<br><span class="hljs-comment">// 设置输入参数</span><br>stmt-><span class="hljs-built_in">setString</span>(<span class="hljs-number">1</span>, name);<br>stmt-><span class="hljs-built_in">setString</span>(<span class="hljs-number">2</span>, email);<br>stmt-><span class="hljs-built_in">setString</span>(<span class="hljs-number">3</span>, pwd);<br></code></pre></td></tr></table></figure><p>解决了 <code>statement</code> 的问题,防止 SQL 注入,同时性能更高:SQL 语句在数据库端预编译,多次执行时只需替换参数,无需重复解析。</p><h3 id="4-ResultSet(结果集)"><a href="#4-ResultSet(结果集)" class="headerlink" title="4. ResultSet(结果集)"></a>4. ResultSet(结果集)</h3><p>作用:</p><ul><li><p>表示执行查询(<code>SELECT</code>)后返回的<strong>数据结果集合</strong>。</p></li><li><p>类似一个“游标”,初始位置在第一行之前,通过 <code>next()</code> 向下移动</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function">unique_ptr<sql::ResultSet> <span class="hljs-title">res</span><span class="hljs-params">(stmtResult->executeQuery(<span class="hljs-string">"SELECT @result AS result"</span>))</span></span>;<br><span class="hljs-keyword">if</span> (res-><span class="hljs-built_in">next</span>()) {<br> <span class="hljs-type">int</span> result = res-><span class="hljs-built_in">getInt</span>(<span class="hljs-string">"result"</span>);<br> cout << <span class="hljs-string">"Result: "</span> << result << endl;<br> pool_-><span class="hljs-built_in">returnConnection</span>(std::<span class="hljs-built_in">move</span>(con));<br> <span class="hljs-keyword">return</span> result;<br>}<br></code></pre></td></tr></table></figure><p>只能向前遍历(默认情况下),同时必须在 <code>Connection</code> 和 <code>Statement</code> 关闭前读取数据。</p>]]></content>
<categories>
<category>数据库</category>
</categories>
</entry>
<entry>
<title>JavaScript</title>
<link href="/2026/02/04/Js/"/>
<url>/2026/02/04/Js/</url>
<content type="html"><![CDATA[<h1 id="JavaScript-基础"><a href="#JavaScript-基础" class="headerlink" title="JavaScript 基础"></a>JavaScript 基础</h1><h2 id="一、Nodejs-Js-npm-的关系"><a href="#一、Nodejs-Js-npm-的关系" class="headerlink" title="一、Nodejs, Js, npm 的关系"></a>一、Nodejs, Js, npm 的关系</h2><p>JavaScript 是一种动态、弱类型、解释型的脚本语言(基于 C++ 封装)</p><p>Node.js 是基于 Chrome V8 引擎构建的 JavaScript Runtime,目的是能 js 在服务端上运行</p><p>npm(Node Package Manager) 是 Nodejs 的官方包管理工具,目的是管理第三方代码依赖</p><p>三者关系是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs text">JavaScript(语言)<br> ↑<br>Node.js(运行环境:提供 fs/http/crypto 等 API + V8 引擎)<br> ↑<br>npm(包管理器:安装/发布/管理 JS 库)<br></code></pre></td></tr></table></figure><h2 id="二、隔离不同项目依赖"><a href="#二、隔离不同项目依赖" class="headerlink" title="二、隔离不同项目依赖"></a>二、隔离不同项目依赖</h2><p>不像 python 有 venv, Js 是通过本地 <code>node_modules</code> + 锁定文件来实现逻辑上的隔离</p><h3 id="每个项目有自己的-node-modules-目录"><a href="#每个项目有自己的-node-modules-目录" class="headerlink" title="每个项目有自己的 node_modules 目录"></a>每个项目有自己的 <code>node_modules</code> 目录</h3><p>比如执行 <code>npm install ioredis</code> 的时候,包会被安装到当前项目目录下的 <code>./node_modules</code>。</p><p>不同项目的 <code>node_modules</code> 互不影响</p><h3 id="依赖解析规则("><a href="#依赖解析规则(" class="headerlink" title="依赖解析规则("></a>依赖解析规则(</h3><p>当代码中写 <code>const Redis = require('ioredis')</code> 的时候,Node.js 会:</p><ol><li>优先在 <strong>当前项目 <code>node_modules</code></strong> 中找;</li><li>如果没有,逐级向上查找父目录的 <code>node_modules</code>,直到根目录;</li><li><strong>不会自动使用全局安装的包</strong>(除非显式指定)。</li></ol><h3 id="锁定依赖版本"><a href="#锁定依赖版本" class="headerlink" title="锁定依赖版本"></a>锁定依赖版本</h3><p><code>npm install</code> 下载生成的 <code>package-lock.json</code> 记录<strong>精确版本号 + 依赖树结构</strong>。</p><p>团队成员执行 <code>npm ci</code> 可重建<strong>完全一致</strong>的 <code>node_modules</code>。</p><h2 id="三、JavaScript-的内存管理"><a href="#三、JavaScript-的内存管理" class="headerlink" title="三、JavaScript 的内存管理"></a>三、JavaScript 的内存管理</h2><h3 id="内存分配机制"><a href="#内存分配机制" class="headerlink" title="内存分配机制"></a>内存分配机制</h3><p>Js 引擎在运行时<strong>自动推断值的类型</strong>,并为其分配合适内存:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-keyword">let</span> a = <span class="hljs-number">42</span>; <span class="hljs-comment">// → 存为 32 位整数(Smis,V8 优化)</span><br>a = <span class="hljs-number">3.14</span>; <span class="hljs-comment">// → 改为 64 位双精度浮点数(HeapNumber)</span><br>a = <span class="hljs-string">"hello"</span>; <span class="hljs-comment">// → 分配字符串对象(在堆上)</span><br>a = { <span class="hljs-attr">x</span>: <span class="hljs-number">1</span> }; <span class="hljs-comment">// → 分配对象(在堆上)</span><br></code></pre></td></tr></table></figure><p>其中基本类型(number, string, boolean, symbol)小整数或短字符串可能直接内联存储(栈或对象内),大值存在堆上</p><p>引用类型(Object, Array, Function) 总是分配在堆上,</p><h3 id="垃圾回收机制"><a href="#垃圾回收机制" class="headerlink" title="垃圾回收机制"></a>垃圾回收机制</h3><p>Js 的内存管理机制是自动垃圾回收(GC),C++ 现在则是利用 RAII 思想来析构对象,可以直接用智能指针来管理对象</p><p>后现代语言通常采用 GC 机制回收变量,当创建的对象不再被使用的时候就会变成 “垃圾”,被 GC 自动回收。</p><p><strong>识别</strong>核心也是:</p><ol><li>引用计数(存在循环引用问题):每个对象有计数器,被引用一次,计数就 + 1,引用消失,计数就 -1,为 0 时就被回收</li><li>可达性分析:从 “根对象”(全局变量,栈变量,当前执行上下文)开始遍历,能遍历到的 = 活着,遍历不到 = 垃圾</li></ol><p><strong>回收</strong>核心则是:</p><ol><li>分代回收(Java/JS v8 核心):把对象分为 ”新对象“/”老对象“,其中新生代对象死得快,GC 频繁,老生代对象活得久,GC 少(效率高)</li><li>Mark-Sweep:先标记所有可达对象,再清除未标记的垃圾</li><li>Mark-Compact:标记 → 把存活对象往一边挤 → 清除剩下的垃圾,这样能够解决碎片问题</li></ol><p>GC 的<strong>缺点</strong>很明显:</p><ol><li>GC 工作时,会有停顿(STW),影响系统性能</li><li>GC 本身会有开销,占 CPU,内存</li></ol>]]></content>
<categories>
<category>JavaScript</category>
</categories>
</entry>
<entry>
<title>池化</title>
<link href="/2026/02/03/%E6%B1%A0%E5%8C%96/"/>
<url>/2026/02/03/%E6%B1%A0%E5%8C%96/</url>
<content type="html"><![CDATA[<h1 id="池化"><a href="#池化" class="headerlink" title="池化"></a>池化</h1><blockquote><p>池化是一种资源复用计数,通过<strong>预先创建一组可复用的资源对象</strong>,避免频繁申请/释放带来的开销(如 TCP 连接建立、线程上下文切换、内存分配等),从而提升系统性能和稳定性。</p></blockquote><h2 id="一-分配策略"><a href="#一-分配策略" class="headerlink" title="一. 分配策略"></a>一. 分配策略</h2><p>特点:分配策略 + 无状态共享</p><p>核心设计:</p><ul><li><p>预先创建多个 <code>io_context</code> 对象(默认 2 个),每个运行在独立线程中。</p></li><li><p>使用 <strong>轮询(Round-Robin)策略</strong> 分配 <code>io_context</code> 给新连接。</p></li><li><p>每个 <code>io_context</code> 绑定一个 <code>work guard</code>,防止因无任务而退出。</p></li><li><p><strong>不涉及借还</strong>,分配后 socket 生命周期内独占该 <code>io_context</code>。</p></li></ul><h4 id="Asio-连接池:"><a href="#Asio-连接池:" class="headerlink" title="Asio 连接池:"></a>Asio 连接池:</h4><p>IOContextPool.h</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 创建 IOService 池子, acceptor 接收到连接以后取出一个 IOService 来创建 Http 管理(含通信套接字), 该 ioc 就负责通信套接字上的 IO 读写</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">CAsioIoServicePool</span> : <span class="hljs-keyword">public</span> Singleton<CAsioIoServicePool> {<br><span class="hljs-keyword">friend</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span><CAsioIoServicePool>;<br><span class="hljs-keyword">public</span>:<br><span class="hljs-keyword">using</span> IOService = boost::asio::io_context;<br><span class="hljs-comment">// 创建一个 work guard 来防止 io_context 因为没有任务而提前结束</span><br><span class="hljs-keyword">using</span> Work = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>;<br>~<span class="hljs-built_in">CAsioIoServicePool</span>();<br><span class="hljs-comment">// round-robin 策略: 分配策略</span><br>boost::<span class="hljs-function">asio::io_context& <span class="hljs-title">GetIOService</span><span class="hljs-params">()</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Stop</span><span class="hljs-params">()</span></span>;<br><span class="hljs-keyword">private</span>:<br><span class="hljs-built_in">CAsioIoServicePool</span>(<span class="hljs-type">size_t</span> size = <span class="hljs-number">2</span>);<br>std::vector<IOService> m_IOServices;<br>std::vector<Work> m_Works;<br>std::vector<std::thread> m_Threads;<br>std::<span class="hljs-type">size_t</span> _nextIOService;<br>};<br></code></pre></td></tr></table></figure><h2 id="二-借还策略"><a href="#二-借还策略" class="headerlink" title="二. 借还策略"></a>二. 借还策略</h2><p>特点:借还策略 + 有状态独占</p><p>核心设计:</p><ul><li><p>预先创建多个 <code>VerifyService::Stub</code>(每个 stub 内含一个 gRPC channel,即 TCP 连接)。</p></li><li><p>线程通过 <code>getConnection()</code> <strong>借出</strong> 一个 stub,使用完毕必须调用 <code>pushConnection()</code> <strong>归还</strong>。</p></li><li><p>使用 <code>std::mutex + std::condition_variable</code> 保护连接队列,支持等待空闲连接。</p></li><li><p><strong>同一 stub 在被借出期间只能由一个线程使用</strong>(gRPC stub 非线程安全)。</p></li></ul><h4 id="grpc-连接池:"><a href="#grpc-连接池:" class="headerlink" title="grpc 连接池:"></a>grpc 连接池:</h4><p>RPConPoo.h</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 借还策略:池化多个存根 stub 创建多个通信通道 channel 谁用就来取一个, 用完之后要还给池子</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">RPConPool</span> {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-built_in">RPConPool</span>(<span class="hljs-type">size_t</span> poolSize, std::string host, std::string port);<br>~<span class="hljs-built_in">RPConPool</span>();<br><span class="hljs-function">std::unique_ptr<VerifyService::Stub> <span class="hljs-title">getConnection</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">pushConnection</span><span class="hljs-params">(std::unique_ptr<VerifyService::Stub> context)</span></span>;<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Close</span><span class="hljs-params">()</span></span>;<br><br><span class="hljs-keyword">private</span>:<br>std::atomic<<span class="hljs-type">bool</span>> m_stop_;<br>std::queue<std::unique_ptr<VerifyService::Stub>> m_conn_;<br>std::mutex m_mtx_;<br>std::condition_variable m_cond_;<br><span class="hljs-type">size_t</span> m_poolSize_;<br>std::string m_host_;<br>std::string m_port_;<br>};<br></code></pre></td></tr></table></figure><h4 id="redis-连接池:"><a href="#redis-连接池:" class="headerlink" title="redis 连接池:"></a>redis 连接池:</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">RedisConPool</span> {<br><span class="hljs-keyword">public</span>:<br><span class="hljs-built_in">RedisConPool</span>(<span class="hljs-type">size_t</span> poolSize, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* host, <span class="hljs-type">int</span> port, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* pwd);<br>~<span class="hljs-built_in">RedisConPool</span>();<br><span class="hljs-function">redisContext* <span class="hljs-title">getConnection</span><span class="hljs-params">()</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">returnConnection</span><span class="hljs-params">(redisContext* context)</span></span>;<br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Close</span><span class="hljs-params">()</span></span>;<br><span class="hljs-keyword">private</span>:<br><span class="hljs-comment">// 连接元数据</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span>* m_host_;<br><span class="hljs-type">int</span> m_port_;<br><span class="hljs-type">size_t</span> m_poolSize_;<br><span class="hljs-comment">// 连接池</span><br>std::mutex m_mtx_;<br>std::condition_variable m_cond_;<br>std::queue<redisContext*> m_conns_;<br>std::atomic<<span class="hljs-type">bool</span>> m_stop_;<br>};<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>项目经验</category>
</categories>
</entry>
<entry>
<title>redis 的使用</title>
<link href="/2026/02/03/redis%20%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
<url>/2026/02/03/redis%20%E7%9A%84%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><p>redis(Remote Directionary Server)是一个开源的、基于内存的键值存储系统,常被用作数据库、缓存和消息中间件。支持多种数据结构,每种结构都有对应的命令操作。</p><h2 id="一、Redis-的对象类型"><a href="#一、Redis-的对象类型" class="headerlink" title="一、Redis 的对象类型"></a>一、Redis 的对象类型</h2><p>Redis 的值(value)可以是以下五种基本数据类型之一,前三种比较常用:</p><h3 id="1-String-(字符串)"><a href="#1-String-(字符串)" class="headerlink" title="1. String (字符串)"></a>1. String (字符串)</h3><ul><li>最基本的数据类型。</li><li>可以存储字符串、整数或浮点数(最大 512MB)。</li><li>常用于缓存简单数据,如用户信息、计数器等。</li></ul><p>常用操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash">SET key value <span class="hljs-comment"># 设置键值</span><br>GET key <span class="hljs-comment"># 获取值</span><br>INCR key <span class="hljs-comment"># 自增 1(值必须为整数)</span><br>DECR key <span class="hljs-comment"># 自减 1</span><br>INCRBY key increment <span class="hljs-comment"># 增加指定整数</span><br>APPEND key value <span class="hljs-comment"># 追加字符串</span><br>STRLEN key <span class="hljs-comment"># 获取字符串长度</span><br>MSET key1 val1 key2 val2 ... <span class="hljs-comment"># 批量设置</span><br>MGET key1 key2 ... <span class="hljs-comment"># 批量获取</span><br></code></pre></td></tr></table></figure><p>Ps. Redis 的 key 总是字符串类型</p><h3 id="2-Hash(哈希)"><a href="#2-Hash(哈希)" class="headerlink" title="2. Hash(哈希)"></a>2. Hash(哈希)</h3><p>类似二级缓存,【WebSite】【blogSite】 = example.com</p><ul><li><p>类似于一个“字典”或“对象”,存储字段(field)和值(value)的映射。</p></li><li><p>适合存储对象(如用户资料:name、age、email 等字段)。</p></li></ul><p>常用操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">HSET key field value <span class="hljs-comment"># 设置哈希字段的值</span><br>HGET key field <span class="hljs-comment"># 获取哈希字段的值</span><br>HMSET key field1 val1 field2 val2 ... <span class="hljs-comment"># 批量设置(Redis 4.0+ 推荐用 HSET)</span><br>HMGET key field1 field2 ... <span class="hljs-comment"># 批量获取</span><br>HGETALL key <span class="hljs-comment"># 获取所有字段和值(慎用,大数据量会阻塞)</span><br>HDEL key field [field...] <span class="hljs-comment"># 删除一个或多个字段</span><br>HEXISTS key field <span class="hljs-comment"># 判断字段是否存在</span><br>HLEN key <span class="hljs-comment"># 获取哈希中字段数量</span><br>HKEYS key <span class="hljs-comment"># 获取所有字段名</span><br>HVALS key <span class="hljs-comment"># 获取所有字段值</span><br></code></pre></td></tr></table></figure><h3 id="3-List(列表)"><a href="#3-List(列表)" class="headerlink" title="3. List(列表)"></a>3. List(列表)</h3><ul><li>有序、可重复的字符串列表,底层是双向链表。</li><li>常用于消息队列、最新 N 条记录等场景。</li></ul><p>常用操作:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">LPUSH key value [value...] <span class="hljs-comment"># 从左边插入</span><br>RPUSH key value [value...] <span class="hljs-comment"># 从右边插入</span><br>LPOP key <span class="hljs-comment"># 弹出左边第一个元素</span><br>RPOP key <span class="hljs-comment"># 弹出右边第一个元素</span><br></code></pre></td></tr></table></figure><h3 id="4-Set(集合)"><a href="#4-Set(集合)" class="headerlink" title="4. Set(集合)"></a>4. Set(集合)</h3><ul><li>类似 Set,但每个成员关联一个分数(score),按分数排序。</li><li>常用于排行榜、带权重的任务队列等。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">SADD key member [member...] <span class="hljs-comment"># 添加成员</span><br>SMEMBERS key <span class="hljs-comment"># 获取所有成员</span><br>SISMEMBER key member <span class="hljs-comment"># 判断是否是成员</span><br>SCARD key <span class="hljs-comment"># 获取集合大小</span><br></code></pre></td></tr></table></figure><h3 id="5-Sorted-Set(有序集合-ZSet)"><a href="#5-Sorted-Set(有序集合-ZSet)" class="headerlink" title="5. Sorted Set(有序集合 / ZSet)"></a>5. Sorted Set(有序集合 / ZSet)</h3><ul><li><p>类似 Set,但是每个成员关联一个分数(score),按分数排序。</p></li><li><p>常用于排行榜、带权重的任务队列等。</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">ZADD key score member [score member...] <span class="hljs-comment"># 添加成员及分数</span><br>ZRANGE key start stop [WITHSCORES] <span class="hljs-comment"># 按分数升序返回成员</span><br>ZREVRANGE key start stop [WITHSCORES] <span class="hljs-comment"># 按分数降序</span><br>ZSCORE key member <span class="hljs-comment"># 获取成员的分数</span><br></code></pre></td></tr></table></figure><h2 id="二、HiRedis"><a href="#二、HiRedis" class="headerlink" title="二、HiRedis"></a>二、HiRedis</h2><p>HiRedis 是 C++ 的一种库,C++ 还有其他库(redis-plus-plus)等等。源码这里不去介绍,不过通常我们会对 Redis 提供的操作进行一些封装,比如</p><h3 id="1-Connect-操作"><a href="#1-Connect-操作" class="headerlink" title="1. Connect 操作"></a>1. Connect 操作</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CRedisMgr::Connect</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& host, <span class="hljs-type">int</span> port)</span> </span>{<br><span class="hljs-keyword">this</span>->m_conn_ = <span class="hljs-built_in">redisConnect</span>(host.<span class="hljs-built_in">c_str</span>(), port);<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_conn_ == <span class="hljs-literal">nullptr</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_conn_ != <span class="hljs-literal">nullptr</span> && <span class="hljs-keyword">this</span>->m_conn_->err) {<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="2-Auth-操作"><a href="#2-Auth-操作" class="headerlink" title="2. Auth 操作"></a>2. Auth 操作</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CRedisMgr::Auth</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& password)</span> </span>{<br><span class="hljs-keyword">this</span>->m_reply_ = (redisReply*)<span class="hljs-built_in">redisCommand</span>(<span class="hljs-keyword">this</span>->m_conn_, <span class="hljs-string">"AUTH %s"</span>, password.<span class="hljs-built_in">c_str</span>());<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_reply_->type == REDIS_REPLY_ERROR) {<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-keyword">else</span> {<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-keyword">return</span> <br></code></pre></td></tr></table></figure><h3 id="3-Get-操作"><a href="#3-Get-操作" class="headerlink" title="3. Get 操作"></a>3. Get 操作</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CRedisMgr::Get</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& key, std::string& value)</span> </span>{<br><span class="hljs-keyword">this</span>->m_reply_ = (redisReply*)<span class="hljs-built_in">redisCommand</span>(<span class="hljs-keyword">this</span>->m_conn_, <span class="hljs-string">"GET %s"</span>, key.<span class="hljs-built_in">c_str</span>());<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_reply_ == <span class="hljs-literal">nullptr</span>) {<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_reply_->type == REDIS_REPLY_ERROR) {<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br>value = <span class="hljs-keyword">this</span>->m_reply_->str;<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br>std::cout << <span class="hljs-string">"Succeed to execute command [ GET "</span> << key << <span class="hljs-string">" ]"</span> << <span class="hljs-string">'\n'</span>;<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="4-HSet-操作"><a href="#4-HSet-操作" class="headerlink" title="4. HSet 操作"></a>4. HSet 操作</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CRedisMgr::HSet</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span>* key, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* hkey, <span class="hljs-type">const</span> <span class="hljs-type">char</span>* hvalue, <span class="hljs-type">size_t</span> hvalueLen)</span> </span>{<br><span class="hljs-comment">// 采用 redisCommandArgv 方法</span><br><span class="hljs-type">const</span> <span class="hljs-type">char</span>* argv[<span class="hljs-number">4</span>];<br><span class="hljs-type">size_t</span> argvlen[<span class="hljs-number">4</span>];<br>argv[<span class="hljs-number">0</span>] = <span class="hljs-string">"HSET"</span>;<br>argvlen[<span class="hljs-number">0</span>] = <span class="hljs-number">4</span>;<br>argv[<span class="hljs-number">1</span>] = key;<br>argvlen[<span class="hljs-number">1</span>] = <span class="hljs-built_in">strlen</span>(key);<br>argv[<span class="hljs-number">2</span>] = hkey;<br>argvlen[<span class="hljs-number">2</span>] = <span class="hljs-built_in">strlen</span>(hkey);<br>argv[<span class="hljs-number">3</span>] = hvalue;<br>argvlen[<span class="hljs-number">3</span>] = hvalueLen;<br><span class="hljs-keyword">this</span>->m_reply_ = (redisReply*)<span class="hljs-built_in">redisCommandArgv</span>(<span class="hljs-keyword">this</span>->m_conn_, <span class="hljs-number">4</span>, argv, argvlen);<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>->m_reply_ == <span class="hljs-literal">nullptr</span> || m_reply_->type != REDIS_REPLY_INTEGER) {<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br><span class="hljs-built_in">freeReplyObject</span>(<span class="hljs-keyword">this</span>->m_reply_);<br><span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></table></figure><p>上面的操作基本是遵循一个<strong>模板</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">CRedisMgr::xxx</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& key, std::string& value)</span> </span>{<br> redisContext *context;<br>redisReply *reply = (redisReply*)<span class="hljs-built_in">redisCommand</span>(context, <span class="hljs-string">"xxx %s %s"</span>, key.<span class="hljs-built_in">c_str</span>(), value.<span class="hljs-built_in">c_str</span>());<br> <span class="hljs-keyword">if</span>(reply == <span class="hljs-literal">nullptr</span> || reply == REDIS_REPLY_ERROR) {<br> ~~<span class="hljs-built_in">Debug</span>();<br> <span class="hljs-built_in">freeReplyObject</span>(reply);<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> }<br> ~~<span class="hljs-built_in">Debug</span>();<br> <span class="hljs-built_in">freeReplyObject</span>(reply);<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></table></figure><p>不过比较蛋疼的是不同操作如果出错对应的不同种的宏,所以还得自己去查,这里给个大概的分类</p><ul><li>“查询值” 型命令 -> 返回 <code>STRING</code> 或者 <code>NIL</code>(Get, HGet, ZScore)</li><li>“操作计数” 型命令-> 返回 <code>INTEGER</code>(DEL, HSET, RPUSH, EXISTS, INCR)</li><li>“状态反馈” 型命令 -> 返回 <code>STATUS</code>(SET, FLUSHDB, SAVE)</li><li>“集合/列表” 型命令 -> 返回 <code>ARRAY</code>(HGETALL , SMEMBERS)</li></ul>]]></content>
<categories>
<category>第三方库</category>
</categories>
</entry>
<entry>
<title>nodemailer 的使用</title>
<link href="/2026/01/31/nodemailer/"/>
<url>/2026/01/31/nodemailer/</url>
<content type="html"><![CDATA[<h1 id="NodeMailer"><a href="#NodeMailer" class="headerlink" title="NodeMailer"></a>NodeMailer</h1><h2 id="概念介绍"><a href="#概念介绍" class="headerlink" title="概念介绍"></a>概念介绍</h2><p>NodeMailer 是一个用于 Node.js 的邮件发送库, 通过连接 SMTP 协议,与指定的邮件服务商(QQ、163、Gmail 等)的 SMTP 服务器建立安全连接,并 <strong>代理</strong> 开发者完成邮件的提交与投递(先提交再投递)</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> transport = nodemailer.<span class="hljs-title function_">createTransport</span>({<br> <span class="hljs-attr">host</span>: <span class="hljs-string">'smtp.qq.com'</span>,<span class="hljs-comment">// ← 1. 指定 SMTP 服务器地址</span><br> <span class="hljs-attr">port</span>: <span class="hljs-number">465</span>,<span class="hljs-comment">// ← 2. 使用 SMTPS(SMTP over SSL)端口</span><br> <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,<span class="hljs-comment">// ← 3. 启用 TLS/SSL 加密</span><br> <span class="hljs-attr">auto</span>: {<br> <span class="hljs-attr">user</span>: <span class="hljs-string">'your_email@example.com'</span>,<span class="hljs-comment">// ← 4. 身份认证:邮箱账号</span><br> <span class="hljs-attr">pass</span>: <span class="hljs-string">'your_smtp_auth_code'</span><span class="hljs-comment">// ← 5. 身份认证:授权码(非登录密码!)</span><br> }<br>})<br></code></pre></td></tr></table></figure><p>当执行:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">await</span> transport.<span class="hljs-title function_">sendMail</span>(mailOptiion)<br></code></pre></td></tr></table></figure><p><code>nodemailer</code> 会:</p><ol><li><strong>建立 TCP 连接</strong> 到 <code>smtp.qq.com:465</code></li><li><strong>自动协商 SSL/TLS 加密通道</strong>(因为 <code>secure: true</code>)</li><li>发送 SMTP 命令序列<ul><li><code>EHLO</code> → 握手</li><li><code>AUTH LOGIN</code> → 使用 Base64 编码发送 <code>user</code> 和 <code>pass</code></li><li><code>MAIL FROM:<from@example.com</code> → 声明发件人</li><li><code>RCPT TO:<to@example.com></code> → 声明收件人</li><li><code>DATA</code> → 发送邮件头 + 正文</li><li><code>QUIT</code> → 断开连接</li></ul></li><li><strong>将邮件提交给 QQ 邮箱的 SMTP 服务器</strong></li><li><strong>由 QQ 邮箱系统负责最终投递到目标邮箱</strong>(如 163、Gmail、Outlook 等)</li></ol><blockquote><p><code>nodemailer</code> <strong>不直接投递到收件人邮箱</strong>,而是把邮件“交给”你配置的 SMTP 服务商(这里是 QQ),由它完成后续路由和投递。</p></blockquote><p>一句话总结就是:</p><blockquote><p>✅ **<code>nodemailer</code> <strong>是一个 SMTP 客户端库,它封装了 SMTP 协议的底层通信细节,让你能用简单的 JavaScript 对象配置,即可通过任意支持 SMTP 的邮件服务商(如 QQ、163、SendGrid、Amazon SES 等)发送邮件。</strong></p></blockquote><h2 id="Nodemailer-使用流程(5-步)"><a href="#Nodemailer-使用流程(5-步)" class="headerlink" title="Nodemailer 使用流程(5 步)"></a>Nodemailer 使用流程(5 步)</h2><h3 id="第-1-步:安装依赖"><a href="#第-1-步:安装依赖" class="headerlink" title="第 1 步:安装依赖"></a><strong>第 1 步:安装依赖</strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install nodemailer<br></code></pre></td></tr></table></figure><blockquote><p>⚠️ 注意:仅用于服务端(Node.js),<strong>不能在浏览器中使用</strong>(涉及 SMTP 密钥,且浏览器无 TCP 权限)。</p></blockquote><hr><h3 id="第-2-步:创建传输器(Transporter)"><a href="#第-2-步:创建传输器(Transporter)" class="headerlink" title="第 2 步:创建传输器(Transporter)"></a><strong>第 2 步:创建传输器(Transporter)</strong></h3><p>配置 SMTP 服务器连接信息:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> transport = nodemailer.<span class="hljs-title function_">createTransport</span>({<br> <span class="hljs-attr">host</span>: <span class="hljs-string">'smtp.qq.com'</span>,<span class="hljs-comment">// ← 1. 指定 SMTP 服务器地址</span><br> <span class="hljs-attr">port</span>: <span class="hljs-number">465</span>,<span class="hljs-comment">// ← 2. 使用 SMTPS(SMTP over SSL)端口</span><br> <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,<span class="hljs-comment">// ← 3. 启用 TLS/SSL 加密</span><br> <span class="hljs-attr">auto</span>: {<br> <span class="hljs-attr">user</span>: <span class="hljs-string">'your_email@example.com'</span>,<span class="hljs-comment">// ← 4. 身份认证:邮箱账号</span><br> <span class="hljs-attr">pass</span>: <span class="hljs-string">'your_smtp_auth_code'</span><span class="hljs-comment">// ← 5. 身份认证:授权码(非登录密码!)</span><br> }<br>})<br></code></pre></td></tr></table></figure><blockquote><p>🔐 <strong>授权码获取方式</strong>(以 QQ 邮箱为例):</p><ol><li>登录 QQ 邮箱 → 设置 → 账户</li><li>开启 “POP3/SMTP 服务” → 按提示生成 <strong>16 位授权码</strong></li></ol></blockquote><hr><h3 id="第-3-步:定义邮件内容(Mail-Options)"><a href="#第-3-步:定义邮件内容(Mail-Options)" class="headerlink" title="第 3 步:定义邮件内容(Mail Options)"></a><strong>第 3 步:定义邮件内容(Mail Options)</strong></h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> mailOptions = {<br> <span class="hljs-attr">from</span>: <span class="hljs-string">'"Your App" <your_email@qq.com>'</span>, <span class="hljs-comment">// 发件人(必须与 auth.user 一致或别名)</span><br> <span class="hljs-attr">to</span>: <span class="hljs-string">'recipient@example.com'</span>, <span class="hljs-comment">// 收件人(可多个:'a@x.com, b@y.com')</span><br> <span class="hljs-attr">subject</span>: <span class="hljs-string">'验证码'</span>, <span class="hljs-comment">// 邮件标题</span><br> <span class="hljs-attr">text</span>: <span class="hljs-string">'您的验证码是:123456'</span>, <span class="hljs-comment">// 纯文本正文</span><br> <span class="hljs-comment">// html: '<b>您的验证码是:123456</b>' // HTML 正文(可选,优先级高于 text)</span><br>};<br></code></pre></td></tr></table></figure><blockquote><p>📌 关键规则:</p><ul><li><code>from</code> 中的邮箱地址 <strong>必须与 <code>auth.user</code> 一致</strong>,否则会被 SMTP 服务器拒绝。</li><li>可同时提供 <code>text</code> 和 <code>html</code>,客户端会优先显示 <code>html</code>。</li></ul></blockquote><hr><h3 id="第-4-步:发送邮件(异步)"><a href="#第-4-步:发送邮件(异步)" class="headerlink" title="第 4 步:发送邮件(异步)"></a><strong>第 4 步:发送邮件(异步)</strong></h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">sendEmail</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-keyword">const</span> info = <span class="hljs-keyword">await</span> transporter.<span class="hljs-title function_">sendMail</span>(mailOptions);<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'Message sent: %s'</span>, info.<span class="hljs-property">messageId</span>);<br> <span class="hljs-comment">// 示例输出: Message sent: <b658f8ca-4d9e-11ea-8f00-0a0b12345678@qq.com></span><br> } <span class="hljs-keyword">catch</span> (error) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'Send email failed:'</span>, error);<br> <span class="hljs-comment">// 常见错误:认证失败、网络超时、配额超限</span><br> }<br>}<br><br><span class="hljs-title function_">sendEmail</span>();<br></code></pre></td></tr></table></figure><blockquote><p>💡 返回的 <code>info</code> 包含邮件 ID、响应状态等,可用于日志或追踪。</p></blockquote><hr><h3 id="第-5-步(可选):验证-SMTP-配置是否有效"><a href="#第-5-步(可选):验证-SMTP-配置是否有效" class="headerlink" title="第 5 步(可选):验证 SMTP 配置是否有效"></a><strong>第 5 步(可选):验证 SMTP 配置是否有效</strong></h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 测试连接(不发邮件)</span><br>transporter.<span class="hljs-title function_">verify</span>(<span class="hljs-function">(<span class="hljs-params">error, success</span>) =></span> {<br> <span class="hljs-keyword">if</span> (error) {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'SMTP config error:'</span>, error);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'SMTP server is ready to take messages'</span>);<br> }<br>});<br></code></pre></td></tr></table></figure><hr><h2 id=""><a href="#" class="headerlink" title=""></a></h2>]]></content>
<categories>
<category>第三方库</category>
</categories>
</entry>
<entry>
<title>rpc 的使用</title>
<link href="/2026/01/30/rpc%20%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
<url>/2026/01/30/rpc%20%E7%9A%84%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<h1 id="RPC-介绍"><a href="#RPC-介绍" class="headerlink" title="RPC 介绍"></a><code>RPC</code> 介绍</h1><h2 id="核心思想:"><a href="#核心思想:" class="headerlink" title="核心思想:"></a>核心思想:</h2><blockquote><p>让调用远程服务像调用本地函数一样简单</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 本地函数调用</span><br><span class="hljs-type">int</span> result = <span class="hljs-built_in">local_add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>);<br><span class="hljs-comment">// RPC 调用 - 看起来一样,但实际在远程执行</span><br><span class="hljs-type">int</span> result = <span class="hljs-built_in">remote_add</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>); <span class="hljs-comment">// 这个函数在另一台机器上执行</span><br></code></pre></td></tr></table></figure><p>解决了分布式系统中的服务间通信,同时隐藏了网络通信的复杂性,提供了类型安全的远程调用</p><h2 id="RPC-基本组件"><a href="#RPC-基本组件" class="headerlink" title="RPC 基本组件"></a><code>RPC</code> 基本组件</h2><p>一个完整的 RPC 系统通常包含以下三个层次:</p><ol><li><strong>接口定义语言(IDL, Interface Definition Language)</strong><br>—— 描述服务方法和数据结构。</li><li><strong>数据序列化格式</strong><br>—— 将结构化数据转换为可传输的字节流。</li><li><strong>网络通信协议</strong><br>—— 负责请求/响应的传输、连接管理、错误处理等。</li></ol><p><code>protobuf</code> 能解决前两个问题,比如</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs protobuf"><span class="hljs-comment">// 定义接口</span><br><span class="hljs-keyword">service </span><span class="hljs-title class_">UserService</span> {<span class="hljs-comment">// 服务接口定义</span><br> <span class="hljs-function"><span class="hljs-keyword">rpc</span> GetUser(GetUserReq) <span class="hljs-keyword">returns</span> (UserRsp)</span>;<span class="hljs-comment">// 远程方法定义</span><br> <span class="hljs-function"><span class="hljs-keyword">rpc</span> CreateUser(CreateUserReq) <span class="hljs-keyword">returns</span> (UserRsp)</span>;<br>}<br><span class="hljs-keyword">message </span><span class="hljs-title class_">GetUserReq</span> { <span class="hljs-type">int32</span> user_id = <span class="hljs-number">1</span>; }<br></code></pre></td></tr></table></figure><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs protobuf"><span class="hljs-comment">// Protobuf 提供序列化方法</span><br>GetUserReq request;<br>request.set_user_id(<span class="hljs-number">123</span>);<br>std::<span class="hljs-type">string</span> serialized_data = request.SerializeAsString();<br><br><span class="hljs-comment">// 反序列化</span><br>GetUserReq new_request;<br>new_request.ParseFromString(serialized_data);<br></code></pre></td></tr></table></figure><p>而 <code>rpc</code> 框架通常能自己解决第三层网络协议</p><ul><li><code>RPC</code> 系统分层</li></ul><p>第一层:接口描述文件层(IDL Layer),通常用 <code>.proto</code> 来定义</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs protobuf"><span class="hljs-comment">// user_service.proto - 接口描述层</span><br>syntax = <span class="hljs-string">"proto3"</span>;<br><br><span class="hljs-keyword">service </span><span class="hljs-title class_">UserService</span> {<br> <span class="hljs-function"><span class="hljs-keyword">rpc</span> GetUser(GetUserRequest) <span class="hljs-keyword">returns</span> (UserResponse)</span>;<br> <span class="hljs-function"><span class="hljs-keyword">rpc</span> CreateUser(CreateUserRequest) <span class="hljs-keyword">returns</span> (UserResponse)</span>;<br>}<br><br><span class="hljs-keyword">message </span><span class="hljs-title class_">GetUserRequest</span> {<br> <span class="hljs-type">int32</span> user_id = <span class="hljs-number">1</span>;<br>}<br><br><span class="hljs-keyword">message </span><span class="hljs-title class_">UserResponse</span> {<br> <span class="hljs-type">int32</span> user_id = <span class="hljs-number">1</span>;<br> <span class="hljs-type">string</span> name = <span class="hljs-number">2</span>;<br> <span class="hljs-type">string</span> email = <span class="hljs-number">3</span>;<br>}<br></code></pre></td></tr></table></figure><p>第二层:<code>RPC</code> 协议层(Protocol Layer),通常由 <code>protoc.exe</code>配合 <code>RPC</code> 的插件使用,生成的是比如 <code>.grpc.pb.cc</code> 和 <code>.grpc.pb.h</code>文件</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 由 protoc 生成的代码 - RPC 协议层</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService_Stub</span> : <span class="hljs-keyword">public</span> ::google::protobuf::Service {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 客户端存根 - 封装调用细节</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">GetUser</span><span class="hljs-params">(::google::protobuf::RpcController* controller,</span></span><br><span class="hljs-params"><span class="hljs-function"> <span class="hljs-type">const</span> GetUserRequest* request,</span></span><br><span class="hljs-params"><span class="hljs-function"> UserResponse* response,</span></span><br><span class="hljs-params"><span class="hljs-function"> ::google::protobuf::Closure* done)</span></span>;<br> <br> <span class="hljs-comment">// 服务端骨架 - 提供实现基类 </span><br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">GetUser</span><span class="hljs-params">(::google::protobuf::RpcController* controller,</span></span><br><span class="hljs-params"><span class="hljs-function"> <span class="hljs-type">const</span> GetUserRequest* request,</span></span><br><span class="hljs-params"><span class="hljs-function"> UserResponse* response,</span></span><br><span class="hljs-params"><span class="hljs-function"> ::google::protobuf::Closure* done)</span> </span>= <span class="hljs-number">0</span>;<br>};<br></code></pre></td></tr></table></figure><p>第三层:网络通信层(Transport Layer),这一层通常自己实现</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs protobuf"><span class="hljs-comment">// gRPC 网络通信层</span><br>class GrpcTransport {<br>public:<br> <span class="hljs-comment">// 建立连接</span><br> <span class="hljs-type">bool</span> Connect(const std::<span class="hljs-type">string</span>& endpoint);<br> <span class="hljs-comment">// 发送请求</span><br> <span class="hljs-type">bool</span> SendRequest(const std::<span class="hljs-type">string</span>& method_name, <br> const std::<span class="hljs-type">string</span>& serialized_data);<br> <span class="hljs-comment">// 接收响应</span><br> std::<span class="hljs-type">string</span> ReceiveResponse();<br> <span class="hljs-comment">// 处理网络错误、超时、重试等</span><br> void HandleErrors();<br>};<br></code></pre></td></tr></table></figure><h2 id="使用-grpc-protobuf-的完整流程"><a href="#使用-grpc-protobuf-的完整流程" class="headerlink" title="使用 grpc + protobuf 的完整流程"></a>使用 <code>grpc</code> + <code>protobuf</code> 的完整流程</h2><p>Protocol Buffers 是一种接口定义语言(IDL)和高效的序列化框架,它提供了<strong>跨语言</strong>的<strong>数据结构</strong>和<strong>服务接口定义</strong>能力。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">Protocol Buffers = <br>├── 接口定义语言 (IDL) <span class="hljs-comment">// 定义数据结构和服务契约</span><br>└── 序列化框架 <span class="hljs-comment">// 提供高效的二进制序列化</span><br></code></pre></td></tr></table></figure><p>gRPC 是基于 Protobuf 构建的一个高性能 RPC 框架,基于 HTTP/2 协议构建,但不仅仅 “协议” 本身,而是一个 “面向服务的通信框架”。专为微服务设计。</p><table><thead><tr><th>层级</th><th>技术</th></tr></thead><tbody><tr><td>传输层</td><td>TCP</td></tr><tr><td>应用层协议</td><td>HTTP/2</td></tr><tr><td>序列化格式</td><td>Protocol Buffers</td></tr><tr><td>RPC 框架</td><td>gRPC(封装了上述三层)</td></tr></tbody></table><p>而 Protobuf 本身还可以作为许多其他系统(如消息队列、API 网关、配置管理等)的<strong>基础数据定义格式</strong>,基础用法如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 用 .proto 定义接口以后,<br><span class="hljs-number">2.</span> 使用 .protoc 配合对应框架的插件直接生成代码<br><span class="hljs-number">3.</span> 将代码代码添加进项目中,在项目中直接使用生成的类。<br></code></pre></td></tr></table></figure><p>步骤 1:编写 <code>.proto</code> 文件(定义服务契约)</p><p>首先序列化生成数据结构(<code>.proto</code>文件不依赖任何特定编程语言),通常写作:</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs protobuf">syntax = <span class="hljs-string">"proto3"</span>;<br><span class="hljs-keyword">package</span> example;<br><br><span class="hljs-keyword">service </span><span class="hljs-title class_">Calculator</span> {<br> <span class="hljs-function"><span class="hljs-keyword">rpc</span> Add(AddRequest) <span class="hljs-keyword">returns</span> (AddResponse)</span>;<br>}<br><br><span class="hljs-keyword">message </span><span class="hljs-title class_">AddRequest</span> { <span class="hljs-type">int32</span> a = <span class="hljs-number">1</span>; <span class="hljs-type">int32</span> b = <span class="hljs-number">2</span>; }<br><span class="hljs-keyword">message </span><span class="hljs-title class_">AddResponse</span> { <span class="hljs-type">int32</span> result = <span class="hljs-number">1</span>; }<br></code></pre></td></tr></table></figure><p>步骤 2:用 <code>protoc</code> 生成代码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">protoc --cpp_out=. calculator.proto -> calculator.pb.h, calculator.pb.cc<br></code></pre></td></tr></table></figure><p>再利用插件生成 grpc 服务框架(插件保证了 protobuf 可以为不同用途生成不同代码)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">protoc --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe calculator.proto <br>-> calculator.grpc.pb.h, calculator.grpc.pb.cc<br></code></pre></td></tr></table></figure><p>输出文件:</p><ul><li><code>calculator.pb.h/cc</code> → 数据结构</li><li><code>calculator.grpc.pb.h/cc</code> →服务接口(Stub + Service)</li></ul><p>步骤 3 :在项目中使用生成代码:</p><p>客户端:通过 <code>Stub</code> 发起远程调用;</p><p>服务端:继承 <code>Service</code> 类并实现具体方法;</p>]]></content>
<categories>
<category>第三方库</category>
</categories>
</entry>
<entry>
<title>shared_from_this</title>
<link href="/2026/01/30/shared_from_this/"/>
<url>/2026/01/30/shared_from_this/</url>
<content type="html">< {<br> <span class="hljs-comment">// 即使外部不再持有 self,这个 lambda 也会 keep-alive 整个连接对象</span><br> self-><span class="hljs-built_in">HandleRequest</span>(); <span class="hljs-comment">// 安全调用成员函数</span><br> });<br>}<br></code></pre></td></tr></table></figure><h3 id="1-2-原理"><a href="#1-2-原理" class="headerlink" title="1.2 原理"></a>1.2 原理</h3><p><code>shared_from_this()</code> 是 <code>std::enable_shared_from_this<T></code> 提供的一个成员函数。调用 <code>shared_from_this()</code> 返回的 <code>shared_ptr</code> 与原始 <code>shared_ptr</code> <strong>共享同一块控制块(control block)</strong>,因此:</p><ul><li>引用计数 +1;</li><li>只要还有任何一个 <code>shared_ptr</code>(包括 lambda 捕获的那个 <code>self</code>)未被销毁,对象就不会析构;</li></ul><p>为什么不直接写 <code>auto self = std::shared_ptr(this);</code>。因为会造成<strong>双重析构</strong>的风险:</p><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs angelscript"><span class="hljs-built_in">auto</span> bad_self = std::shared_ptr<CHttpConnection>(<span class="hljs-keyword">this</span>); <span class="hljs-comment">// 危险!</span><br></code></pre></td></tr></table></figure><p>这会创建一个<strong>全新的、独立的 <code>shared_ptr</code></strong>,它拥有自己的引用计数器。<br>但 <code>this</code> 已经被另一个 <code>shared_ptr</code>(比如 <code>make_shared</code> 创建的那个)管理了。</p><p>→ 当这两个 <code>shared_ptr</code> 各自析构时,都会尝试 <code>delete this</code>,导致 <strong>重复释放同一块内存</strong>,引发 <strong>崩溃或未定义行为</strong>。</p>]]></content>
<categories>
<category>C++ 新特性</category>
</categories>
</entry>
<entry>
<title>Boost.Asio 的使用</title>
<link href="/2026/01/29/Boost.Asio/"/>
<url>/2026/01/29/Boost.Asio/</url>
<content type="html"><![CDATA[<h1 id="Boost-Asio-库"><a href="#Boost-Asio-库" class="headerlink" title="Boost.Asio 库"></a>Boost.Asio 库</h1><h2 id="io-context"><a href="#io-context" class="headerlink" title="io_context"></a>io_context</h2><h3 id="1-概念"><a href="#1-概念" class="headerlink" title="1. 概念"></a>1. 概念</h3><p><code>io_context</code> 是 Asio 库的核心事件循环和 IO 调度器,也是所有异步 I/O 事件处理的基础。工作逻辑为</p><ul><li><p>将需要监听的 IO 事件(如 socket 的读/写就绪、连接请求就绪)和对应的处理回调注册到 <code>io_context</code> 的事件管理体系中</p></li><li><p>启用 <code>io_context</code> 的事件循环以后,会持续通过底层多路复用的机制检测已注册的事件状态……</p></li><li><p>当检测到某个 IO 事件就绪以后,会自动调用执行该事件对应的上层回调函数,完成 IO 事件的处理</p></li></ul><h3 id="2-和-epoll-iocp-的关系"><a href="#2-和-epoll-iocp-的关系" class="headerlink" title="2. 和 epoll/iocp 的关系"></a>2. 和 epoll/iocp 的关系</h3><p><code>io_context</code> 是跨平台抽象层,epoll 只是其在 Linux 下的一种底层实现,类似</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">【上层:开发者代码】→ 【中间层:io_context(事件调度)】→ 【底层:操作系统IO多路复用(epoll/IOCP)】<br></code></pre></td></tr></table></figure><p>用代码实现的话</p><ul><li><p>注册:将代码中 <code>tcp::acceptor</code>(监听套接字)、<code>tcp::socket</code>(通信套接字)和各种事件(端口监听,socket 读/写)等事件<strong>注册</strong>到 io_context,同时绑定自定义回调;</p></li><li><p>传递:<code>io_context</code> 接收到注册请求后,会将对应的 socket 句柄、事件类型(读 / 写 / 连接)<strong>传递给底层的 epoll</strong>,完成 epoll 的事件注册;</p></li><li><p>启动:调用 <code>io_context.run()</code> 启动事件循环,<code>io_context</code> 会调用 epoll 的 <code>epoll_wait</code> 方法,阻塞等待内核的事件就绪通知</p></li><li><p>响应处理:有事件就绪,内核通过 epoll 向 <code>io_context</code> 发送就绪通知,然后调度执行开发者注册的上层回调函数</p></li></ul><h3 id="3-核心特点"><a href="#3-核心特点" class="headerlink" title="3. 核心特点"></a>3. 核心特点</h3><p><strong>跨平台性</strong>:若你的服务端移植到 Windows,<code>io_context</code> 会自动切换为基于 IOCP 实现,<strong>你的上层代码无需任何修改</strong>,这是 <code>io_context</code> 最核心的价值(屏蔽了不同系统的底层 IO 差异);</p><p><strong>不止封装 epoll</strong>:<code>io_context</code> 除了封装 IO 多路复用,还实现了<strong>事件调度、回调管理、线程池适配</strong>等功能,而 epoll 仅负责内核态的 IO 事件检测,无上层调度能力;</p>]]></content>
<categories>
<category>第三方库</category>
</categories>
</entry>
<entry>
<title>Json 序列化</title>
<link href="/2026/01/28/Json%20%E5%BA%8F%E5%88%97%E5%8C%96/"/>
<url>/2026/01/28/Json%20%E5%BA%8F%E5%88%97%E5%8C%96/</url>
<content type="html"><![CDATA[<h1 id="Qt"><a href="#Qt" class="headerlink" title="Qt"></a>Qt</h1><p>序列化:内存中的结构化数据 -> 可存储/网络传输的字符串/字节流(Json 格式或者其它格式)</p><p>反序列化:文件/网络中读取的 Json 字符串/字节流 -> 还原成内存中的结构化数据</p><p>Qt 内置了 Json 的处理,核心流程可以概括为:</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs pgsql">内存对象(QJsonObject)⇌ <span class="hljs-type">Json</span> 文档(QJsonDocument)⇌ 字节数组(QByteArray)⇌ 文件/网络<br></code></pre></td></tr></table></figure><h3 id="Json-序列化:"><a href="#Json-序列化:" class="headerlink" title="Json 序列化:"></a><code>Json</code> 序列化:</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> QJsonObject: 通过 insert 修改 QJsonObject 里面的键值对<br> m_config.<span class="hljs-built_in">insert</span>(<span class="hljs-string">"user"</span>, <span class="hljs-string">"test"</span>);<br>m_config.<span class="hljs-built_in">insert</span>(<span class="hljs-string">"auto"</span>, <span class="hljs-literal">true</span>);<br><span class="hljs-number">2.</span> QJSonDocument: 直接作为参数构造<br> <span class="hljs-function">QJsonDocument <span class="hljs-title">doc</span><span class="hljs-params">(m_config)</span></span><br><span class="hljs-function">3. QByteArray: 通过 toJson 将文档转换为 JSON 格式的字符串</span><br><span class="hljs-function"> QByteArray jsonData =</span> doc.<span class="hljs-built_in">toJson</span>();<br><span class="hljs-number">4.</span> 文件或者网络处理<br></code></pre></td></tr></table></figure><h3 id="Json-反序列化:"><a href="#Json-反序列化:" class="headerlink" title="Json 反序列化:"></a><code>Json</code> 反序列化:</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 文件或者网络处理: 文件的话通过 readAll 来提取字符串<br>QByteArray data = file.<span class="hljs-built_in">readAll</span>();<br><span class="hljs-number">2.</span> QJsonDocument: 通过 fromJson 提取文档内容<br> QJsonParseError error;<br> QJsonDocument doc = QJsonDocument::<span class="hljs-built_in">fromJson</span>(data, &error);<br><span class="hljs-number">3.</span> QJsonObject: 通过 <span class="hljs-built_in">object</span>() 直接提取<br> QJsonObject m_config = doc.<span class="hljs-built_in">object</span>();<br><span class="hljs-number">4.</span> 提取完毕<br></code></pre></td></tr></table></figure><h1 id="Jsoncpp"><a href="#Jsoncpp" class="headerlink" title="Jsoncpp"></a>Jsoncpp</h1><p>Jsoncpp 是第三方库,给他编译配置项目中即可,主要介绍一下使用的方法</p><p>首先 Jsoncpp 里面比较重要的是 <code>Json::Value</code> 和 <code>Json::Reader</code></p><h3 id="Json-序列化"><a href="#Json-序列化" class="headerlink" title="Json 序列化"></a><code>Json</code> 序列化</h3><p>主要利用 <code>Json::value.toStyleString</code> 接口</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++">jsonSend[<span class="hljs-string">"email"</span>] = email;<br>jsonSend[<span class="hljs-string">"error"</span>] = ErrorCodes::SUCCESS;<br>std::string jsonstr = jsonSend.<span class="hljs-built_in">toStyledString</span>();<br></code></pre></td></tr></table></figure><h3 id="Json-反序列化"><a href="#Json-反序列化" class="headerlink" title="Json 反序列化"></a><code>Json</code> 反序列化</h3><p>主要是利用 <code>Json::Reader.parse</code> 接口</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">std::string body_str = beast::<span class="hljs-built_in">buffers_to_string</span>(m_request.<span class="hljs-built_in">body</span>().<span class="hljs-built_in">data</span>());<br>Json::Value jsonRecv;<br>Json::Reader reader;<br><span class="hljs-comment">// 反序列化</span><br><span class="hljs-type">bool</span> success = reader.<span class="hljs-built_in">parse</span>(body_str, jsonRecv);<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>第三方库</category>
</categories>
</entry>
<entry>
<title>网络通信协议</title>
<link href="/2026/01/28/%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE/"/>
<url>/2026/01/28/%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AE/</url>
<content type="html"><![CDATA[<h1 id="网络通信协议"><a href="#网络通信协议" class="headerlink" title="网络通信协议"></a>网络通信协议</h1><h2 id="Tcp-协议"><a href="#Tcp-协议" class="headerlink" title="Tcp 协议"></a>Tcp 协议</h2><p>TCP 是<strong>传输层</strong>的可靠字节流通信协议,为 HTTP/HTTPS/WebSocket 等应用层协议提供<strong>底层传输支撑</strong>,核心解决「数据如何有序、无丢失、无重复地在两台设备间传输」的问题。</p><p>其连接的持续时间完全由上层应用(开发者)决定</p><ul><li><strong>核心特点</strong></li></ul><ol><li>面向连接:通信前需通过「三次握手」建立连接,通信结束后通过「四次挥手」关闭连接;</li><li>可靠传输:通过序列号、确认应答(ACK)、重传机制保证数据不丢失、不重复、有序到达;</li><li>字节流传输:无固定应用层格式,仅传输<strong>连续字节流</strong>,需上层协议<strong>自定义</strong>数据边界;</li><li>拥塞控制:采用慢启动、拥塞避免、快重传、快恢复来防止网络拥塞</li></ol><ul><li><strong>数据传输格式</strong></li></ul><p>TCP 本身无固定报文格式,需开发者自定义「消息头 + 消息体」区分数据边界(解决字节流粘包 / 拆包问题):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 自定义TCP报文结构(通用设计)</span><br><span class="hljs-number">1.</span> 消息头(固定长度):<span class="hljs-number">4</span>字节(消息总长度) + <span class="hljs-number">2</span>字节(消息类型) + <span class="hljs-number">1</span>字节(版本号)<br><span class="hljs-number">2.</span> 消息体(可变长度):实际传输的业务数据(长度由消息头指定)<br></code></pre></td></tr></table></figure><ul><li><strong>使用场景</strong></li></ul><ol><li>需自定义协议的高性能场景(如游戏服务器、物联网设备)</li><li>作为 HTTP/HTTPS/WebSocket 的底层传输基础;</li></ol><h2 id="Http-协议"><a href="#Http-协议" class="headerlink" title="Http 协议"></a>Http 协议</h2><p>Http 是客户端和服务器之间通信的协议。一个完整的 Http 请求包含:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 请求行:方法 + URL + 协议版本<br><span class="hljs-number">2.</span> 请求头:元数据<br><span class="hljs-number">3.</span> 请求体:实际传输的数据(GET 请求通常没有) <br></code></pre></td></tr></table></figure><p>应用层协议,Http 1.0 默认是短连接,1.1 就变成了默认的长连接(核心改进)</p><p>Http 请求如下所示,一般来说可以用 url 来构造 http 请求(Content-Type/Content-Length 这种没在 url 里面的就手动设置):</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs http"><!-- 请求行 --><br>POST /api/orders HTTP/1.1<br><!-- 请求头 --> <br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>api.shop.com<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/json<br><span class="hljs-attribute">Authorization</span><span class="hljs-punctuation">: </span>Bearer token123<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>MyApp/1.0<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>87<br><br><!-- 请求体 Json 格式 --> <br>{<br> "productId": "12345",<br> "quantity": 2,<br> "customer": {<br> "name": "张三",<br> "address": "北京市"<br> }<br>}<br></code></pre></td></tr></table></figure><p>Http 响应:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs http"><!-- 响应行 --><br><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">201</span> Created<br><!-- 响应头 --><br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/json<br><span class="hljs-attribute">Date</span><span class="hljs-punctuation">: </span>Mon, 23 Oct 2023 10:00:00 GMT<br><br><span class="language-dust"><span class="language-xml"><span class="hljs-comment"><!-- 响应头 --></span></span></span><br><span class="language-xml"><span class="language-dust"></span><span class="hljs-template-variable">{</span></span><br><span class="hljs-template-variable"><span class="language-dust"> "id": "order_67890",</span></span><br><span class="hljs-template-variable"><span class="language-dust"> "status": "created",</span></span><br><span class="hljs-template-variable"><span class="language-dust"> "createdAt": "2023-10-23T10:00:00Z"</span></span><br><span class="hljs-template-variable"><span class="language-dust">}</span></span><br></code></pre></td></tr></table></figure><ul><li><strong>请求类型</strong></li></ul><ol><li><code>Get</code>- 获取数据</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++">GET /api/users/<span class="hljs-number">123</span> HTTP/<span class="hljs-number">1.1</span><br>Host: example.com<br></code></pre></td></tr></table></figure><p>用于获取服务器的资源</p><ol start="2"><li><code>Post</code> - 创建数据</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++">POST /api/users HTTP/<span class="hljs-number">1.1</span><br>Host: example.com<br>Content-Type: application/json<br><br>{<span class="hljs-string">"name"</span>: <span class="hljs-string">"张三"</span>, <span class="hljs-string">"email"</span>: <span class="hljs-string">"zhang@example.com"</span>}<br></code></pre></td></tr></table></figure><p>用于在服务器当中<strong>创建</strong>新资源</p><ol start="3"><li><code>PUT</code>:更新完整资源</li><li><code>Delete</code>: 删除资源</li></ol><h2 id="Https-协议"><a href="#Https-协议" class="headerlink" title="Https 协议"></a>Https 协议</h2><p>HTTPS 是「HTTP + TLS/SSL 加密」的组合协议,本质是加密版的 HTTP,基于 TCP 传输,核心解决 HTTP 明文传输的安全问题(防窃听、防篡改、防冒充)。</p><p>应用层协议,Http 1.0 默认是短连接,1.1 就变成了默认的长连接(核心改进)</p><ul><li><strong>核心特性</strong></li></ul><ol><li>加密传输:通过 TLS/SSL 对 HTTP 数据加密,传输过程中无法被破解;</li><li>身份验证:通过数字证书验证服务器合法性,防止访问钓鱼网站;</li><li>兼容 HTTP:请求 / 响应格式与 HTTP 一致,仅增加 TLS 加密层;</li><li>基于 TCP:先完成 TCP 三次握手,再完成 TLS 握手,最后传输加密数据;</li><li>默认端口:443(HTTP 默认 80)。</li></ol><ul><li><strong>TLS 握手核心流程</strong></li></ul><ol><li><p>客户端发送:TLS 版本、加密套件列表、随机数;</p></li><li><p>服务器返回:数字证书、选定的加密套件、随机数;</p></li><li><p>客户端验证证书,生成「预主密钥」并加密发送给服务器;</p></li><li><p>双方基于随机数 + 预主密钥生成「会话密钥」;</p></li><li><p>确认加密通道建立,后续 HTTP 数据均用会话密钥加密。</p></li></ol><ul><li><strong>Https 请求/响应示例</strong></li></ul><p>HTTPS 报文结构与 HTTP 一致,仅传输过程为密文,以下是「明文视角」的示例:</p><p>请求:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs http"><!-- 请求行 --><br><span class="hljs-keyword">POST</span> <span class="hljs-string">/api/pay</span> <span class="hljs-meta">HTTP/1.1</span><br><!-- 请求头(明文传输,用于TLS握手和基础标识) --><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>pay.example.com<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/json<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>78<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>MyApp/1.0<br><!-- 请求体(传输时为密文,明文如下) --><br>{<br> "orderId": "order_67890",<br> "amount": 99.00,<br> "payType": "wechat"<br>}<br></code></pre></td></tr></table></figure><p>响应:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">200</span> OK<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/json<br><span class="hljs-attribute">Date</span><span class="hljs-punctuation">: </span>Mon, 23 Oct 2023 10:05:00 GMT<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>65<br><!-- 响应体(传输时为密文,明文如下) --><br>{<br> "payId": "pay_123456",<br> "status": "success",<br> "payTime": "2023-10-23T10:05:00Z"<br>}<br></code></pre></td></tr></table></figure><p>数据加密避免请求 / 响应被中间人窃听</p><h2 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h2><p>WebSocket 是应用层的双向实时通信协议,基于 TCP 构建,通过 HTTP 握手升级为持久化长连接,核心解决 HTTP「客户端主动请求、服务端被动响应」的单向通信限制。</p><p>应用层协议,<strong>默认且唯一的使用方式是长连接</strong></p><ul><li><strong>核心特性</strong></li></ul><ol><li>双向通信:连接建立后,客户端 / 服务器可主动、实时发送数据(全双工);</li><li>持久化长连接:一次握手后连接保持,直到主动关闭(无需重复建立 TCP 连接);</li><li>轻量级:数据帧格式简洁,无 HTTP 冗余头信息,传输开销远低于 HTTP;</li><li>基于 HTTP 握手:首次连接兼容现有 HTTP 服务器,便于穿透防火墙;</li><li>默认端口:80(WebSocket)/443(WSS,加密版 WebSocket)。</li></ol><ul><li><strong>通信流程</strong></li></ul><ol><li>客户端发送 HTTP 升级请求(握手);</li><li>服务器响应升级确认,连接转为 WebSocket;</li><li>双方通过 WebSocket 帧双向传输数据;</li><li>任意一方发送关闭帧,结束连接。</li></ol><ul><li><strong>报文格式示例</strong></li></ul><p>WebSocket 握手请求(客户端 -> 服务器):</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/ws/chat</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>chat.example.com<br><span class="hljs-attribute">Upgrade</span><span class="hljs-punctuation">: </span>websocket <!-- 核心:请求升级为WebSocket --><br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>Upgrade <!-- 确认连接升级 --><br><span class="hljs-attribute">Sec-WebSocket-Key</span><span class="hljs-punctuation">: </span>dGhlIHNhbXBsZSBub25jZQ== <!-- 随机密钥,用于验证 --><br><span class="hljs-attribute">Sec-WebSocket-Version</span><span class="hljs-punctuation">: </span>13 <!-- 固定版本 --><br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>MyChatApp/1.0<br></code></pre></td></tr></table></figure><p>WebSocket 握手响应</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-meta">HTTP/1.1</span> <span class="hljs-number">101</span> Switching Protocols <!-- <span class="hljs-number">101</span>状态码表示协议切换 --><br><span class="hljs-attribute">Upgrade</span><span class="hljs-punctuation">: </span>websocket<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>Upgrade<br><span class="hljs-attribute">Sec-WebSocket-Accept</span><span class="hljs-punctuation">: </span>s3pPLMBiTxaQ9kYGzzhZRbK+xOo= <!-- 密钥验证结果 --><br></code></pre></td></tr></table></figure><p>WebSocket 数据帧</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-comment">// 客户端发送文本帧</span><br>{<span class="hljs-string">"type"</span>:<span class="hljs-string">"chat"</span>,<span class="hljs-string">"content"</span>:<span class="hljs-string">"你好,服务器!"</span>,<span class="hljs-string">"userId"</span>:<span class="hljs-number">123</span>}<br><br><span class="hljs-comment">// 服务器推送文本帧</span><br>{<span class="hljs-string">"type"</span>:<span class="hljs-string">"broadcast"</span>,<span class="hljs-string">"content"</span>:<span class="hljs-string">"用户123发送了新消息"</span>,<span class="hljs-string">"time"</span>:<span class="hljs-string">"2023-10-23 10:10:00"</span>}<br></code></pre></td></tr></table></figure><ul><li><strong>使用场景</strong></li></ul><p>实时聊天(网页版微信、直播弹幕)</p><p>实时数据监控(股票行情、物联网设备数据推送)</p>]]></content>
<categories>
<category>计算机网络</category>
</categories>
</entry>
<entry>
<title>银行科技岗一览</title>
<link href="/2026/01/21/%E9%93%B6%E8%A1%8C%E7%A7%91%E6%8A%80%E5%B2%97%E4%B8%80%E8%A7%88/"/>
<url>/2026/01/21/%E9%93%B6%E8%A1%8C%E7%A7%91%E6%8A%80%E5%B2%97%E4%B8%80%E8%A7%88/</url>
<content type="html"><![CDATA[<h1 id="银行科技岗"><a href="#银行科技岗" class="headerlink" title="银行科技岗"></a>银行科技岗</h1><h2 id="工作类型"><a href="#工作类型" class="headerlink" title="工作类型"></a>工作类型</h2><ul><li><strong>金融科技</strong></li></ul><p>工作内容不涉及技术相关工作,主要写材料,项目管理(算是大甲方)</p><p>岗位集中在<strong>总行</strong>,省行有少数岗位</p><ul><li><strong>软件开发</strong></li></ul><p>典型的开发(前后端,测试等产品开发工作)</p><p>岗位于省行(比较少),软件中心/研发中心,金科(外包) ,都有开放</p><ul><li><strong>数据中心运维</strong></li></ul><p>工作内容和名字相同,就是做系统维护的工作</p><p>岗位于省行,数据中心,市分科技岗都有开放</p><p><strong>Ps. 金融科技 > 运维 > 测试 > 开发</strong></p><h2 id="工作部门"><a href="#工作部门" class="headerlink" title="工作部门"></a>工作部门</h2><ul><li><strong>T0 级</strong></li></ul><p>六大国有行和三大政策行总行,六大国有行就是中国银行、中国工商银行、中国建设银行、交通银行、中国邮政储蓄银行、中国农业银行。</p><p>三大政策行就是国家开放银行、中国进出口银行、中国农业发展银行。总行都在北京,毋庸置疑好, T0 级别</p><ul><li><strong>T0.5 级</strong></li></ul><p>二线城市地位高,待遇好,人民银行和公务员都差不多了。T0.5 级别</p><p>头部城商行(成都银行)、股份制银行(招商银行)的总行(在地方来说也算是待遇非常好的了)</p><ul><li><strong>T1 级</strong></li></ul><p>农邮,政策行的直属中心(数据中心、软开中心、研发中心)</p><p>工农建的省行科技岗、管培岗,没有直属中心待遇那么好,但是也没那么卷</p><ul><li><strong>T2 级</strong></li></ul><p>中交直属中心</p><p>中交邮省行科技岗(真科技)</p><p>招商分行,地方城商行总行,农商行分行</p><ul><li><strong>T3 级</strong></li></ul><p>金科公司,招银网络、民生科技等银行子公司</p><p>地方城商分行,农商行分行等等</p><p>市分行后台,营销等(柜员等等)</p><h2 id="备考建议"><a href="#备考建议" class="headerlink" title="备考建议"></a>备考建议</h2><pre><code class=" mermaid">graph LR网申 --> 笔试 --> 机试 --> 无领导讨论 --> 半结构面试</code></pre><p>网申:注意提前批,根据学历例行投递</p><p>笔试:提前在 App 上刷题,2 h 能做完就行</p><p>机试:直属中心一般都要机试,hot100 难度(可能没有)</p><p>无领导讨论:准备一下,政策行、管培、省行都有这个环节(可能没有)</p><p>半结构面试:根据个人履历进行提问</p>]]></content>
<categories>
<category>计算机求职</category>
</categories>
</entry>
<entry>
<title>烤面筋 Day 04</title>
<link href="/2026/01/16/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day04/"/>
<url>/2026/01/16/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day04/</url>
<content type="html"><![CDATA[<h1 id="烤面筋-Day-04"><a href="#烤面筋-Day-04" class="headerlink" title="烤面筋 Day 04"></a>烤面筋 Day 04</h1><h2 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h2><h3 id="1-单例模式是什么?有哪些单例模式?"><a href="#1-单例模式是什么?有哪些单例模式?" class="headerlink" title="1. 单例模式是什么?有哪些单例模式?"></a>1. 单例模式是什么?有哪些单例模式?</h3><p>答:单例模式是一种设计模式,旨在确保一个类在整个应用程序的生命周期当中只有一个实例,提供一个全局访问点来获取该实例。</p><p>有一般的单例模式(用 static 修饰成员函数和 static 的局部变量),饿汉式单例模式,懒汉式单例模式,还有就是个人比较常用的 CRTP (声明单例的通用模板类)</p><h3 id="2-分别介绍一下这几种单例模式?"><a href="#2-分别介绍一下这几种单例模式?" class="headerlink" title="2. 分别介绍一下这几种单例模式?"></a>2. 分别介绍一下这几种单例模式?</h3><ul><li><strong>一般单例模式</strong>:静态成员函数 + 静态局部变量</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">static</span> Singleton& <span class="hljs-title">GetInstance</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-comment">// 静态局部变量只会被初始化一次</span><br> <span class="hljs-type">static</span> Singleton instance;<br> <span class="hljs-keyword">return</span> instance;<br> }<br> ~<span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-built_in">Singleton</span>(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br> Singleton& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br>};<br><br></code></pre></td></tr></table></figure><ul><li><strong>饿汉式单例模式</strong>:静态成员指针变量 + cpp 文件定义</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> {<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-function"><span class="hljs-type">static</span> Singleton* <span class="hljs-title">GetInstance</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-keyword">if</span>(instance == <span class="hljs-literal">nullptr</span>) <br> instance = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Singleton</span>();<br> <span class="hljs-keyword">return</span> instance;<br> }<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-type">static</span> Singleton* instance;<br> <span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-built_in">Singleton</span>(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br> Singleton& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br>};<br></code></pre></td></tr></table></figure><p>同时在 cpp 文件中定义 instance 实例</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c++">Singleton* Singleton::instance = Singleton::<span class="hljs-built_in">GetInstance</span>();<br></code></pre></td></tr></table></figure><ul><li><strong>懒汉式单例模式</strong>: 智能指针 + <code>once_flag</code></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> {<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-function"><span class="hljs-type">static</span> std::shared_ptr<Singleton> <span class="hljs-title">GetInstance</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">static</span> std::once_flag flag;<br> <span class="hljs-comment">// flag 底层采用了原子性的原理,紧接的线程如果发现了 flag 已经被初始化,</span><br> <span class="hljs-comment">// 就不会执行 call_once 后面的可调用对象</span><br> std::<span class="hljs-built_in">call_once</span>(flag, [](){<br> <span class="hljs-comment">// 这里不能使用 std::make_shared 因为构造函数是私有的,外部作用域无法访问</span><br> instance = std::<span class="hljs-built_in">shared_ptr</span><Singleton>(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Singleton</span>());<br> });<br> <span class="hljs-keyword">return</span> instance;<br> }<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-type">static</span> std::shared_ptr<Singleton> instance;<br> <span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-built_in">Singleton</span>(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br> Singleton& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Singleton&) = <span class="hljs-keyword">delete</span>;<br>};<br></code></pre></td></tr></table></figure><h3 id="3-CRTP-是什么?能不能实现一下?"><a href="#3-CRTP-是什么?能不能实现一下?" class="headerlink" title="3. CRTP 是什么?能不能实现一下?"></a>3. CRTP 是什么?能不能实现一下?</h3><p>答:是一种将派生类作为模板参数传递给基类的技术,即一个类继承以自身为模板参数的基类。</p><p>基类代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> {<br><span class="hljs-keyword">protected</span>:<br> <span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-built_in">Singleton</span>(<span class="hljs-type">const</span> Singleton<T>& other) = <span class="hljs-keyword">delete</span>;<br> Singleton& <span class="hljs-keyword">operator</span>=(<span class="hljs-type">const</span> Singleton<T>& other) = <span class="hljs-keyword">delete</span>;<br> <span class="hljs-type">static</span> std::shared_ptr<T> _instance;<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">Singleton</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-function"><span class="hljs-type">static</span> std::shared_ptr<T> <span class="hljs-title">GetInstance</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">static</span> std::once_flag flag;<br> std::<span class="hljs-built_in">call_once</span>(flag, [&](){<br> _instance = std::<span class="hljs-built_in">shared_ptr</span><T>(<span class="hljs-keyword">new</span> T);<br> });<br> <span class="hljs-keyword">return</span> _instance;<br> }<br>};<br><br><span class="hljs-comment">// 注意模板类的 static 一定要在头文件中定义</span><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br>std::shared_ptr<T> Singleton<T>::_instance = <span class="hljs-literal">nullptr</span>;<br></code></pre></td></tr></table></figure><p>派生类代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">class</span> <span class="hljs-title class_">SingleNet</span> : <span class="hljs-keyword">public</span> Singleton<SingleNet> {<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-built_in">SingleNet</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-keyword">friend</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span><SingleNet>;<br><span class="hljs-keyword">public</span>:<br> ~<span class="hljs-built_in">SingleNet</span>() = <span class="hljs-keyword">default</span>;<br>};<br></code></pre></td></tr></table></figure><h2 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h2><h3 id="1-什么是观察者模式?"><a href="#1-什么是观察者模式?" class="headerlink" title="1. 什么是观察者模式?"></a>1. 什么是观察者模式?</h3><p>答:<strong>观察者模式</strong> 是一种行为设计模式。它定义了对象间的一种 <strong>一对多</strong> 的依赖关系,使得每当一个对象状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。</p><h3 id="2-你觉得里面的重点是什么?"><a href="#2-你觉得里面的重点是什么?" class="headerlink" title="2. 你觉得里面的重点是什么?"></a>2. 你觉得里面的重点是什么?</h3><ul><li><p><strong>解耦</strong>:被观察者(Subject)不需要知道观察者(Observer)的具体类,只需要知道它们实现了某个接口。</p></li><li><p><strong>触发联动</strong>:状态改变自动触发行为,不需要轮询检查。</p></li><li><p><strong>抽象依赖</strong>:Subject 依赖于 Observer 的抽象基类,符合开闭原则(对扩展开放,对修改关闭)。</p></li></ul><h3 id="3-实现一下观察者模式吧"><a href="#3-实现一下观察者模式吧" class="headerlink" title="3. 实现一下观察者模式吧"></a>3. 实现一下观察者模式吧</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><memory></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><algorithm></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><string></span></span><br><br><span class="hljs-comment">// 1. 抽象观察者</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Observer</span> : <span class="hljs-keyword">public</span> std::enable_shared_from_this<Observer> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-keyword">virtual</span> ~<span class="hljs-built_in">Observer</span>() = <span class="hljs-keyword">default</span>;<br> <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& state)</span> </span>= <span class="hljs-number">0</span>;<br>};<br><br><span class="hljs-comment">// 2. 被观察者(Subject)</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Subject</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 线程安全的订阅:使用 weak_ptr 记录观察者</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">attach</span><span class="hljs-params">(std::weak_ptr<Observer> observer)</span> </span>{<br> <span class="hljs-function">std::lock_guard<std::mutex> <span class="hljs-title">lock</span><span class="hljs-params">(_mtx)</span></span>;<br> _observers.<span class="hljs-built_in">push_back</span>(observer);<br> }<br><br> <span class="hljs-comment">// 状态更新并通知</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">setState</span><span class="hljs-params">(std::string state)</span> </span>{<br> std::vector<std::shared_ptr<Observer>> active_observers;<br> {<br> <span class="hljs-function">std::lock_guard<std::mutex> <span class="hljs-title">lock</span><span class="hljs-params">(_mtx)</span></span>;<br> _state = state;<br><br> <span class="hljs-comment">// 遍历并检查生命周期</span><br> <span class="hljs-keyword">auto</span> it = _observers.<span class="hljs-built_in">begin</span>();<br> <span class="hljs-keyword">while</span> (it != _observers.<span class="hljs-built_in">end</span>()) {<br> <span class="hljs-comment">// 尝试提升为 shared_ptr</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">auto</span> obj = it-><span class="hljs-built_in">lock</span>()) {<br> active_observers.<span class="hljs-built_in">push_back</span>(obj);<br> ++it;<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 观察者已销毁,自动清理从列表中剔除</span><br> it = _observers.<span class="hljs-built_in">erase</span>(it);<br> }<br> }<br> }<br><br> <span class="hljs-comment">// 在锁外进行通知,避免死锁风险及长时间占用锁</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span>& obs : active_observers) {<br> obs-><span class="hljs-built_in">update</span>(_state);<br> }<br> }<br><br><span class="hljs-keyword">private</span>:<br> std::string _state;<br> std::vector<std::weak_ptr<Observer>> _observers; <span class="hljs-comment">// 核心:弱引用</span><br> std::mutex _mtx; <span class="hljs-comment">// 核心:互斥锁保证线程安全</span><br>};<br><br><span class="hljs-comment">// 3. 具体观察者示例</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Worker</span> : <span class="hljs-keyword">public</span> Observer {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">Worker</span>(std::string name) : _name(name) {}<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-type">const</span> std::string& state)</span> <span class="hljs-keyword">override</span> </span>{<br> std::cout << <span class="hljs-string">"Worker "</span> << _name << <span class="hljs-string">" received: "</span> << state << std::endl;<br> }<br><span class="hljs-keyword">private</span>:<br> std::string _name;<br>};<br></code></pre></td></tr></table></figure><hr><h2 id="Inline-内联函数"><a href="#Inline-内联函数" class="headerlink" title="Inline 内联函数"></a>Inline 内联函数</h2><h3 id="1-内联函数的实现机制?"><a href="#1-内联函数的实现机制?" class="headerlink" title="1. 内联函数的实现机制?"></a>1. 内联函数的实现机制?</h3><p>答:内联函数是向编译器发出的一个<strong>建议</strong>,请求将函数调用替换为函数体本身。</p><p>目的是消除函数调用的开销(如压栈、跳转、返回等),对于频繁调用的小函数性能提升明显。</p><h3 id="2-编译器一定会内联吗?"><a href="#2-编译器一定会内联吗?" class="headerlink" title="2. 编译器一定会内联吗?"></a>2. 编译器一定会内联吗?</h3><p>答:不一定会内联。编译器会根据复杂的启发式算法自行决定。</p><p>如果函数过大或者过于复杂或者函数是虚函数(内联是编译时确定,虚函数是运行时确定的)的情况下,编译器会拒绝内联。同时现代的编译器非常聪明,即使没有写 <code>inline</code> 关键字,只要它认为有益且符合条件,也会自动进行优化内联</p>]]></content>
<categories>
<category>C++ 面经</category>
</categories>
</entry>
<entry>
<title>烤面筋 Day 03</title>
<link href="/2026/01/16/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day03/"/>
<url>/2026/01/16/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day03/</url>
<content type="html"><</p><p>图中的 <code>vptr</code>分别指向自己类的虚函数表,通常一个类的虚指针放在内存布局的最前面,所以这里 B 的虚指针一般会把 A 的虚指针给覆盖掉。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs css">构造一个子类对象:首先会先去构建基类的对象内容,比如这里的 <span class="hljs-selector-tag">A</span> 和 C,然后再构造自己的内容。<br>析构一个子类对象:和构造相反,先执行自己的析构函数,然后去执行基类的析构函数。<br></code></pre></td></tr></table></figure><p>Question: 那这里有个问题,如果 A 的析构函数是虚函数的话,之前覆盖了 A 的虚指针,这里就找不到 A 的虚函数表,那么如何析构 A 的内容呢?</p><p>答案就是编译器会在调用 B 的虚析构函数的时候,就把头部的虚指针重置为 A 的了,所以能够正常执行。</p><p>所以这里我们就明白了解决上面问题最重要的一个点,那就是 B 的内存空间一定会继承基类的虚指针以及基类的一些成员变量。</p><p>那么解决这个问题还需要明白一个知识点,那就是<strong>内存对齐</strong>。内存对齐的规则是什么呢?</p><ol><li><p>类的每个基准内存块大小 = 类当中占用内存大小最大的成员变量的内存空间,比如这里的 A,虽然最大的成员变量是 1 字节,但是它存在虚函数,所以内部有虚指针。又由于虚指针是 8 字节,所以基准内存块大小就是 8 字节,所以这里 A 所占用是 8 + 8 = 16 字节</p></li><li><p>每个类型的起始地址都是该类型大小的整数倍,比如 int 的成员变量,那么它的起始地址只有可能是 0, 4, 8, ….</p></li></ol><p>明白了上面的两个点以后,现在我们来看这个问题:</p><p>首先由于 A 和 C 中存在虚函数(虚指针)的缘故,他们的大小都是 16 字节。然后我们看到 B 中自己的成员变量,首先 short 一定是在 4 字节的,因为 short 本身是占 2 字节的,但是由于对齐规则 2 ,后续跟进的 int 的起始地址只能是 4 的倍数,所以 2 - 4 的空间也是只能给 short ,这样的话就是 16 + 16 + 16,最后还有一个 char 类型,不过因为对齐规则 1,这里只能是也占用 8 个字节,所有总共占用的字节数是 16 + 16 + 16 + 8 = 56 个字节。</p><h2 id="Http-协议"><a href="#Http-协议" class="headerlink" title="Http 协议"></a>Http 协议</h2><h3 id="1-Http-请求的核心部分有哪些?"><a href="#1-Http-请求的核心部分有哪些?" class="headerlink" title="1. Http 请求的核心部分有哪些?"></a>1. Http 请求的核心部分有哪些?</h3><p>一个标准的 HTTP 请求报文由四个部分组成:</p><ul><li>**请求行:**包含 Method、URL、协议版本</li><li>**请求头:**键值对,描述客户端环境、压缩格式,持久连接等(Host, User-Agent, Connection)</li><li><strong>空行:</strong> <code>\r\n</code>,用于分隔 Header 和 Body,这是协议格式的强制要求</li><li>**请求体:**可选,通常用于 POST/PUT 提交的数据</li></ul><h3 id="2-Http1-1-常用-Method-及-GET-POST-区别?"><a href="#2-Http1-1-常用-Method-及-GET-POST-区别?" class="headerlink" title="2. Http1.1 常用 Method 及 GET/POST 区别?"></a>2. Http1.1 常用 Method 及 GET/POST 区别?</h3><p>常用 Method:GET, POST,PUT,DELETE,HEAD</p><p>GET 和 POST 的区别:</p><ul><li>**语义:**GET 倾向于获取资源,是幂等的;POST 倾向于处理资源(创建/修改),是非幂等的。</li><li>**参数位置:**GET 参数放在 URL 后面,POST 参数通常放在 Request Body 中。</li><li>**数据大小:**GET 受限于 URL 长度(浏览器/服务器限制);POST 理论上无限制。</li></ul><h3 id="3-为什么-GET-请求一般没有请求体?"><a href="#3-为什么-GET-请求一般没有请求体?" class="headerlink" title="3. 为什么 GET 请求一般没有请求体?"></a>3. 为什么 GET 请求一般没有请求体?</h3><p>有下面三点原因:</p><ul><li><p>**语义冲突:**GET 的定义是根据 URL 及其参数获取资源,引入 Body 会破坏这种简洁的映射。</p></li><li><p>**缓存兼容性:**CDN、代理服务器和浏览器通常只根据 URL 缓存 GET 请求。如果 Body 影响结果,这些缓存机制将失效。</p></li><li><p>**服务器实现:**许多 Web 服务器和解析器为了性能优化,会直接忽略 GET 请求中的 Body。</p></li></ul><h3 id="4-HTTP-1-1、HTTP-2、HTTP-3-的主要区别是什么?"><a href="#4-HTTP-1-1、HTTP-2、HTTP-3-的主要区别是什么?" class="headerlink" title="4. HTTP/1.1、HTTP/2、HTTP/3 的主要区别是什么?"></a>4. HTTP/1.1、HTTP/2、HTTP/3 的主要区别是什么?</h3><table><thead><tr><th><strong>特性</strong></th><th><strong>HTTP/1.1</strong></th><th><strong>HTTP/2</strong></th><th><strong>HTTP/3</strong></th></tr></thead><tbody><tr><td><strong>传输格式</strong></td><td>文本(明文)</td><td><strong>二进制分帧</strong></td><td>二进制分帧</td></tr><tr><td><strong>多路复用</strong></td><td>无(有线头阻塞)</td><td><strong>支持(单一连接并发)</strong></td><td>支持</td></tr><tr><td><strong>底层协议</strong></td><td>TCP</td><td>TCP</td><td><strong>UDP (QUIC)</strong></td></tr><tr><td><strong>头部压缩</strong></td><td>无</td><td><strong>HPACK</strong></td><td>QPACK</td></tr><tr><td><strong>连接建立</strong></td><td>TCP 握手 + TLS 握手</td><td>同 1.1</td><td><strong>QUIC 握手(1-RTT/0-RTT)</strong></td></tr></tbody></table><h3 id="5-HTTP-有哪些常见的状态码?"><a href="#5-HTTP-有哪些常见的状态码?" class="headerlink" title="5. HTTP 有哪些常见的状态码?"></a>5. HTTP 有哪些常见的状态码?</h3><p>首先是<strong>分类</strong>:Http 状态共 5 大类,首位数字界定类别:1xx(信息)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务端错误)</p><p>其次列举一些<strong>高频的状态码</strong>:</p><p>200(成功)、404(资源不存在)、500(服务器错误)、304(缓存)、401/403(权限)、429(限流)</p>]]></content>
<categories>
<category>C++ 面经</category>
</categories>
</entry>
<entry>
<title>烤面筋 Day 02</title>
<link href="/2026/01/14/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day02/"/>
<url>/2026/01/14/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day02/</url>
<content type="html"><![CDATA[<h1 id="烤面筋-Day-02"><a href="#烤面筋-Day-02" class="headerlink" title="烤面筋 Day 02"></a>烤面筋 Day 02</h1><hr><h2 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h2><h3 id="1-向上转型-vs-向下转型"><a href="#1-向上转型-vs-向下转型" class="headerlink" title="1. 向上转型 vs 向下转型?"></a>1. 向上转型 vs 向下转型?</h3><p>这是多态中两个方向完全相反的操作:</p><p>向上转型:</p><ul><li>定义:将子类的指针或引用转换成父类</li><li>安全性:绝对安全。因为子类也是一种父类</li><li>用法:这种转换是隐式,不需要显式调用 <code>static_cast</code> ,是实现多态的基础</li></ul><p>向下转型:</p><ul><li>定义:将<strong>父类</strong>的指针或引用转换为<strong>子类</strong>。</li><li>安全性: <strong>不安全</strong>。父类对象不一定是子类。</li><li>用法:必须使用显式转换,出于安全保证使用 <code>dynamic_cast</code></li></ul><h3 id="2-static-cast-与-dynamic-cast-的区别?"><a href="#2-static-cast-与-dynamic-cast-的区别?" class="headerlink" title="2. static_cast 与 dynamic_cast 的区别?"></a>2. <code>static_cast</code> 与 <code>dynamic_cast</code> 的区别?</h3><p>本质区别在于 “转换发生的时机” 和 “安全检查的机制”</p><p><code>static_cast</code>:</p><ul><li>在编译阶段完成,不进行运行时类型检查</li><li>主要用于基本类型转换(如 <code>int</code> 转 <code>float</code>),非多态层级结构内的指针/引用转换。</li></ul><p><code>dynamic_cast</code>:</p><ul><li><p>在运行阶段利用 <strong>RTTI(运行时类型信息)</strong> 检查转换是否合法</p></li><li><p>专门用于处理**多态(Polymorphism)**层级结构中的转换</p></li></ul><h3 id="3-static-cast-在什么场景有风险?"><a href="#3-static-cast-在什么场景有风险?" class="headerlink" title="3. static_cast 在什么场景有风险?"></a>3. <code>static_cast</code> 在什么场景有风险?</h3><p><code>static_cast</code> 的风险主要发生在 <strong>向下转型(Downcasting)</strong>,即把基类指针转换为派生类指针时:</p><ul><li><p>风险点: 如果该基类指针实际上并没有指向那个派生类对象,<code>static_cast</code> 依然会强行转换成功,返回一个地址。</p></li><li><p>后果: 当你通过这个转换后的指针访问派生类特有的成员变量或虚函数时,会发生<strong>未定义行为(Undefined Behavior)</strong>,通常表现为内存越界访问或程序崩溃,且这种错误在编译期无法察觉。</p></li></ul><h3 id="4-dynamic-cast-的优势是什么?"><a href="#4-dynamic-cast-的优势是什么?" class="headerlink" title="4. dynamic_cast 的优势是什么?"></a>4. <code>dynamic_cast</code> 的优势是什么?</h3><p>它的核心优势是 <strong>“安全性”</strong>:</p><ul><li><p>类型安全检查: 它会检查目标类型是否与对象的实际类型匹配。如果转换非法,对于指针会返回 <code>nullptr</code>,对于引用会抛出 <code>std::bad_cast</code> 异常。</p></li><li><p>支持虚继承转换: 在复杂的深层或菱形继承中,<code>dynamic_cast</code> 能够正确处理指针偏移。</p></li></ul><hr><h2 id="Reactor-线程池"><a href="#Reactor-线程池" class="headerlink" title="Reactor + 线程池"></a><code>Reactor + 线程池</code></h2><h3 id="1-Reactor-模式是怎么实现的?"><a href="#1-Reactor-模式是怎么实现的?" class="headerlink" title="1. Reactor 模式是怎么实现的?"></a>1. <code>Reactor</code> 模式是怎么实现的?</h3><p>Reactor 模式本质上是 “I/O 复用 + 派发”,其核心组件包括:</p><ol><li>Event Demultiplexer:底层通常是 <code>epoll</code>或者 <code>poll</code> ,监听注册了的一堆文件描述符有哪些动静</li><li>Reactor:核心循环。通过多路分离器等待事件发生,一旦有事件(如可读、可写),就将其分发(Dispatch)给对应的 Handler。</li><li>Handlers:绑定在事件上的回调函数,负责非阻塞的读写操作。</li></ol><h3 id="2-为什么采用主从-Reactor-线程池?"><a href="#2-为什么采用主从-Reactor-线程池?" class="headerlink" title="2. 为什么采用主从 Reactor + 线程池?"></a>2. 为什么采用主从 Reactor + 线程池?</h3><p>主要是为了解决单线程 <code>Reactor</code> 的性能瓶颈:</p><ul><li><p>分工明确:<strong>Main Reactor</strong> 只负责监听连接(Accept),<strong>Sub Reactor</strong> 负责处理已连接套接字的 I/O 事件。这避免了因为某个请求的 I/O 耗时过长导致新连接无法进入。</p></li><li><p>充分利用多核: 多个 Sub Reactor 可以运行在不同的 CPU 核心上,并行处理 I/O。</p></li><li><p>解耦计算:<strong>线程池</strong> 将业务计算逻辑从 I/O 线程中剥离。如果业务逻辑耗时较长,它不会阻塞 I/O 事件的分发,从而极大提高了系统的吞吐量。</p></li></ul><p><code>accept()</code> 仅需内核拷贝 socket 描述符(<strong>轻量高频操作</strong>),单线程 <code>BossGroup</code> 足以应对万级连接请求。如果是多线程竞争锁,反而会降低效率。IO 事件是重量级操作,涉及数据读写,业务逻辑,可能阻塞(数据库查询)。WorkGroup 多线程并行处理,充分利用多核 CPU。</p><p>Ps. 这里的 I/O 线程处理的是 I/O 读写,指的是从<strong>网卡驱动缓存</strong>拷贝到用户缓存区,或者将用户态数据拷贝到<strong>内核发送缓存区</strong>。业务计算逻辑则是加工数据(数据读上来以后,程序需要处理它),例如协议解析(二进制流解包成 Protobuf 或 JSON),数据库查询(根据请求差 SQL)等等</p><h3 id="3-从-Reactor-读到的数据如何传递给线程池?能不能实现一下?"><a href="#3-从-Reactor-读到的数据如何传递给线程池?能不能实现一下?" class="headerlink" title="3. 从 Reactor 读到的数据如何传递给线程池?能不能实现一下?"></a>3. 从 Reactor 读到的数据如何传递给线程池?能不能实现一下?</h3><p>是通过任务队列来实现的。Sub Reactor 将 (数据,处理函数)封装成任务丢进队列,线程池里的 worker 线程通过竞争来获取任务。</p><p>线程安全的任务队列通常有使用条件变量的阻塞队列和无锁队列。</p><p>阻塞队列:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><condition_variable></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><queue></span></span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">BlockQueue</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T& val)</span> </span>{<br> <span class="hljs-function">std::lock_guard <span class="hljs-title">lock</span><span class="hljs-params">(mtx_)</span></span>;<br> <span class="hljs-keyword">if</span> (stop_) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// 队列已停止,返回false</span><br> que_.<span class="hljs-built_in">push</span>(val);<br> cv_.<span class="hljs-built_in">notify_one</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; <br> }<br> <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">pop</span><span class="hljs-params">(T& val)</span> </span>{<br> <span class="hljs-function">std::unique_lock <span class="hljs-title">lock</span><span class="hljs-params">(mtx_)</span></span>;<br> cv_.<span class="hljs-built_in">wait</span>(lock, [<span class="hljs-keyword">this</span>](){<br> <span class="hljs-keyword">return</span> stop_ || !que_.<span class="hljs-built_in">empty</span>();<br> });<br> <span class="hljs-keyword">if</span>(stop_ && que_.<span class="hljs-built_in">empty</span>()) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> val = que_.<span class="hljs-built_in">front</span>();<br> que_.<span class="hljs-built_in">pop</span>();<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">stop</span><span class="hljs-params">()</span> </span>{<br> {<br> <span class="hljs-function">std::lock_guard <span class="hljs-title">lock</span><span class="hljs-params">(mtx_)</span></span>;<br> stop_ = <span class="hljs-literal">true</span>;<br> }<br> cv_.<span class="hljs-built_in">notify_all</span>();<br> }<br><span class="hljs-keyword">private</span>:<br> std::condition_variable cv_;<br> std::queue<T> que_;<br> std::mutex mtx_;<br> <span class="hljs-type">bool</span> stop_ = <span class="hljs-literal">false</span>;<br>};<br><br></code></pre></td></tr></table></figure><p>无锁队列:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><atomic></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><cstdio></span></span><br><br><span class="hljs-comment">/*</span><br><span class="hljs-comment">* push: 先检查尾部的一致性, tail->next = new_node, tail = new_node(用 CAS 版本实现)</span><br><span class="hljs-comment">* pop:先检查头部的一致性,如果队列为空 tail = tail->next, 不为空 head = head->next</span><br><span class="hljs-comment">*/</span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">LockFreeQueue</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">LockFreeQueue</span>();<br> ~<span class="hljs-built_in">LockFreeQueue</span>();<br><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T& value)</span> </span>{<br> Node* newNode = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Node</span>(value);<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> Node* current_tail = tail.<span class="hljs-built_in">load</span>();<br> Node* next = current_tail->next.<span class="hljs-built_in">load</span>();<br> <br> <span class="hljs-comment">// 一致性检查</span><br> <span class="hljs-keyword">if</span>(tail.<span class="hljs-built_in">load</span>() == current_tail) {<br> <span class="hljs-comment">// tail 是真正的尾部</span><br> <span class="hljs-keyword">if</span>(next == <span class="hljs-literal">nullptr</span>) {<br> <span class="hljs-comment">// 一致性检查</span><br> <span class="hljs-keyword">if</span>(current_tail->next.<span class="hljs-built_in">compare_exchange_weak</span>(next, newNode)) {<br> <span class="hljs-comment">// 进行更新</span><br> tail.<span class="hljs-built_in">compare_exchange_weak</span>(current_tail, newNode);<br> <span class="hljs-keyword">return</span>;<br> }<br> } <span class="hljs-keyword">else</span> {<br> tail.<span class="hljs-built_in">compare_exchange_weak</span>(current_tail, next);<br> }<br> } <br> }<br> }<br><br> <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">pop</span><span class="hljs-params">(T& result)</span> </span>{<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> Node* current_head = head.<span class="hljs-built_in">load</span>();<br> Node* current_tail = tail.<span class="hljs-built_in">load</span>();<br> Node* next = current_head->next.<span class="hljs-built_in">load</span>();<br><br> <span class="hljs-keyword">if</span>(current_head == head.<span class="hljs-built_in">load</span>()) {<br> <span class="hljs-comment">// 队列有可能为空</span><br> <span class="hljs-keyword">if</span>(current_head == current_tail) {<br> <span class="hljs-keyword">if</span>(next == <span class="hljs-literal">nullptr</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> <span class="hljs-comment">// 进行更新 tail = tail->next </span><br> tail.<span class="hljs-built_in">compare_exchange_weak</span>(current_tail, next);<br> } <span class="hljs-keyword">else</span> {<br> result = next->data;<br> <span class="hljs-keyword">if</span>(head.<span class="hljs-built_in">compare_exchange_weak</span>(current_head, next)) {<br> <span class="hljs-keyword">delete</span> current_head;<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br> }<br> }<br> }<br> }<br><br><span class="hljs-keyword">private</span>:<br> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Node</span> {<br> T data;<br> std::atomic<Node*> next;<br> <span class="hljs-built_in">Node</span>(<span class="hljs-type">const</span> T& val) : <span class="hljs-built_in">data</span>(val), <span class="hljs-built_in">next</span>(<span class="hljs-literal">nullptr</span>) {}<br> };<br> std::atomic<Node*> head;<br> std::atomic<Node*> tail;<br>};<br><br></code></pre></td></tr></table></figure><h3 id="4-CPU-密集型和-I-O-密集型怎么判断?"><a href="#4-CPU-密集型和-I-O-密集型怎么判断?" class="headerlink" title="4. CPU 密集型和 I/O 密集型怎么判断?"></a>4. CPU 密集型和 I/O 密集型怎么判断?</h3><p>主要是看瓶颈在哪里:$N_{cpu}$ 是 CPU 核心数的意思</p><p>CPU 密集型:</p><ul><li><strong>特点</strong>:大部分时间在做复杂的运算(图像处理、科学计算)</li><li><strong>判断</strong>: 程序运行时,CPU 占用率极高,但几乎没有磁盘或网络 I/O。</li><li><strong>线程池策略:</strong> 线程数通常设置为 <strong>$N_{CPU} + 1$</strong>。</li></ul><p>I/O 密集型:</p><ul><li><strong>特点:</strong> 大部分时间在等待磁盘读写、数据库查询、网络响应。</li><li><strong>判断:</strong> CPU 占用率较低,系统大量时间处于等待状态(Wait)。</li><li><strong>线程池策略:</strong> 线程数可以设置得大一些,如 <strong>$2N_{CPU}$</strong> 甚至更多。</li></ul><h3 id="5-线程池线程数如何确定?"><a href="#5-线程池线程数如何确定?" class="headerlink" title="5. 线程池线程数如何确定?"></a>5. 线程池线程数如何确定?</h3><p>首先针对于 I/O 密集型任务来说,一般设置为内核数 * 2。</p><p>当一个线程因为 I/O 阻塞或等待时,CPU 可以切换到另一个线程。避免过多的上下文切换(Context Switch)带来的损耗。</p><p>不过在实际生产环境下,我会优先通过 <strong>压力测试</strong> 和 <strong>性能监控</strong>(如查看 <code>top</code> 中的 <code>iowait</code> 指标)来动态调整线程数,而不会死守</p><p>公式。</p><h3 id="6-手撕一下线程池?"><a href="#6-手撕一下线程池?" class="headerlink" title="6. 手撕一下线程池?"></a>6. 手撕一下线程池?</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><vector></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><queue></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><condition_variable></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><future></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><functional></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><atomic></span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ThreadPool</span> {<br><span class="hljs-keyword">private</span>:<br> std::vector<std::thread> workers;<br> std::queue<std::function<<span class="hljs-type">void</span>()>> tasks;<br> <br> std::mutex queue_mutex;<br> std::condition_variable condition;<br> std::atomic<<span class="hljs-type">bool</span>> stop;<br> <br><span class="hljs-keyword">public</span>:<br> <span class="hljs-built_in">ThreadPool</span>(<span class="hljs-type">size_t</span> num_threads = std::thread::<span class="hljs-built_in">hardware_concurrency</span>()) <br> : <span class="hljs-built_in">stop</span>(<span class="hljs-literal">false</span>) {<br> <span class="hljs-keyword">for</span>(<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i < num_threads; ++i) {<br> workers.<span class="hljs-built_in">emplace_back</span>([<span class="hljs-keyword">this</span>] {<br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> std::function<<span class="hljs-built_in">void</span>()> task;<br> <br> {<br> std::unique_lock<std::mutex> <span class="hljs-built_in">lock</span>(<span class="hljs-keyword">this</span>->queue_mutex);<br> <span class="hljs-keyword">this</span>->condition.<span class="hljs-built_in">wait</span>(lock, [<span class="hljs-keyword">this</span>] {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>->stop || !<span class="hljs-keyword">this</span>->tasks.<span class="hljs-built_in">empty</span>();<br> });<br> <br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>->stop && <span class="hljs-keyword">this</span>->tasks.<span class="hljs-built_in">empty</span>()) <span class="hljs-keyword">return</span>;<br> <br> task = std::<span class="hljs-built_in">move</span>(<span class="hljs-keyword">this</span>->tasks.<span class="hljs-built_in">front</span>());<br> <span class="hljs-keyword">this</span>->tasks.<span class="hljs-built_in">pop</span>();<br> }<br> <br> <span class="hljs-built_in">task</span>();<br> }<br> });<br> }<br> }<br> <br> ~<span class="hljs-built_in">ThreadPool</span>() {<br> stop = <span class="hljs-literal">true</span>;<br> condition.<span class="hljs-built_in">notify_all</span>();<br> <br> <span class="hljs-keyword">for</span>(std::thread &worker : workers) {<br> <span class="hljs-keyword">if</span>(worker.<span class="hljs-built_in">joinable</span>()) worker.<span class="hljs-built_in">join</span>();<br> }<br> }<br> <br> <span class="hljs-function"><span class="hljs-keyword">template</span><<span class="hljs-keyword">class</span> F, <span class="hljs-keyword">class</span>... Args></span><br><span class="hljs-function"> <span class="hljs-keyword">auto</span> <span class="hljs-title">enqueue</span><span class="hljs-params">(F&& f, Args&&... args)</span> </span><br><span class="hljs-function"> -> std::future<<span class="hljs-keyword">typename</span> std::result_of<<span class="hljs-title">F</span><span class="hljs-params">(Args...)</span>>::type> </span>{<br> <br> <span class="hljs-keyword">using</span> return_type = <span class="hljs-keyword">typename</span> std::result_of<<span class="hljs-built_in">F</span>(Args...)>::type;<br> <br> <span class="hljs-keyword">auto</span> task = std::make_shared<std::packaged_task<<span class="hljs-built_in">return_type</span>()>>(<br> std::<span class="hljs-built_in">bind</span>(std::forward<F>(f), std::forward<Args>(args)...)<br> );<br> <br> std::future<return_type> res = task-><span class="hljs-built_in">get_future</span>();<br> <br> {<br> <span class="hljs-function">std::unique_lock<std::mutex> <span class="hljs-title">lock</span><span class="hljs-params">(queue_mutex)</span></span>;<br> <span class="hljs-keyword">if</span>(stop) <span class="hljs-keyword">throw</span> std::<span class="hljs-built_in">runtime_error</span>(<span class="hljs-string">"enqueue on stopped ThreadPool"</span>);<br> tasks.<span class="hljs-built_in">emplace</span>([task](){ (*task)(); });<br> }<br> <br> condition.<span class="hljs-built_in">notify_one</span>();<br> <span class="hljs-keyword">return</span> res;<br> }<br>};<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>C++ 面经</category>
</categories>
</entry>
<entry>
<title>烤面筋 Day 01</title>
<link href="/2026/01/13/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day01/"/>
<url>/2026/01/13/%E7%83%A4%E9%9D%A2%E7%AD%8B_Day01/</url>
<content type="html"><![CDATA[<h1 id="烤面筋-Day-01"><a href="#烤面筋-Day-01" class="headerlink" title="烤面筋 Day 01"></a>烤面筋 Day 01</h1><hr><h2 id="原子操作与内存模型"><a href="#原子操作与内存模型" class="headerlink" title="原子操作与内存模型:"></a>原子操作与内存模型:</h2><hr><h3 id="1-原子操作是什么?Atomic-跟原子操作的关系是什么?"><a href="#1-原子操作是什么?Atomic-跟原子操作的关系是什么?" class="headerlink" title="1. 原子操作是什么?Atomic 跟原子操作的关系是什么?"></a>1. 原子操作是什么?<code>Atomic</code> 跟原子操作的关系是什么?</h3><p>答:原子操作就是要么不执行,要么全部执行成功,且中途不能被中断的操作。<code>Atomic</code> 就是 <code>C++ 11</code> 提供的标准库模板类,是原子操作在语言层面的封装和工具。</p><h3 id="2-Atomic-底层是怎么实现的?"><a href="#2-Atomic-底层是怎么实现的?" class="headerlink" title="2. Atomic 底层是怎么实现的?"></a>2. Atomic 底层是怎么实现的?</h3><p>答:底层主要依赖硬件层面处理器提供的<strong>原子指令</strong>,在 x86 架构下,通常会使用带有 <code>LOCK</code> 前缀的指令,其能锁定缓存行,确保执行执行期间不会被中断。</p><p>编译器层面也会根据不同的 CPU 架构,将 <code>atomic</code> 操作映射为相应的机器指令,根据指定的内存序,插入必要的 Memory Barrier ,防止指令重排。</p><h3 id="3-能简单讲讲-C-的内存序(Memory-Order)吗?"><a href="#3-能简单讲讲-C-的内存序(Memory-Order)吗?" class="headerlink" title="3. 能简单讲讲 C++ 的内存序(Memory Order)吗?"></a>3. 能简单讲讲 C++ 的内存序(Memory Order)吗?</h3><p>内存序的作用是<strong>约束编译器和 CPU 的指令重排优化</strong>,C++11 定义了 6 种内存序,但常用的是 <code>memory_order_relaxed</code> 和 获取/释放语义和序列一致性。</p><p>宽松顺序:只保证当前操作的<strong>原子性</strong>,不保证任何顺序(没有同步关系)。</p><p>释放(release)语义:持有 <code>release</code> 的线程是发布者,,逻辑是 “做完手头的事情了,现在发布表示数据准备好了”</p><p>获取(acquire)语义:持有 <code>acquire</code> 的线程是获取者,逻辑是 “看到发布的通知了,说明我可以放心去用数据了”</p><p>序列一致性:atomic 的默认参数,要求所有线程看到的执行顺序都是一模一样的,就像所有操作都排在一个全局队列里依次执行。</p><ul><li>那为什么不能全用默认的 <code>seq_cst</code> 序列一致性的内存序?</li></ul><p>高性能场景下(如无锁队列、底层驱动),默认的强顺序会导致大量的<strong>内存屏障指令</strong>,强制刷新 CPU 缓存,这会极大限制现代多核处理器的并行能力。</p><h3 id="4-CAS-指令是什么?"><a href="#4-CAS-指令是什么?" class="headerlink" title="4. CAS 指令是什么?"></a>4. CAS 指令是什么?</h3><p>答:CAS 是实现原子操作的核心指令,包含三个操作数:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 内存地址 V:要读取的变量位置。<br><span class="hljs-number">2.</span> 旧的预期值 A:我们认为该变量当前应该是什么值。<br><span class="hljs-number">3.</span> 准备写入的新值 B:如果检查通过,要更新成的值。<br></code></pre></td></tr></table></figure><p>只有内存地址的值还是我之前读到的那个预期值,我才把它修改成新值。否则说明别人改过它,那我就放弃这次修改。整个过程是<strong>原子性</strong>的</p><h3 id="5-CAS-的优缺点是什么?"><a href="#5-CAS-的优缺点是什么?" class="headerlink" title="5.CAS 的优缺点是什么?"></a>5.CAS 的优缺点是什么?</h3><p>答:优点就是无锁,避免了传统互斥锁导致的线程上下文切换、挂起和恢复,性能开销更小。</p><p>缺点是有自旋开销,如果在高竞争环境下,CAS 一直失败,会导致 CPU 一直空转,浪费资源。同时还存在 ABA 问题。</p><h3 id="6-ABA-问题是什么?如何解决这个问题?"><a href="#6-ABA-问题是什么?如何解决这个问题?" class="headerlink" title="6. ABA 问题是什么?如何解决这个问题?"></a>6. ABA 问题是什么?如何解决这个问题?</h3><p>就是一个线程将数值 A 改成了 B,随后另一个线程又将 B 改回了 A,此时第三个线程进行检查,发现值依然是 A,认为没有改变,从而误操作。</p><p>目前解决的主流办法是版本号,实现方法就是记录值的同时维护一个版本号,每次更新的时候,不仅更新值,还让版本号 <code>+1</code>。这样遇到 ABA 问题的时候版本号不同,就知道值已经发生改变了。</p><h3 id="7-C-atomic-是否自带版本号机制?"><a href="#7-C-atomic-是否自带版本号机制?" class="headerlink" title="7. C++ atomic 是否自带版本号机制?"></a>7. C++ atomic 是否自带版本号机制?</h3><p>不自带。标准的 std::atomic<T> 只是对类型 T 的原子封装。如果 T 是一个指针,仍然会存在 ABA 问题。</p><p>如果需要解决这个问题的话,开发者通常需要配合 <code>std::atomic<std::pair<T, int>></code> 或者一些带有 Tag 的指针技术。</p><h3 id="8-Volatile-关键字的作用是什么?能解决线程安全吗?"><a href="#8-Volatile-关键字的作用是什么?能解决线程安全吗?" class="headerlink" title="8. Volatile 关键字的作用是什么?能解决线程安全吗?"></a>8. Volatile 关键字的作用是什么?能解决线程安全吗?</h3><p>主要的作用是<strong>禁止编译器优化</strong>:告诉编译器,这个变量的值可能随时被外部(如硬件中断、另一个进程)修改,因此每次使用它都必须<strong>从内存中读取</strong>,而不能使用寄存器里的缓存。</p><p>不能,因为他不保证原子性,同时不禁止指令重排,只针对编译器的,但不提供针对 CPU 的内存屏障,无法解决多核环境下的可见性和有序性问题。</p><h3 id="9-手撕一下:写一下-CAS-实现的无锁队列"><a href="#9-手撕一下:写一下-CAS-实现的无锁队列" class="headerlink" title="9. 手撕一下:写一下 CAS 实现的无锁队列"></a>9. 手撕一下:写一下 CAS 实现的无锁队列</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><atomic></span></span><br><br><span class="hljs-keyword">template</span> <<span class="hljs-keyword">typename</span> T><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">LockFreeQueue</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Enqueue</span><span class="hljs-params">(<span class="hljs-type">const</span> T& val)</span> </span>{<br> Node* new_node = <span class="hljs-built_in">Node</span>(val);<br> Node* old_tail = tail.<span class="hljs-built_in">load</span>();<br> <br> <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) {<br> old_tail = tail.<span class="hljs-built_in">load</span>();<br> Node* next = old_tail->next.<span class="hljs-built_in">load</span>();<br><br> <span class="hljs-keyword">if</span>(old_tail == tail.<span class="hljs-built_in">load</span>()) {<br> <span class="hljs-keyword">if</span>(next == <span class="hljs-literal">nullptr</span>) {<br> <span class="hljs-keyword">if</span>(old_tail->next.<span class="hljs-built_in">compare_exchange_strong</span>(next, new_node)) {<br> tail.<span class="hljs-built_in">compare_exchange_weak</span>(old_tail, new_node);<br> <span class="hljs-keyword">return</span>;<br> }<br> } <br> <span class="hljs-keyword">else</span> tail.<span class="hljs-built_in">compare_exchange_strong</span>(old_tail, next);<br> }<br> }<br> }<br><span class="hljs-keyword">private</span>:<br> <span class="hljs-keyword">struct</span> <span class="hljs-title class_">Node</span> {<br> T data;<br> std::atomic<Node*> next;<br> <span class="hljs-built_in">Node</span>(T val) : <span class="hljs-built_in">data</span>(val), <span class="hljs-built_in">next</span>(<span class="hljs-literal">nullptr</span>) {}<br> };<br> std::atomic<Node*> head;<br> std::atomic<Node*> tail;<br>};<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>* argv[])</span> </span>{}<br></code></pre></td></tr></table></figure><h2 id="多线程与锁"><a href="#多线程与锁" class="headerlink" title="多线程与锁"></a>多线程与锁</h2><hr><h3 id="1-多线程最常见的问题是什么?"><a href="#1-多线程最常见的问题是什么?" class="headerlink" title="1. 多线程最常见的问题是什么?"></a>1. 多线程最常见的问题是什么?</h3><p>主要是三个问题:</p><ul><li><p>线程安全问题:多个线程同时读写同一个块内存,导致结果不可预期</p></li><li><p>活跃性问题:包括死锁,活锁和饥饿,其中死锁是最致命的,会导致程序完全卡死</p></li><li><p>性能开销:过渡的上下文切换和锁竞争导致性能下降</p></li></ul><h3 id="2-死锁怎么避免?"><a href="#2-死锁怎么避免?" class="headerlink" title="2. 死锁怎么避免?"></a>2. 死锁怎么避免?</h3><p>避免死锁的核心逻辑是:防止循环等待链的形成。常用的解决办法如下:</p><ul><li>固定加锁顺序:规定所有的线程必须按照相同的顺序获取锁(先拿 A 锁,再拿 B 锁)</li><li>尝试锁(Try-lock):使用 <code>std::unique_lock</code> 的 <code>try_lock</code>,如果拿不到锁就立即释放已占用的所并且回退</li><li>使用高级抽象:使用其他无锁的机制(比如消息传递等等)来替代底层锁</li></ul><h3 id="3-哪些设计方式-技巧能破坏死锁的-4-个条件?"><a href="#3-哪些设计方式-技巧能破坏死锁的-4-个条件?" class="headerlink" title="3. 哪些设计方式/技巧能破坏死锁的 4 个条件?"></a>3. 哪些设计方式/技巧能破坏死锁的 4 个条件?</h3><p>互斥性:资源一次只能被一个线程占用。这个是无法破坏(锁的本意)。</p><p>占用且等待:线程持有一个资源的同时,还在等待另一个资源。采用<strong>一次性申请</strong>的方式解决: 线程启动前一次性申请所有需要的锁;或者在无法获取下一个锁时,释放已占有的所有锁。</p><p>不可剥夺:资源不能被抢占,只能由持有者主动释放。采用<strong>抢占机制</strong>的方式解决:如果线程申请新锁失败,主动释放手中资源,过段时间再重试</p><p>循环等待:存在一个线程等待环路:T1 等 T2,T2 等 T1。采用<strong>资源线性排序</strong>的方式解决: 给所有锁分配唯一序号,强制规定必须按序号从小到大(或从大到小)获取锁。</p><h3 id="4-实际开发中,你会如何实现避免死锁的发生?"><a href="#4-实际开发中,你会如何实现避免死锁的发生?" class="headerlink" title="4. 实际开发中,你会如何实现避免死锁的发生?"></a>4. 实际开发中,你会如何实现避免死锁的发生?</h3><p>我会使用 <code>std::scoped_lock</code> 一次性添加多个锁利用其内部的机制来避免</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><mutex></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><thread></span></span><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Account</span> {<br><span class="hljs-keyword">public</span>:<br> <span class="hljs-type">int</span> balance = <span class="hljs-number">1000</span>;<br> std::mutex mtx; <span class="hljs-comment">// 每个账户自带一把锁</span><br>};<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">transfer</span><span class="hljs-params">(Account& from, Account& to, <span class="hljs-type">int</span> amount)</span> </span>{<br> <span class="hljs-comment">// 关键点:std::scoped_lock 同时接收多个 mutex</span><br> <span class="hljs-comment">// 它内部使用了一种免死锁算法(通常是逐个尝试且不持有等待)</span><br> <span class="hljs-comment">// 无论你传入参数的顺序如何,它都能保证不会发生死锁</span><br> <span class="hljs-function">std::scoped_lock <span class="hljs-title">lock</span><span class="hljs-params">(from.mtx, to.mtx)</span></span>; <br><br> <span class="hljs-keyword">if</span> (from.balance >= amount) {<br> from.balance -= amount;<br> to.balance += amount;<br> std::cout << <span class="hljs-string">"转账成功: "</span> << amount << std::endl;<br> }<br>}<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> Account alice, bob;<br> <span class="hljs-comment">// 线程 1:从 Alice 转账给 Bob</span><br> <span class="hljs-function">std::thread <span class="hljs-title">t1</span><span class="hljs-params">(transfer, std::ref(alice), std::ref(bob), <span class="hljs-number">100</span>)</span></span>;<br> <span class="hljs-comment">// 线程 2:从 Bob 转账给 Alice (如果用普通 lock 会因顺序相反导致死锁)</span><br> <span class="hljs-function">std::thread <span class="hljs-title">t2</span><span class="hljs-params">(transfer, std::ref(bob), std::ref(alice), <span class="hljs-number">200</span>)</span></span>;<br> t<span class="hljs-number">1.</span><span class="hljs-built_in">join</span>();<br> t<span class="hljs-number">2.</span><span class="hljs-built_in">join</span>();<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>其内部的机制</p><ul><li>避免循环等待:内部并不简单地按顺序调用 <code>lock()</code>,而是采用了一种 <strong>“重试机制”</strong>:</li></ul><p>1. 它会尝试锁住第一个互斥量。</p><p>2. 接着尝试 <code>try_lock</code> 后面的互斥量。</p><p>3. 如果中间任何一个锁失败了,它会<strong>立即释放</strong>已经持有的所有锁,然后重新开始。</p><ul><li>原子性加锁:保证了所有传入的互斥量要么全部锁住,要么一个都不锁。</li></ul>]]></content>
<categories>
<category>C++ 面经</category>
</categories>
</entry>
<entry>
<title>连网</title>
<link href="/2025/12/07/%E8%BF%9E%E7%BD%91/"/>
<url>/2025/12/07/%E8%BF%9E%E7%BD%91/</url>
<content type="html"><![CDATA[<h1 id="“连网”"><a href="#“连网”" class="headerlink" title="“连网”"></a>“连网”</h1><p>平时我们说的连网,连网到底是什么呢?为什么实验室的主机只能接网线或者连接 <code>wifi</code> 才能访问互联网上的各种资源呢?</p><p>为了搞清楚这个问题,需要搞懂下面几个概念</p><p><strong>连网</strong>:主机通过有线或无线方式接入网络,获得 IP 地址,并能够与其他设备或互联网进行数据通信的过程。</p><h2 id="主机网络通信的方法"><a href="#主机网络通信的方法" class="headerlink" title="主机网络通信的方法"></a>主机网络通信的方法</h2><h3 id="公网通信"><a href="#公网通信" class="headerlink" title="公网通信"></a>公网通信</h3><p>两台主机都有公网 IP, 可以直接通过 ssh user@公网 IP 连接</p><p>这种通常只适用于具有固定公网 IP 的设备连接。</p><p>家庭的宽带通常是动态的公网 IP (重启光猫以后会变化),企业或者云服务器才是静态公网 IP 。</p><ul><li>解释家里为什么只有连接 <code>wifi</code> (连接路由器)才能访问互联网上的各种资源。</li></ul><p>只要是需要访问互联网上的资源,那么都需要设备具有<strong>公网 IP</strong>, 但是手机等设备出厂时只有 <code>mac</code> 地址,连接路由器的才会给你分配 IP 地址(IP 地址是逻辑地址,由网络环境决定的),但是就算给你分配了,也只是<strong>内网 IP</strong>,路由器才是唯一具有公网 IP 的设备。</p><p>不过连接的路由器会将你的内网 IP 结合自己的 WAN 口(公网 IP)转换成公网 IP,收到回复包的时候,又将这个公网 IP 转换成内网 IP 从而找到你的设备,这其中牵扯到的协议叫做 <strong>NAT 协议</strong>,这里先不仔细讲了,大概是实现了地址转换的意思。</p><blockquote><p>Ps. 用 <code>4G/5G</code> 是不经过家用路由器,直接通过基站连接到运营商的核心网,然后运营商给你分配 CGNAT IP/ 公网 IP 。</p></blockquote><h3 id="局域网通信"><a href="#局域网通信" class="headerlink" title="局域网通信"></a>局域网通信</h3><h4 id="同一局域网内通信"><a href="#同一局域网内通信" class="headerlink" title="同一局域网内通信"></a>同一局域网内通信</h4><p>在同一局域网下面,通过局域网的 IP 能够直接进行通信。这个用的比较多,一般公司/学校/实验室内部服务器都是通过这种方式。</p><p>那么有些时候为了固定比如监控摄像头和打印机的 IP 或者实验室根本就没有路由器(纯内网通信),这个时候作为计算机专业的我们,可能会让我们配置静态 IP。</p><p>于是就会接触下面的概念:</p><ol><li>子网掩码:用于划分网络地址和主机地址。同一局域网的所有 IP & 上这个掩码都会得到同一个地址(一般就填 255.255.255.0 )</li><li>默认网关:当访问不是局域网的地址的时候,数据包就会发给网关。通常是路由器的内网 IP ,大多数默认网关的 IP 地址的最后一位是 1 或者 254(0 不可以,255 用作广播)</li><li>DNS 服务器:用于将输入的域名解析成 IP 地址,比较方便。DNS 服务器通常填 114.114.114.114 这种公共 DNS 服务器即可</li></ol><p>在纯内网通信(实验室中常用)当中,交换机由于没有 <code>DHCP</code> 服务器,就没有自动分配 IP 的功能(路由器有),所以这个时候只能通过手动配置 IP 的方式来实现通信。</p><p>这个时候只需要所有的机器都在同一个网段,比如</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c++">IP: 不冲突就行<br>机器 A : <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.10</span> <br>机器 B : <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.11</span><br>机器 B : <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.12</span> <br>子网掩码:<br> 统一填 <span class="hljs-number">255.255</span><span class="hljs-number">.255</span><span class="hljs-number">.0</span> <br>默认网关:<br>一般留空(<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>),因为一般不需要上网<br>DNS:<br> 一般也留空,除非你要解析域名,但是一般内网用 IP 地址<br></code></pre></td></tr></table></figure><ul><li>如何配置一个新设备的静态 IP</li></ul><ol><li>记录同网段下另一台配置好得设备的网段 (IP + 子网掩码 192.168.31.0/24)和默认网关( 192.168.31.1)</li><li>选择一个不冲突的静态 IP(ping 一下看看连不连通),登录设备的管理界面,这个一般根据设备的不同有些许不同,可以查看下表:</li></ol><table><thead><tr><th>设备类型</th><th>登录方式</th></tr></thead><tbody><tr><td>打印机</td><td>1. 在打印机面板上查看 IP 2. 用浏览器访问该 IP 3. 输入管理员账号密码(默认常印在机身)</td></tr><tr><td>交换机(网管型)</td><td>1. 用 Console 线(串口)连接电脑 2. 用终端软件(PuTTY、SecureCRT)登录</td></tr><tr><td><strong>摄像头 / NAS</strong></td><td>浏览器访问 IP 或专用客户端</td></tr><tr><td>Linux 服务器</td><td>SSH 登录后修改网络配置文件</td></tr></tbody></table><ol start="3"><li>配置 IP 参数(一般内网就配置 IP 地址和子网掩码就可以)同时保存验证,比如用新 IP 能够重新访问设备的管理界面和用其他设备 ping 一下这个设备来测试连通。</li></ol><hr><h4 id="不同局域网之间通信"><a href="#不同局域网之间通信" class="headerlink" title="不同局域网之间通信"></a>不同局域网之间通信</h4><ul><li><h4 id="通过跳板机中转"><a href="#通过跳板机中转" class="headerlink" title="通过跳板机中转"></a>通过跳板机中转</h4></li></ul><p>跳板机:一个专门用于中转访问内网资源的 “堡垒” <strong>服务器</strong>,一般用于集中管理访问权限。</p><p>比如公司的数据库服务器,或者内部的 API 服务部署在私有网络中,处于安全考虑不允许直接暴露到互联网中。所以他们<strong>没有公网 IP</strong>,同时又不可能和你在一个<strong>局域网</strong>内,这个时候就需要先登录一台有公网 IP 的跳板机,再从它跳转过去。</p><p>这样目标服务器就实现了完全隔离,其防火墙只允许跳板机 IP 访问 SSH 端口。</p><ul><li>那么我们如何能够通过跳板机来连接目标服务器呢?</li></ul><p>最推荐的就是直接编辑<strong>本地电脑</strong>的 SSH 配置文件(~/.ssh/config 文件)。假如我们有如下信息</p><table><thead><tr><th>角色</th><th>信息</th></tr></thead><tbody><tr><td>跳板机</td><td>公网 IP: <code>203.0.113.10</code>,用户名: <code>abc</code>,SSH 端口: <code>22</code></td></tr><tr><td>目标服务器</td><td>内网 IP: <code>10.0.1.20</code>,用户名: <code>cba</code></td></tr></tbody></table><p>那么直接在配置文件中定义如下即可:</p><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs crmsh"><span class="hljs-comment"># 跳板机定义</span><br>Host jump<br> HostName <span class="hljs-number">203.0</span>.<span class="hljs-number">113.10</span><br> <span class="hljs-keyword">User</span> <span class="hljs-title">ops</span><br> Port <span class="hljs-number">22</span><br><br><span class="hljs-comment"># 目标服务器(通过跳板机访问)</span><br>Host db-server<br> HostName <span class="hljs-number">10.0</span>.<span class="hljs-number">1.20</span><br> <span class="hljs-keyword">User</span> <span class="hljs-title">app</span><br> ProxyJump jump<br></code></pre></td></tr></table></figure><p><code>vscode</code> 下载相应的插件以后读取这个配置,就能直接连接内部服务器,实现代码编辑和文件上传(vscode 内置的 SFTP )。也有人用 xftp 或者 Filezilla 来实现文件传输,我没用过,所以暂时不评论。</p><p>Ps. <code>vscode</code> 具体操作就是调出命令面板,remote-ssh: connet to host。</p><ul><li><h4 id="内网穿透"><a href="#内网穿透" class="headerlink" title="内网穿透"></a>内网穿透</h4></li></ul><p>这个暂时没有接触到,先挖个坑。。。</p>]]></content>
<categories>
<category>计算机网络</category>
</categories>
</entry>
<entry>
<title>引用第三方库</title>
<link href="/2025/11/26/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/"/>
<url>/2025/11/26/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/</url>
<content type="html"><![CDATA[<h1 id="引用第三方库"><a href="#引用第三方库" class="headerlink" title="引用第三方库"></a>引用第三方库</h1><h2 id="Windows-环境"><a href="#Windows-环境" class="headerlink" title="Windows 环境"></a>Windows 环境</h2><p>一般引用第三方库的时候,主要需要三个东西:</p><ol><li>头文件(include):包含库的函数声明,类定义等等</li><li>库文件(lib):包含编译好的二进制代码</li><li>运行时库(dll):动态链接库,静态链接库则不需要</li></ol><p>为了得到这三个东西,一般的流程是:</p><h3 id="获取项目源码"><a href="#获取项目源码" class="headerlink" title="获取项目源码"></a>获取项目源码</h3><p>这个一般直接从 github 直接拉下来就行,或者浏览器直接下载等等也行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 从 GitHub 克隆或下载源码</span><br>git <span class="hljs-built_in">clone</span> https://github.com/grpc/grpc<br></code></pre></td></tr></table></figure><h3 id="CMake-配置和生成"><a href="#CMake-配置和生成" class="headerlink" title="CMake 配置和生成"></a>CMake 配置和生成</h3><p>注意:<code>cmake</code>的配置和生成阶段<strong>不涉及</strong>编译。目的是生成构建系统所需要的文件(如 <code>.sln</code>和 <code>Makefile</code>文件)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> configure: 解析 CMakeLists.txt 中的配置选项,设置项目变量和缓存,检查依赖关系<br><span class="hljs-number">2.</span> generate: 生成构建系统所需要的文件(如 .sln 和 .vcxproj 项目文件)<br></code></pre></td></tr></table></figure><p>所以 <code>CMake</code>不是编译器,它只是一个跨平台的<strong>构建工具</strong></p><h3 id="编译第三方库"><a href="#编译第三方库" class="headerlink" title="编译第三方库"></a>编译第三方库</h3><p>这一步在 <code>windows</code>通常用 <code>msvc</code>来完成,将源代码编译成二进制文件(如果是可执行项目,可以直接生成 .exe,第三方库就是 .lib 和 .dll 文件 。换句话说 .lib 文件 / .dll 文件 / .exe 文件都是二进制文件)</p><ol><li>选择配置(Debug/Release)</li><li>编译源代码,生成 .obj 文件</li><li>链接 .obj 文件 -> 生成最终的 .lib 或者 .dll 文件(可执行项目就是 .exe 文件)</li></ol><p>Ps. 点击生成之前需要进行一个<strong>配置</strong>,很重要,很多项目不兼容就是因为这个原因。项目中的所有库必须使用<strong>相同的运行时库类型(runtime-link)</strong>!</p><p>这一步就是在配置运行时库的一个类型(属性 -> C/C++ -> 代码生成 -> 运行库)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> <span class="hljs-built_in">MT</span>(Release): 静态链接多线程运行时库<br><span class="hljs-number">2.</span> <span class="hljs-built_in">MD</span>(Release): 动态链接多线程运行时库<br><span class="hljs-number">3.</span> <span class="hljs-built_in">MTd</span>(Debug): 静态链接多线程调试运行时库<br><span class="hljs-number">4.</span> <span class="hljs-built_in">MDd</span>(Debug): 动态链接多线程调试运行时库<br></code></pre></td></tr></table></figure><p>上面这么多类型其实不用记,只需要记住如果有一个项目选择的是 <code>shared</code>方式,其余所有的项目都需要选择 <strong>MD</strong>。</p><h3 id="配置第三方库"><a href="#配置第三方库" class="headerlink" title="配置第三方库"></a>配置第三方库</h3><p>在上一步库的编译以后,会得到第三方库的目录</p><ol><li>include: 头文件目录</li><li>lib:库文件目录(grpc.dll, grpc.lib)</li></ol><p>或者可执行文件目录 bin,然后接下来就是比较重要的配置第三方库:</p><ol><li>包含目录(属性 -> VC++ 目录 -> 包含目录)输入第三方库 <code>include</code> 文件夹的路径</li><li>库目录(属性->VC++目录->库目录)输入第三方库 <code>lib</code> 文件夹的路径</li><li>运行时库(属性->链接器->输入->附加依赖项)输入 <code>lib</code> 文件夹下具体 lib/dll 文件的全称(有些库比如<code>boost</code>库不需要)</li></ol><h2 id="Linux-环境"><a href="#Linux-环境" class="headerlink" title="Linux 环境"></a>Linux 环境</h2><h3 id="1-vscode-clangd-conan配置开源项目"><a href="#1-vscode-clangd-conan配置开源项目" class="headerlink" title="1. vscode + clangd + conan配置开源项目"></a>1. <code>vscode + clangd + conan</code>配置开源项目</h3><ul><li>配置开源项目</li></ul><p>a. 项目中一般有 <code>conanfile.txt</code>,所以第一步就直接安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">conan install . --build=missing -s build_type=Release<br></code></pre></td></tr></table></figure><p>b. 为了让 clangd 正常工作,这里要首先 CMake 生成 <code>compile_commands.json</code>(作者一般写好了,直接构建就行)</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs cmake">cmake \<br> -DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake \<br> -DCMAKE_BUILD_TYPE=Release \<br> -S . \<br> -B build \<br> -G Ninja<br></code></pre></td></tr></table></figure><p>c. 创建软链接,让 <code>clangd</code>自动发现</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">ln</span> -sf build/compile_commands.json ./<br></code></pre></td></tr></table></figure><ul><li>重新配置项目环境</li></ul><p>a. 写 <code>conanfile.txt</code>,因为这里一般是复现,可以直接把作者的该文件抄过来</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs nix">[requires]<br>fmt<span class="hljs-symbol">/11.1.3</span><br>spdlog<span class="hljs-symbol">/1.15.1</span><br>cpp-httplib<span class="hljs-symbol">/0.20.1</span><br>nlohmann_json<span class="hljs-symbol">/3.12.0</span><br>gflags<span class="hljs-symbol">/2.2.2</span><br><br>[generators]<br>CMakeDeps<br>CMakeToolchain<br><br>[layout]<br>cmake_layout<br></code></pre></td></tr></table></figure><p>b. 重新检测缓存环境,于项目根目录下生成第三方库的配置环境</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">conan install . --build=missing -s build_type=Release<br></code></pre></td></tr></table></figure><p>c.写 <code>CMakeLists</code>来构建 <code>compile_commands.json</code>文件,注意一定要<strong>导出编译数据库</strong>,<strong>查找依赖</strong>和<strong>链接库</strong></p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs cmake"><span class="hljs-comment"># CMakeLists.txt - 最小可工作版本</span><br><span class="hljs-keyword">cmake_minimum_required</span>(VERSION <span class="hljs-number">3.20</span>)<br><span class="hljs-keyword">project</span>(Demo LANGUAGES CXX)<br><br><span class="hljs-comment"># ✅ 关键:导出编译数据库(让 clangd 可用)</span><br><span class="hljs-keyword">set</span>(CMAKE_EXPORT_COMPILE_COMMANDS <span class="hljs-keyword">ON</span>)<br><br><span class="hljs-comment"># 设置 C++ 标准</span><br><span class="hljs-keyword">set</span>(CMAKE_CXX_STANDARD <span class="hljs-number">17</span>)<br><span class="hljs-keyword">set</span>(CMAKE_CXX_STANDARD_REQUIRED <span class="hljs-keyword">ON</span>)<br><br><span class="hljs-comment"># 查找 Conan 安装的依赖</span><br><span class="hljs-keyword">find_package</span>(fmt CONFIG REQUIRED)<br><span class="hljs-keyword">find_package</span>(spdlog CONFIG REQUIRED)<br><span class="hljs-keyword">find_package</span>(httplib CONFIG REQUIRED)<br><span class="hljs-keyword">find_package</span>(nlohmann_json CONFIG REQUIRED)<br><span class="hljs-keyword">find_package</span>(gflags CONFIG REQUIRED)<br><br><span class="hljs-comment"># 添加一个空的可执行文件(只是为了触发编译和生成 compile_commands.json)</span><br><span class="hljs-keyword">add_executable</span>(demo main.cpp)<br><br><span class="hljs-comment"># 链接你关心的库(告诉 CMake:“我要用这些头文件和库”)</span><br><span class="hljs-keyword">target_link_libraries</span>(demo PRIVATE<br> fmt::fmt<br> spdlog::spdlog<br> httplib::httplib<br> nlohmann_json::nlohmann_json<br> gflags::gflags<br>)<br></code></pre></td></tr></table></figure><p>d. 执行 CMake 并将生成的 <code>compile_commands.json</code>软链接到项目根目录下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">cmake \<br> -DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake \<br> -DCMAKE_BUILD_TYPE=Release \<br> -S . \<br> -B build \<br> -G Ninja<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">ln</span> -sf build/compile_commands.json ./<br></code></pre></td></tr></table></figure><h3 id="2-vs2022-ssh-配置开源项目(前提是项目是存在本地的)"><a href="#2-vs2022-ssh-配置开源项目(前提是项目是存在本地的)" class="headerlink" title="2. vs2022 + ssh 配置开源项目(前提是项目是存在本地的)"></a>2. <code>vs2022 + ssh</code> 配置开源项目(前提是项目是存在本地的)</h3><p>a. 通过 ssh 连接到远程系统</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 主机名一般填 <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> (wsl),其它看情况<br><span class="hljs-number">2.</span> 端口名一般就是 <span class="hljs-number">22</span><br><span class="hljs-number">3.</span> 用户名最好填 root,免得出现权限问题<br><span class="hljs-number">4.</span> 密码就是 sudo passwd root 的密码<br></code></pre></td></tr></table></figure><p>b. 指定运行的计算机</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 项目属性 -> 常规 -> 远程生成计算机<br><span class="hljs-number">2.</span> 同时下面可以指定远程生成代码的根目录 <br></code></pre></td></tr></table></figure><p>c. 指定调试的绝对路径</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-number">1.</span> 项目属性 -> 调试 -> 程序(写入绝对路径)<br>ps. 这里的绝对路径是指的远端程序的绝对路径,就是刚刚指定生成远程代码根目录下面,比如:<br>/root/projects/LinuxConsole/bin/x64/Debug/LinuxConsole.out <br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>项目经验</category>
</categories>
</entry>
<entry>
<title>Hot 100 刷题记录</title>
<link href="/2025/01/13/Hot100%20%E8%AE%B0%E5%BD%95/"/>
<url>/2025/01/13/Hot100%20%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<h1 id="Hot-100-刷题记录"><a href="#Hot-100-刷题记录" class="headerlink" title="Hot 100 刷题记录"></a>Hot 100 刷题记录</h1><h3 id="(1)滑动窗口套路"><a href="#(1)滑动窗口套路" class="headerlink" title="(1)滑动窗口套路"></a>(1)滑动窗口套路</h3><ul><li><strong>定长滑动窗口</strong></li></ul><p>总结成三步:<strong>入-更新-出</strong>。</p><p><strong>入</strong>:下标为 i 的元素进入窗口,更新相关统计量。如果窗口左端点 i−k+1<0,即 i<k−1,则尚未形成第一个窗口,重复第一步。</p><p><strong>更新</strong>:更新答案。一般是更新最大值/最小值。</p><p><strong>出</strong>:下标为 i−k+1 的元素离开窗口,更新相关统计量,为下一个循环做准备。</p><p>比如</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < s.<span class="hljs-built_in">size</span>(); ++i) {<br> <span class="hljs-keyword">if</span>(uset.<span class="hljs-built_in">count</span>(s[i])) cnt ++;<span class="hljs-comment">// 入</span><br> res = std::<span class="hljs-built_in">max</span>(res, cnt);<span class="hljs-comment">// 更新答案</span><br> <span class="hljs-keyword">if</span>(i < k - <span class="hljs-number">1</span>) <span class="hljs-keyword">continue</span>;<br> <span class="hljs-keyword">if</span>(uset.<span class="hljs-built_in">count</span>(s[i - k + <span class="hljs-number">1</span>])) cnt --; <span class="hljs-comment">// 出</span><br>}<br></code></pre></td></tr></table></figure><ul><li><strong>不定长滑动窗口</strong></li></ul><ol><li>越长越合法:</li></ol><p>采取在 while 循环<strong>外</strong>更新答案</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">for</span>(...) {<br>umap[c] ++;<span class="hljs-comment">// 入</span><br> <span class="hljs-keyword">while</span>(umap[c] > <span class="hljs-number">1</span>) { <span class="hljs-comment">// 合法性判断</span><br> umap[s[left]] --; <span class="hljs-comment">// 出</span><br> left ++; <br> }<br> <span class="hljs-comment">// 更新</span><br> ans = std::<span class="hljs-built_in">max</span>(ans, right - left + <span class="hljs-number">1</span>);<br>}<br></code></pre></td></tr></table></figure><ol start="2"><li>越短越合法:</li></ol><p>采取在 while 循环<strong>内</strong>更新答案</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c++"><span class="hljs-keyword">for</span>(...) {<br>sum += nums[right]; <span class="hljs-comment">// 入</span><br> <span class="hljs-keyword">while</span>(sum >= target) { <span class="hljs-comment">// 合法性判断</span><br> res = <span class="hljs-built_in">min</span>(res, right - left + <span class="hljs-number">1</span>); <span class="hljs-comment">// 更新</span><br> sum -= nums[left];<span class="hljs-comment">// 出</span><br> left ++;<br>}<br>}<br></code></pre></td></tr></table></figure><ol start="3"><li>求子数组个数:</li></ol><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">暂时还没刷到<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>算法题</category>
</categories>
</entry>
</search>