一、爬取基础

1
检查网页源代码的目的是:查看所要爬取的内容是由那个url加载的,之后的代码实现就是围绕这个url

1.简单网页抓取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from urllib.request import urlopen # urllib中的一个叫request模块中的urlopen函数

url = "https://www.baidu.com"
response = urlopen(url) # urlopen函数是访问这个网址,返回一个http.client.HTTPResponse对象



print(response.read().decode("utf-8")) # read()函数是读取响应体,返回字节类型的内容 # ctrl+f charset: 得知网页是由那个字符集编码的
# 字符解码之后,就可看到正常的html代码了

# 将html代码写入到test.html文件中

with open("./test.html", "w", encoding="utf-8") as f: # with: 上下文管理器 open()函数: 打开一个文件,"w"表示写入模式,encoding指定编码格式 as f: 表示将文件对象赋值给f
f.write(response.read().decode("utf-8"))


上述脚本是无法实现要求的:
HTTP响应体只能被读取一次。当你第一次调用response.read()后,响应内容已经被消耗了,第二次调用会返回空字节,所以文件写入的是空内容

也就是说,要使用变量存储一下网页内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from urllib.request import urlopen # urllib中的一个叫request模块中的urlopen函数

url = "https://www.baidu.com"
response = urlopen(url) # urlopen函数是访问这个网址,返回一个http.client.HTTPResponse对象

html_content = response.read().decode("utf-8")

print(html_content) # read()函数是读取响应体,返回字节类型的内容 # ctrl+f charset: 得知网页是由那个字符集编码的
# 字符解码之后,就可看到正常的html代码了

# 将html代码写入到test.html文件中

with open("./test.html", "w", encoding="utf-8") as f: # with: 上下文管理器 open()函数: 打开一个文件,"w"表示写入模式,encoding指定编码格式 as f: 表示将文件对象赋值给f
f.write(html_content) # write()函数: 将内容写入文件

# w 覆盖 a 追加


2.requests库-get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一些基本框架

import requests

# 爬取百度的页面源代码

url = "http://www.baidu.com"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象

print(resp.status_code) # 获取状态码

resp.encoding = "utf-8" # 手动指定编码格式

print(resp.text) # 拿到页面源代码


1
2
3
4
5
6
7
8
9
10
11
12
import requests

# 爬取百度的页面源代码

query = input("请输入要搜索的内容: ")
url = f"https://www.sogou.com/web?query={query}"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求

print(resp.text) # 拿到页面源代码


问题出现在发送的请求头: user-agent
print(resp.request.headers)
{‘User-Agent’: ‘python-requests/2.32.5’, ‘Accept-Encoding’: ‘gzip, deflate’, ‘Accept’: ‘/‘, ‘Connection’: ‘keep-alive’, ‘Cookie’: ‘ABTEST=0|1769246131|v17; SNUID=F6B114304146085FF7752DA84149FB07; IPLOC=CN4412; SUID=B7F05571DBA6A20B0000000069748DB3; cuid=AAFCduelWAAAAAuippMXowEAEAM=’}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

# 爬取百度的页面源代码

query = input("请输入要搜索的内容: ")
url = f"https://www.sogou.com/web?query={query}"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求`

headers= {

"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0"

}

resp = requests.get(url, headers=headers) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求`

print(resp.text) # 拿到页面源代码


3.requests库-post

此时的请求行:https://fanyi.baidu.com/sug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

# 爬取百度的页面源代码

url = "https://fanyi.baidu.com/sug"

data = {

# "kw": "你好"
"kw": input("请输入要翻译的内容: ")

}

resp = requests.post(url, data=data) # 发送POST请求

# print(resp.text) # .text是拿到响应体的文本内容
print(resp.json()) # .json()是拿到响应体的JSON字符串,然后转换为Python字典

4.多get请求

  • 参数
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
# 多get参数

import requests

url = "https://movie.douban.com/j/chart/top_list?"

params = {
"type": 13,
"interval_id": "100:90",
"action": "",
"start": 0,
"limit": 20
}

headers = { # 模拟浏览器访问 绕过反爬
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}

resp = requests.get(url, params=params, headers=headers)

print(resp.request.url)
print(resp.status_code)
print(resp.text)
print(resp.json())

二、数据解析

1.python正则

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
. : 匹配除\n外任意单个字符  re模块的一个坑
\d : 匹配数字(0-9)
\D : 匹配非数字
\w : 匹配字母/数字/下划线
\W : 匹配非字母/数字/下划线
\s : 匹配空白字符(空格/\t/\n)
\S : 匹配非空白字符

--------------------------------------
* : 前字符匹配0+次(贪婪)
*? : 前字符匹配0+次(非贪婪)
+ : 前字符匹配1+次(贪婪)
+? : 前字符匹配1+次(非贪婪)
? : 前字符匹配0/1次(贪婪)
?? : 前字符匹配0/1次(非贪婪)
{n} : 前字符匹配n次
{n,} : 前字符匹配≥n次(贪婪)
{n,}? : 前字符匹配≥n次(非贪婪)
{n,m} : 前字符匹配n-m次(贪婪)
{n,m}? : 前字符匹配n-m次(非贪婪)


--------------------------------------
^ : 匹配字符串开头(re.M模式匹配每行开头)
$ : 匹配字符串结尾(re.M模式匹配每行结尾)
\b : 匹配单词边界
\B : 匹配非单词边界
() : 分组(可通过\num引用)
(?:) : 非捕获分组
| : 多选一匹配
\num : 反向引用第num个分组结果
\ : 转义元字符为字面量(如\.匹配点)
[] : 字符集合(匹配内任意字符)
[^] : 否定集合(匹配外任意字符)
  • 重要例子理解
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
这是对于一个匹配的字符所说的匹配多少次
比如 "我有100000元" \d* \d? \d+

字符序列:我(0)、有(1)、1(2)、0(3)、0(4)、0(5)、0(6)、0(7)、元(8)

\d* 允许匹配 0 次数字(非数字位置),遇到数字则贪婪匹配所有连续数字:
位置 0(字符我):非数字 → \d* 匹配 0 次数字 → 结果:空字符串'' 注意,他是有匹配的,只是匹配结果为空
位置 1(字符有):非数字 → \d* 匹配 0 次数字 → 结果:空字符串''
位置 2(字符1):是数字 → 贪婪向后匹配所有连续数字(位置 2-7:1、0、0、0、0、0)→ 结果:100000
位置 8(字符元):非数字 → \d* 匹配 0 次数字 → 结果:空字符串''


\d? 最多匹配 1 次数字,非数字位置匹配 0 次,数字位置仅匹配 1 个字符:
位置 0(字符我):非数字 → 匹配 0 次 → 结果:''
位置 1(字符有):非数字 → 匹配 0 次 → 结果:''
位置 2(字符1):是数字 → 匹配 1 次 → 结果:1
位置 3(字符0):是数字 → 匹配 1 次 → 结果:0
位置 4(字符0):是数字 → 匹配 1 次 → 结果:0
位置 5(字符0):是数字 → 匹配 1 次 → 结果:0
位置 6(字符0):是数字 → 匹配 1 次 → 结果:0
位置 7(字符0):是数字 → 匹配 1 次 → 结果:0
位置 8(字符元):非数字 → 匹配 0 次 → 结果:''


\d+ 要求至少匹配 1 次数字,非数字位置直接跳过,遇到数字则贪婪匹配所有连续数字:
位置 0(字符我):非数字 → 不满足 “至少 1 次” → 无匹配结果
位置 1(字符有):非数字 → 不满足 “至少 1 次” → 无匹配结果
位置 2(字符1):是数字 → 贪婪向后匹配所有连续数字(位置 2-7:100000)→ 结果:100000
位置 8(字符元):非数字 → 不满足 “至少 1 次” → 无匹配结果





玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏 玩.*游戏

字符序列:玩(0)、吃(1)、鸡(2)、游(3)、戏(4)、,(5)、干(6)、嘛(7)、呢(8)、?(9)、上(10)、机(11)、玩(12)、游(13)、戏(14)、,(15)、玩(16)、游(17)、戏(18)

正则首先匹配第一个玩(位置 0),触发匹配起始;
.* 进入贪婪模式,从位置 1 开始向后尽可能多匹配所有字符,直到找到最后一个能匹配游戏的位置:
先遇到位置 3-4 的 “游戏”,但*贪婪,不会停,继续向后匹配;
再遇到位置 13-14 的 “游戏”,仍不停,继续匹配到位置 17-18 的 “游戏”(最后一个);
匹配终止,最终匹配范围是:位置 0 → 位置 18(即从第一个 “玩” 到最后一个 “游戏” 的全部内容)。

结果:['玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏']


玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏 玩.*?游戏

第一步:匹配第一个 “玩吃鸡游戏”
正则先匹配第一个 “玩”(位置 0),触发匹配起始;
.*? 开始非贪婪匹配中间字符,从位置 1 开始向后找,直到遇到第一个 “游戏”:
位置 1(吃)→ 位置 2(鸡)→ 位置 3(游)→ 位置 4(戏):遇到 “游戏”(位置 3-4),.*? 停止匹配(仅匹配了 “吃鸡”);
匹配终止,第一个结果:玩吃鸡游戏(位置 0-4)。

第二步:匹配第二个 “玩游戏”
正则从第一步结束的位置(位置 5)继续向后找下一个 “玩”,直到位置 12(字符 “玩”),触发第二次匹配起始;
.*? 非贪婪匹配中间字符,从位置 13 开始:
位置 13(游)→ 位置 14(戏):直接遇到 “游戏”,.*? 匹配 0 个中间字符;
匹配终止,第二个结果:玩游戏(位置 12-14)。

第三步:匹配第三个 “玩游戏”
正则从第二步结束的位置(位置 15)继续向后找下一个 “玩”,直到位置 16(字符 “玩”),触发第三次匹配起始;
.*? 非贪婪匹配中间字符,从位置 17 开始:
位置 17(游)→ 位置 18(戏):直接遇到 “游戏”,.*? 匹配 0 个中间字符;
匹配终止,第三个结果:玩游戏(位置 16-18)。

['玩吃鸡游戏', '玩游戏', '玩游戏'] 事实上这个机制是现贪婪找到最后一个游戏,再往回溯



2.re库的使用

  • 基础函数
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
import re


result1 = re.findall(r"\d+", "123a456b789") # 查找所有符合正则表达式的字符串 参数1: 正则表达式 参数2: 要匹配的字符串
# 加 r 表示原始字符串 避免转义字符的问题
print(result1)
print("".join(result1)) # 将列表转换为字符串

result2 = re.finditer(r"\d+", "我今年18岁,有10000000块") # finditer 返回的是匹配对象的迭代器
for i in result2: # nums 这里是迭代器数组
print(i.group()) # 打印匹配到的字符串

result3 = re.search(r"\d+", "我叫周杰伦,今年18岁,是五年级4班学生") # search 返回的是匹配到的第一个字符串
print(result3.group()) # 打印匹配到的字符串



result4 = re.match(r"\d+", "我叫周杰伦,今年18岁,是五年级4班学生") # match 是在字符串的开头进行匹配,类似在真正表达式的开头加上 ^
# 即默认是 ^\d+ 很少用
print(result4.group()) # 打印匹配到的字符串


# 预加载正则表达式
# 作用:将正则表达式编译为正则表达式对象
# 编译后的正则表达式对象可以重复使用,避免重复编译

pattern = re.compile(r"\d+")



  • 例子
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
import re


content = """

<div class='西游记'><span id='10010'>中国联通</span></div>
<div class='红楼梦'><span id='10086'>中国移动</span></div>
<div class='水浒传'><span id='10001'>中国电信</span></div>
<div class='三国演义'><span id='10000'>广电</span></div>

"""

pattern_1 = re.compile(r"<div class='.*'><span id='\d*'>.*?</span></div>")

result = pattern_1.findall(content)

for item in result:
print(item)


print("-"*50)

# 提取数据就需要用到()分组

pattern_2 = re.compile(r"<div class='(.*)'><span id='(\d*)'>(.*?)</span></div>")

result = pattern_2.findall(content)

for item in result:
print(item)


print("-"*50)

pattern_3 = re.compile(r"<div class='(.*)'><span id='(?P<id>\d*)'>(?P<name>.*?)</span></div>")
# 没有设置字段名的会被忽略

result = pattern_3.finditer(content)

for item in result:
print(item.groupdict())
print(item.group("id"), item.group("name"))



<div class='西游记'><span id='10010'>中国联通</span></div>
<div class='红楼梦'><span id='10086'>中国移动</span></div>
<div class='水浒传'><span id='10001'>中国电信</span></div>
<div class='三国演义'><span id='10000'>广电</span></div>
--------------------------------------------------
('西游记', '10010', '中国联通')
('红楼梦', '10086', '中国移动')
('水浒传', '10001', '中国电信')
('三国演义', '10000', '广电')
--------------------------------------------------
{'id': '10010', 'name': '中国联通'}
10010 中国联通
{'id': '10086', 'name': '中国移动'}
10086 中国移动
{'id': '10001', 'name': '中国电信'}
10001 中国电信
{'id': '10000', 'name': '广电'}
10000 广电


3.bs4解析

1
2
3
4
bs4是 Python 专门用于解析 HTML/XML 文档的第三方库,可以把它理解成一个 “网页内容整理工”:
网页源代码本质是一堆杂乱的字符串(比如你之前爬取的电影页面源码),直接用正则提取内容需要写复杂的匹配规则,还容易出错;
bs4能把这堆字符串转换成结构化、可操作的对象,像 “查字典” 一样
通过标签名(如<div>、<a>)、类名(class)、属性(href)等简单规则,精准提取想要的内容,不用再写复杂的正则。
  • bs4_base
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
from bs4 import BeautifulSoup # 从bs4库导入BeautifulSoup类

simple_html = """
<ul>
<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
<li><a href="zhubajie.com">猪八戒</a></li>
<li><a href="wuzetian.com">武则天</a></li>
</ul>
"""

# 创建BeautifulSoup对象(核心:解析HTML)
# "html.parser"是Python内置的解析器,无需额外安装
soup = BeautifulSoup(simple_html, "html.parser")

# find("标签名") 第一个符合条件的标签
# find("标签名", attrs={"属性名": "属性值"}) 查找对应值的标签
# find_all() 方法: 查找所有符合条件的标签
# 嵌套的标签可以逐层提取
# 例如:先找到<ul>,再在<ul>中找<li>

li_1 = soup.find("li")
print(li_1)

li_2 = soup.find("li", attrs={"id": "abc"})
print(li_2)

print("-" * 20)

ul = soup.find("ul")
print(ul)

li_list = ul.find_all("li")

for li in li_list:
a = li.find("a")
print(a)
print(a["href"]) # 或 a.get("href")
print(a.text)


# 拿文本 .text 或 .get_text()
# 拿属性 ["属性名"] 或 .get("属性名")


<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
--------------------
<ul>
<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
<li><a href="zhubajie.com">猪八戒</a></li>
<li><a href="wuzetian.com">武则天</a></li>
</ul>
<a href="zhangwuji.com">张无忌</a>
zhangwuji.com
张无忌
<a href="zhouxingchi.com">周星驰</a>
zhouxingchi.com
周星驰
<a href="zhubajie.com">猪八戒</a>
zhubajie.com
猪八戒
<a href="wuzetian.com">武则天</a>
wuzetian.com
武则天


4.xpath解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
XPath 是什么?
1.html 是 xml 的子集
2.把 HTML/XML 文档想象成一棵「标签树」(比如<html>是根节点,<div>是分支,<a>是叶子),XPath 就是这棵树的「导航路径」——
3.通过写 XPath 表达式,能精准定位到你想要的标签 / 属性 / 文本就像用地址找快递:中国→北京市→丰台区→XX小区→1号楼→301室 一样,XPath 就是给 HTML 标签写 “地址"


//:从文档任意位置找(忽略层级);
/:子节点(必须是直接子级);
@:匹配属性(比如@class、@id、@href);
text():提取标签内的文本;
[]:条件筛选(比如[@class='xxx']、[1])



  • xml
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
from lxml import etree


xml = '''

<book>
<id>1</id>
<name>野花遍地香</name>
<name>野花遍地不香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="joy">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>惹了</nick>
</div>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>

'''

# 有html() xml()
et = etree.XML(xml)
book = et.xpath("/book") # / 表示根节点
print(book)
name = et.xpath("/book/name")
print(name)
name_text = et.xpath("/book/name/text()") # text() 表示获取文本内容, 返回的是一个列表
print(name_text)
print(name_text[0])
nick_list1 = et.xpath("/book//nick") # /book//nick 表示获取所有子孙节点中所有的nick标签
print(nick_list1)
nick_list2 = et.xpath("/book/*/nick") # /book/*/nick 表示获取所有直接子节点中所有的nick标签
print(nick_list2)

jay = et.xpath("/book/author/nick[@class='joy']/text()") # @class='joy' 表示获取class属性值为joy的nick标签 返回的依然是一个列表
print(jay[0])

ids = et.xpath("/book/partner/nick/@id") # @id 表示获取id属性值 返回的依然是一个列表
for id in ids:
print(id)


  • html
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
from lxml import etree


html = '''

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉嘉</div>
<div class="common">胡辣汤</div>
</body>
</html>


'''

et = etree.HTML(html)

google = et.xpath("/html/body/ul/li[2]/a/@href") # 其实位置是1
print(google[0])


# 就找li

li_list = et.xpath("//li")

for li in li_list:
href = li.xpath("./a/@href") # 列表
text = li.xpath("./a/text()") # 列表
print(href, text)



http://www.google.com
['http://www.baidu.com'] ['百度']
['http://www.google.com'] ['谷歌']
['http://www.sogou.com'] ['搜狗']
['feiji'] ['飞机']
['dapao'] ['大炮']
['huoche'] ['火车']

  • 在大量的网页代码确定xpath路径

实战1:爬取电影top250

  • 爬取模板
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
<li>
<div class="item">
<div class="pic">
<em>1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/">
<span class="title">肖申克的救赎</span>
<span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
<span class="other">&nbsp;/&nbsp;月黑高飞(港) / 刺激1995(台)</span>
</a>


<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p>
导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
</p>


<div>
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>3251199人评价</span>
</div>

<p class="quote">
<span>希望让人自由。</span>
</p>


<p>

<span class="gact">
<a href="https://movie.douban.com/wish/293299521/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-wish" rel="nofollow">想看</a>
</span>&nbsp;&nbsp;

<span class="gact">
<a href="https://movie.douban.com/collection/293299521/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-collection" rel="nofollow">看过</a>
</span>&nbsp;&nbsp;
</p>

</div>
</div>
</div>
</li>
  • cookie获取
  • 页面跳转
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
import requests
import re
import csv # 用于CSV写入
import json # 用于json写入
import random # 用于随机延时
import time # 用于延时


base_url = "https://movie.douban.com/top250"

# pattern = re.compile(
# r'''
# <li>.*?
# <span class="title">(?P<name>.*?)</span>
# .*?<p>.*?导演:(?P<director>.*?)
# .*?&nbsp;(?P<actor>.*?)
# .*?<br>(?P<year>.*?)
# .*?&nbsp;(?P<contry>.*?)
# .*?&nbsp;(?P<type>.*?)
# .*?(?P<score>\d+)</span>
# .*?(?P<persons>\d)人评价
# </li>
# '''
# , re.S) # 我的错误

pattern = re.compile(
r'<li>.*?'
# 电影名称:匹配到下一个<span class="title">(副标题)或</a>截止
r'<span class="title">(?P<name>[^/]+?)</span>.*?'
# 导演+主演:匹配到<p>闭合标签前的<br>截止
r'导演:\s*(?P<director>[^&]+?)\s*&nbsp;&nbsp;&nbsp;主演:\s*(?P<actor>[^<]+?)\s*<br>.*?'
# 年份/国家/类型:匹配到</p>截止(避免包含后面的<div>)
r'(?P<year>\d{4})\s*&nbsp;/&nbsp;\s*(?P<country>[^&]+?)\s*&nbsp;/&nbsp;\s*(?P<type>[^<]+?)\s*</p>.*?'
# 评分:精准匹配rating_num标签内的内容
r'<span class="rating_num" property="v:average">(?P<score>\d+\.\d+)</span>.*?'
# 评价人数:匹配到“人评价”前的数字,后面跟</span>截止
r'(?P<persons>\d+)人评价</span>'
, re.S | re.M)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Referer": "https://www.douban.com/", # 增加来源页,模拟正常访问
"Cookie": 'll="118290"; bid=P5S04iWSpqg; _vwo_uuid_v2=DC66DCF2BF77499C0C67FB201BB813A47|989d543c015801abda54a5fb298d38ec; ap_v=0,6.0; dbcl2="293299521:v1DkVrRnkvM"; ck=-2P0; push_noty_num=0; push_doumail_num=0; frodotk_db="64215c40a36a96dc2c6414c1ea3fc494"'
}


movie_list = [] # 用于存储所有电影信息的列表
movie_rank = 1 # 用于记录当前电影的排名

try:

for start in range(0, 226, 25):

page_num = start // 25 + 1
url = f"{base_url}?start={start}&filter="
time.sleep(random.uniform(1, 2))
result = requests.get(url, headers=headers)
result.encoding = 'utf-8'
result = pattern.finditer(result.text)

for item in result:
movie_list.append({
"rank": movie_rank,
"name": item.group("name"),
"director": item.group("director"),
"actor": item.group("actor"),
"year": item.group("year"),
"country": item.group("country"),
"type": item.group("type"),
"score": item.group("score"),
"persons": item.group("persons")
})
movie_rank += 1

if movie_list:
print("成功收集到电影信息")

with open("doubao_top250.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=movie_list[0].keys())
writer.writeheader()
writer.writerows(movie_list)

print("电影信息已写入doubao_top250.csv")

else:
print("未收集到电影信息")


except Exception as e:
print(e)


实战2:爬取dy2018热片

  • 任务

爬取2026年热片信息,以及其下载地址

  • 很细节
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
import requests
import re
import csv

# 拿到2026必看热片的url 凭借存入数组中
# 逐个访问数组中的url,进入子页面获取其中的电影信息

base_url = "https://www.dytt8899.com/"

resp = requests.get(base_url)
resp.encoding = "gbk"

pattern_1 = re.compile(r"2026必看热片.*?<ul>(?P<html>.*?)</ul>", re.S)

result_1 = pattern_1.search(resp.text)

html = result_1.group("html")

# 仅保留href分组的正则
pattern_2 = re.compile(
r"<li><a href='(?P<href>.*?)' title=",
re.S # 开启单行模式,让.匹配换行符
)

result_2 = pattern_2.finditer(html)

movies_list = []

for item in result_2:
movies_list.append(base_url.strip("/") + item.group("href"))

for i in range(len(movies_list)): # 默认从0开始到len(movies_list)-1
print(movies_list[i])

# 访问子页面获取电影信息
movies_info = []

pattern_4 = re.compile(
r'<div id="Zoom">.*?◎片  名 (?P<name>.*?)<br />◎年  代 (?P<year>.*?)<br />.*?'
r'<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="(?P<magnet>.*?)">',
re.S
)

for url in movies_list:
resp = requests.get(url)
resp.encoding = "gbk"

result_4 = pattern_4.search(resp.text)

name = result_4.group("name")
year = result_4.group("year")
magnet = result_4.group("magnet")
movies_info.append({
"name": name,
"year": year,
"magnet": magnet
})

# 打印所有电影信息
for item in movies_info:
print(item)

# 1. 定义CSV文件路径和列名
csv_file = "./dy2018.csv"
# 列名要和字典的key对应
headers = ["name", "year", "magnet"]

# 2. 写入CSV(newline=''避免空行,encoding='utf-8-sig'避免中文乱码)
with open(csv_file, "w", newline="", encoding="utf-8-sig") as f:
# 创建CSV写入器
writer = csv.DictWriter(f, fieldnames=headers)
# 写入表头
writer.writeheader()
# 写入所有电影数据
writer.writerows(movies_info)

print(f"\n✅ 数据已成功写入 {csv_file} 文件!")

实战3:爬取新发地菜品单价

  • 请求方式
  • 参数
  • 返回数据的格式内容
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

# 单线程

import requests
import csv
import time

url = "http://www.xinfadi.com.cn/getPriceData.html"

# 请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded"
}

# 基础请求参数
base_data = {
"limit": "20",
"current": "1", # 初始页码是1
"pubDateStartTime": "2024/01/01",
"pubDateEndTime": "",
"prodPcatid": "",
"prodCatid": "",
"prodName": ""
}


# 先请求第1页,拿到总条数count
response = requests.post(url, headers=headers, data=base_data)
result = response.json() # 返回的是一个字典,包含count和list两个键
total_count = result["count"] # 总数据条数(比如页面显示的“共345962条”)
page_size = int(base_data["limit"]) # 每页20条
total_pages = (total_count // page_size) + 1 # 总页数(345962÷20≈17298页)
print(f"发现共{total_count}条数据,分{total_pages}页")


all_data = [] # 存所有页面的数据

for page in range(1, total_pages + 1):
try:
# 更新参数:当前页码
base_data["current"] = str(page)

# 发送请求
response = requests.post(url, headers=headers, data=base_data)
response.raise_for_status()
page_result = response.json()
page_data = page_result["list"] # 当前页的价格数据

# 把当前页数据加到总列表
all_data.extend(page_data)

# 打印进度
print(f"已爬第{page}/{total_pages}页,累计{len(all_data)}条数据")

# 加延时(每爬1页停0.5秒,避免给服务器压力)
time.sleep(0.5)

except Exception as e:
print(f"第{page}页爬取失败:{e}")
continue # 失败就跳过这一页,继续爬下一页

csv_headers = [
"一级分类", "二级分类", "品名", "最低价", "平均价",
"最高价", "规格", "产地", "单位", "发布日期"
]

# 打开CSV文件,写入数据
with open("xinfadi.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=csv_headers)
writer.writeheader() # 写入列名

# 循环每条数据,按列名对应字段
for item in all_data:
writer.writerow({
"一级分类": item.get("prodCat", ""),
"二级分类": item.get("prodPcat", ""),
"品名": item.get("prodName", ""),
"最低价": item.get("lowPrice", ""),
"平均价": item.get("avgPrice", ""),
"最高价": item.get("highPrice", ""),
"规格": item.get("specInfo", ""),
"产地": item.get("place", ""),
"单位": item.get("unitInfo", ""),
"发布日期": item.get("pubDate", "")[:10]
})

print(f"全部数据已写入CSV!共{len(all_data)}条")

一、Java虚拟机与平台特性

1. JVM架构详解

  • 类加载器子系统

    • 启动类加载器(Bootstrap):加载rt.jar等核心库
    • 扩展类加载器(Extension):加载jre/lib/ext目录
    • 应用类加载器(Application):加载CLASSPATH路径
    • 自定义类加载器:继承ClassLoader实现热部署
  • 运行时数据区

  • 垃圾回收机制

    • 分代收集:新生代(Eden+S0+S1)和老年代
    • GC算法
      • 标记-清除:产生内存碎片
      • 复制算法:适用于新生代(90%对象存活率低)
      • 标记-整理:适用于老年代
    • 常见GC器
      • Serial:单线程,适合客户端应用
      • Parallel:多线程吞吐量优先
      • CMS:低延迟,但会产生碎片
      • G1:分区收集,预测停顿时间

2. 跨平台原理

  • 字节码文件.class):
    • 由Java编译器生成,与操作系统无关
    • 包含:常量池、字段表、方法表、属性表
  • JVM执行过程
    1
    2
    3
    4
    5
    public class Test {
    public static void main(String[] args) {
    System.out.println("Hello");
    }
    }
  • 编译后字节码(javap -c Test.class):
    1
    2
    3
    4
    5
    6
    public static void main(java.lang.String[]);
    Code:
    0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc #3 // String Hello
    5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: return

二、基础语法与数据类型

1. 基本数据类型解析

基本类型 包装类 大小 默认值 取值范围 特殊说明
byte Byte 1字节 0 -128~127 适合存储小整数
short Short 2字节 0 -32768~32767
int Integer 4字节 0 -2^31~2^31-1 最常用整型
long Long 8字节 0L -2^63~2^63-1 后缀L
float Float 4字节 0.0f ±3.40282347E+38F 后缀f
double Double 8字节 0.0d ±1.79769313486231570E+308 默认浮点类型
char Character 2字节 ‘\u0000’ 0~65535 Unicode字符
boolean Boolean 1位 false true/false 无具体大小

关键说明

  1. 包装类均为引用类型,首字母大写(两个特殊情况:int 对应 Integerchar 对应 Character,其余基本类型直接首字母大写即可)
  2. 包装类主要用于需要使用对象的场景(如集合框架、反射等),可实现基本类型与包装类的自动装箱/拆箱
  • 自动类型转换规则

    1
    2
    3
    4
    int a = 10;
    double b = a; // 自动提升:int → double
    long c = 100L;
    int d = (int)c; // 显式转换,可能丢失精度
  • 装箱拆箱细节

    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
    //自动装箱:
    将基本类型(如 int)自动转换为包装类(如 Integer)。
    编译后等价于:Integer x = Integer.valueOf(100);
    //自动拆箱:
    将包装类(如 Integer)自动转换为基本类型(如 int)。
    编译后等价于:int y = x.intValue();

    Integer x = 100; // 自动装箱
    int y = x; // 自动拆箱

    // Integer缓存机制(-128~127)
    Integer a = 100;
    Integer b = 100;
    System.out.println(a == b); // true(引用相同)
    //Java 为 Integer 在 -128 ~ 127 范围内创建了缓存对象(通过 IntegerCache 实现)。
    //当通过 Integer.valueOf() 创建该范围内的值时,直接复用缓存对象,因此 a 和 b 指向同一个对象,== 比较返回true

    Integer c = 200;
    Integer d = 200;
    System.out.println(c == d); // false(超出缓存范围) 所以缓存范围要记住!

    //存在的意义:
    1.让基本类型“对象化”
    Java 的泛型、集合框架、反射、注解等机制只能操作对象(引用类型),不能操作基本类型。包装类(如 Integer)将基本类型“封装”为对象,解决了这一矛盾。

    2.支持 null
    基本类型(如 int)必须有默认值(int 默认是 0),无法表示“无值”状态。
    包装类可以为 null,适用于需要“可选值”的场景
    Integer age = null; // 合法:表示年龄未知
    int age = 0; // 不合法:0 可能被误解为有效值

    3.提供丰富的实用方法
    int num = Integer.parseInt("123"); // 字符串转数字
    String binary = Integer.toBinaryString(10); // 转二进制字符串
    int max = Integer.max(10, 20); // 比较大小

2. 字符串处理深度

  • String不可变性原理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    String 对象一旦创建,其内容无法修改
    效率问题:如果直接用 + 拼接多个字符串(如 a + b + c),每次操作都会创建新的 String 对象,导致大量临时对象和内存浪费。

    编译器的优化机制:
    当代码中出现字符串拼接(+)时,Java编译器会自动将其转换为 StringBuilder 的链式调用
    String s = "Hello";
    s = s + " World"; // 实际创建了新对象

    // 底层:new StringBuilder("Hello").append(" World").toString()
    1. new StringBuilder(s)
    将当前 s 的值("Hello")作为初始内容传入 StringBuilder 的构造函数。
    此时 StringBuilder 内部会复制 "Hello" 的字符数组(非引用传递)。
    2. .append(" World")
    " World" 追加到 StringBuilder 的内部缓冲区。
    内部通过 char[] 扩容和复制实现,避免频繁创建新对象。
    3. .toString()
    将 StringBuilder 的内容转换为一个新的 String 对象("Hello World")。
    这个新对象被赋值给 s,而原来的 "Hello" 对象(若无其他引用)将被垃圾回收。

  • 字符串常量池

    1
    2
    3
    4
    5
    String s1 = "Hello"; // 字面量->直接放入常量池(若不存在)
    String s2 = new String("Hello"); // 创建新对象,不在常量池,属于堆
    String s3 = "Hello"; // 字面量->引用常量池中的对象
    System.out.println(s1 == s2); // false
    System.out.println(s1 == s3); // true
  • String常用方法详解

    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
    // 1. 内容比较(区分大小写)
    "Hello".equals("hello"); // false(默认是严格对比)
    "Hello".equalsIgnoreCase("hello"); // true(不区分大小写)

    // 2. 字符串分割: split()函数的参数是正则表达式
    "a,b,c".split(","); // ["a","b","c"]
    "a..b".split("\\.\\."); // ["a","b"](转义点号)

    // 3. 字符串反转
    new StringBuilder("abc").reverse().toString(); // "cba"

    // 4. intern()方法
    String s = new String("abc").intern(); // 返回常量池中的引用

    1.new String("abc") 的创建过程:
    字面量 "abc":类加载时,"abc" 会被自动放入字符串常量池(如果不存在)。
    2.new String("abc"):
    强制在堆内存中创建新对象,内容为 "abc"。该对象与常量池中的 "abc" 是两个独立对象!此时堆中有一个新对象,常量池中有一个 "abc"
    3.调用 .intern() 的过程:
    JVM 检查常量池中是否已有内容相同的字符串("abc" 已存在),则直接返回常量池中的引用(不再创建新对象)。
    4.s 被赋值为常量池中 "abc" 的引用。

    intern() 作用的关键总结:
    如果常量池中已有该字符串,则返回池中的引用;
    如果不存在,则将当前字符串放入池中,再返回池中的引用。
  • StringBuilder vs StringBuffer

    特性 StringBuilder StringBuffer
    线程安全 ❌ 不安全 ✅ 安全(synchronized)
    性能 ⚡️ 更快 ⏳ 较慢
    使用场景 单线程环境 多线程环境
  • 用法完全一致!两者的 API 完全相同(方法名、参数、返回值、行为逻辑都一致),区别仅在于 线程安全性和性能。

三、正则表达式深度解析

正则表达式(Regular Expression)是处理字符串的强大工具,用于匹配、查找、替换符合特定规则的文本。Java中通过java.util.regex包提供正则支持,核心类为Pattern(编译正则表达式)和Matcher(执行匹配操作)。

1. 正则表达式基本语法

(1)字符匹配

语法 描述 示例
普通字符 匹配自身 abc 匹配 “abc”
. 匹配任意单个字符(除换行符) a.c 匹配 “abc”、”a1c”
[abc] 匹配括号内任意一个字符 [abc] 匹配 “a”、”b”、”c”
[^abc] 匹配不在括号内的任意字符 [^abc] 匹配 “d”、”1”
[a-z] 匹配指定范围的字符 [0-9a-zA-Z] 匹配数字或字母
\d 等价于 [0-9](数字) \d{3} 匹配 “123”
\D 等价于 [^0-9](非数字) \D 匹配 “a”、”!”
\w 等价于 [a-zA-Z0-9_](单词字符) \w+ 匹配 “user123”
\W 等价于 [^a-zA-Z0-9_](非单词字符) \W 匹配 “@”、”#”
\s 匹配空白字符(空格、制表符等) a\sb 匹配 “a b”、”a\tb”
\S 匹配非空白字符 a\Sb 匹配 “a1b”、”a@b”

(2)量词(控制匹配次数)

语法 描述 示例
* 匹配前一个元素0次或多次(贪婪模式) a* 匹配 “”、”a”、”aa”
+ 匹配前一个元素1次或多次(贪婪模式) a+ 匹配 “a”、”aa”
? 匹配前一个元素0次或1次(贪婪模式) a? 匹配 “”、”a”
{n} 匹配前一个元素恰好n次 a{3} 匹配 “aaa”
{n,} 匹配前一个元素至少n次 a{2,} 匹配 “aa”、”aaa”
{n,m} 匹配前一个元素n到m次 a{1,3} 匹配 “a”、”aa”、”aaa”
*?/+?/?? 非贪婪模式(尽可能少匹配) "a.*?b" 匹配 “aab” 中的 “aab”(而非贪婪的 “aab…b”)

(3)边界匹配

语法 描述 示例
^ 匹配字符串开始位置 ^abc 匹配 “abc123”(不匹配 “xabc”)
$ 匹配字符串结束位置 abc$ 匹配 “123abc”(不匹配 “abcx”)
\b 匹配单词边界(单词与非单词字符之间) \bcat\b 匹配 “cat”(不匹配 “category” 中的 “cat”)
\B 匹配非单词边界 \Bcat\B 匹配 “category” 中的 “cat”

(4)分组与捕获

语法 描述 示例
(pattern) 分组,将pattern视为一个整体,可捕获匹配结果 (ab)+ 匹配 “abab”
\n 引用第n个分组的匹配结果(n为1-9) (a)\1 匹配 “aa”
(?:pattern) 非捕获分组(仅分组不捕获结果) (?:ab)+ 匹配 “abab” 但不保存分组
(?<name>pattern) 命名分组(通过名称引用) (?<id>\d+) 匹配数字并命名为”id”

(5)逻辑运算符

语法 描述 示例
` ` 逻辑或,匹配左边或右边的表达式
() 结合逻辑或 `(ab

2. Java正则核心类(Pattern与Matcher)

(1)Pattern类(编译正则表达式)

  • 作用:预编译正则表达式为Pattern对象(线程安全,可复用)。
  • 常用方法:
    • Pattern.compile(String regex):编译正则表达式。
    • Pattern.matches(String regex, CharSequence input):快速匹配(等价于编译后直接调用matches())。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class MatcherDemo {
public static void main(String[] args) {
// 1. 先通过Pattern编译正则表达式(匹配3位数字)
Pattern pattern = Pattern.compile("\\d{3}");

// 2. 传入输入字符串,通过Pattern的matcher()方法获取Matcher对象
String input = "abc123def456ghi78";
Matcher matcher = pattern.matcher(input);

// 此时matcher已绑定正则(\\d{3})和输入字符串(abc123def456ghi78),可执行匹配操作
}
}

(2)Matcher类(执行匹配操作)

  • 作用:通过Pattern对象创建,用于对输入字符串执行匹配操作。
  • 常用方法:
    • matches():整个字符串是否完全匹配正则。
    • find():查找字符串中是否有匹配的子序列(可多次调用,每次找下一个)。
    • group():获取匹配的子序列(结合分组使用)。
    • start()/end():获取匹配子序列的起始/结束索引。
    • replaceAll(String replacement):替换所有匹配的子序列。
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
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class MatcherMethodWithResultDemo {
public static void main(String[] args) {
// 1. 预编译正则表达式:\\d+ 匹配1个及以上的数字(整数)
Pattern pattern = Pattern.compile("\\d+");
// 待匹配的输入字符串
String inputStr = "abc123def456ghi7890";
// 2. 通过Pattern创建Matcher对象(绑定正则和输入字符串)
Matcher matcher = pattern.matcher(inputStr);

// ========== 1. 演示 matches() 方法:全量匹配 ==========
System.out.println("===== matches() 全量匹配 =====");
// 判断整个输入字符串是否完全符合正则(\\d+ 要求全是数字,此处有字母,返回false)
boolean isFullMatch = matcher.matches();
System.out.println("整个字符串是否完全匹配数字:" + isFullMatch);
// 运行结果注释:输出 -> 整个字符串是否完全匹配数字:false

// 注意:调用matches()后,Matcher的匹配状态会改变,如需后续操作,先重置
matcher.reset();

// ========== 2. 演示 find() + group() + start()/end() 方法:查找+提取+获取位置 ==========
System.out.println("\n===== find() + group() + start()/end() 查找与提取 =====");
// while循环迭代:find() 每次调用查找下一个匹配的子串,无匹配时返回false
while (matcher.find()) {
// group():获取当前匹配到的完整子串(等价于group(0))
String matchContent = matcher.group();
// start():获取匹配子串在输入字符串中的起始索引(从0开始,左闭)
int startIndex = matcher.start();
// end():获取匹配子串在输入字符串中的结束索引(左闭右开,即最后一个字符的索引+1)
int endIndex = matcher.end();

// 打印结果
System.out.printf("匹配到数字:%s,起始索引:%d,结束索引:%d,对应区间:[%d, %d)%n",
matchContent, startIndex, endIndex, startIndex, endIndex);
// 循环内运行结果注释(依次输出):
// 匹配到数字:123,起始索引:3,结束索引:6,对应区间:[3, 6)
// 匹配到数字:456,起始索引:9,结束索引:12,对应区间:[9, 12)
// 匹配到数字:7890,起始索引:15,结束索引:19,对应区间:[15, 19)
}

// ========== 3. 演示 group() 分组功能:正则中用()表示分组 ==========
System.out.println("\n===== group() 分组提取 =====");
// 重新编译带分组的正则:(\\d{2})(\\d+) 表示把数字分为2组:前2位 + 剩余位数
Pattern groupPattern = Pattern.compile("(\\d{2})(\\d+)");
Matcher groupMatcher = groupPattern.matcher(inputStr);
// 查找并提取分组内容
while (groupMatcher.find()) {
// 1. 获取当前匹配项的完整子串(group(0) 固定对应完整匹配内容)
String fullMatch = groupMatcher.group(0);
// 匹配123时:fullMatch = "123";匹配456时:fullMatch = "456";匹配7890时:fullMatch = "7890"

// 2. 获取第1个分组匹配的内容(对应第一个括号 (\\d{2}):匹配2位数字)
String group1 = groupMatcher.group(1);
// 匹配123时:group1 = "12"(前2位数字);匹配456时:group1 = "45";匹配7890时:group1 = "78"

// 3. 获取第2个分组匹配的内容(对应第二个括号 (\\d+):匹配1位及以上剩余数字)
String group2 = groupMatcher.group(2);
// 匹配123时:group2 = "3"(剩余1位数字);匹配456时:group2 = "6";匹配7890时:group2 = "90"

// 4. 格式化打印完整内容和分组内容
System.out.printf("完整匹配:%s,分组1(前2位):%s,分组2(剩余):%s%n",
fullMatch, group1, group2);
// 按格式拼接字符串并输出,对应示例中的三行打印结果
// 循环内运行结果注释(依次输出):
// 完整匹配:123,分组1(前2位):12,分组2(剩余):3
// 完整匹配:456,分组1(前2位):45,分组2(剩余):6
// 完整匹配:7890,分组1(前2位):78,分组2(剩余):90
}

// ========== 4. 演示 replaceAll() 和 replaceFirst() 替换方法 ==========
System.out.println("\n===== replaceAll() + replaceFirst() 替换 =====");
// 重置Matcher状态,重新绑定输入字符串
matcher.reset();
// replaceAll():替换所有匹配的子串为指定内容
String replaceAllResult = matcher.replaceAll("【数字】");
System.out.println("替换所有数字后的结果:" + replaceAllResult);
// 运行结果注释:输出 -> 替换所有数字后的结果:abc【数字】def【数字】ghi【数字】

// 再次重置Matcher
matcher.reset();
// replaceFirst():只替换第一个匹配的子串
String replaceFirstResult = matcher.replaceFirst("【首个数字】");
System.out.println("只替换第一个数字后的结果:" + replaceFirstResult);
// 运行结果注释:输出 -> 只替换第一个数字后的结果:abc【首个数字】def456ghi7890
}
}

3. 常用场景示例

(1)数据验证

1
2
3
4
5
6
7
// 验证邮箱(简单规则)
String emailRegex = "^[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)+$";
boolean isEmail = "user@example.com".matches(emailRegex); // true

// 验证手机号(中国大陆)
String phoneRegex = "^1[3-9]\\d{9}$";
boolean isPhone = "13812345678".matches(phoneRegex); // true

(2)文本提取

1
2
3
4
5
6
7
8
9
10
11
// 从HTML中提取所有链接(<a href="xxx">)
String html = "<a href='https://a.com'>链接1</a><a href='https://b.com'>链接2</a>";
Pattern linkPattern = Pattern.compile("href=['\"](.*?)['\"]");
Matcher matcher = linkPattern.matcher(html);

while (matcher.find()) {
System.out.println("链接:" + matcher.group(1)); // 提取分组1的内容
}
// 输出:
// 链接:https://a.com
// 链接:https://b.com

(3)文本替换

1
2
3
4
// 替换字符串中的所有数字为"*"
String text = "密码:123456,验证码:789";
String replaced = text.replaceAll("\\d", "*");
System.out.println(replaced); // 输出:密码:******,验证码:***

四、数组与集合框架

1. 数组底层实现

  • 内存布局
    1
    2
    3
    int[] arr = new int[3];
    // 内存结构:
    // [arr对象头][length=3][0][0][0]
  • 多维数组
    1
    2
    3
    4
    5
    int[][] matrix = new int[2][3];
    // 内存结构:
    // [matrix对象头][length=2][array1][array2]
    // array1: [length=3][0][0][0]
    // array2: [length=3][0][0][0]

2. 集合框架深度解析

(1) List实现类

类型 底层结构 扩容机制 特点 适用场景
ArrayList 动态数组 1.5倍扩容 随机访问快
插入删除慢
频繁查询
较少修改
LinkedList 双向链表 无扩容 插入删除快
随机访问慢
频繁增删
较少查询
  • ArrayList扩容细节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // JDK1.8源码
    private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
  • ArrayList常用方法

    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
    import java.util.ArrayList;
    import java.util.List;

    public class ArrayListSimpleDemo {
    public static void main(String[] args) {
    // 1. 创建ArrayList对象,存储字符串类型元素
    List<String> arrayList = new ArrayList<>();

    // 2. 向集合中添加元素(尾部添加,简单高效)
    arrayList.add("Java");
    arrayList.add("Python");
    arrayList.add("C++");
    arrayList.add("Go");
    System.out.println("初始ArrayList:" + arrayList);
    // 运行结果:初始ArrayList:[Java, Python, C++, Go]

    // 3. 核心优势:随机访问(通过索引快速获取元素,对应表格中“随机访问快”)
    // 索引从0开始,直接获取第2个元素(Python)、第3个元素(C++)
    String element1 = arrayList.get(1);
    String element2 = arrayList.get(2);
    System.out.println("索引1的元素:" + element1 + ",索引2的元素:" + element2);
    // 运行结果:索引1的元素:Python,索引2的元素:C++

    // 4. 修改元素(通过索引快速修改)
    arrayList.set(3, "Rust"); // 将索引3的元素(Go)改为Rust
    System.out.println("修改后ArrayList:" + arrayList);
    // 运行结果:修改后ArrayList:[Java, Python, C++, Rust]

    // 5. 删除元素(尾部删除高效,中间删除效率低,对应表格“插入删除慢”)
    arrayList.remove(2); // 删除索引2的元素(C++)
    System.out.println("删除索引2元素后:" + arrayList);
    // 运行结果:删除索引2元素后:[Java, Python, Rust]

    // 6. 遍历集合(普通for循环,利用索引快速遍历,适配ArrayList特性)
    System.out.println("普通for循环遍历(适配ArrayList随机访问特性):");
    for (int i = 0; i < arrayList.size(); i++) {
    System.out.println("索引" + i + ":" + arrayList.get(i));
    }
    // 运行结果:
    // 索引0:Java
    // 索引1:Python
    // 索引2:Rust
    }
    }
  • LinkedList常用方法

    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
    import java.util.LinkedList;

    public class LinkedListSimpleDemo {
    public static void main(String[] args) {
    // 1. 创建LinkedList对象,存储整数类型元素
    LinkedList<Integer> linkedList = new LinkedList<>();

    // 2. 向集合中添加元素(支持尾部、头部添加,高效)
    linkedList.add(10); // 尾部添加(普通添加方法)
    linkedList.add(20);
    linkedList.addFirst(5); // 特有方法:头部添加(高效,对应“插入删除快”)
    linkedList.addLast(25); // 特有方法:尾部添加(高效)
    System.out.println("初始LinkedList:" + linkedList);
    // 运行结果:初始LinkedList:[5, 10, 20, 25]

    // 3. 核心优势:频繁增删(头部、尾部、指定位置,效率高于ArrayList)
    linkedList.add(2, 15); // 在索引2的位置插入15(高效)
    System.out.println("索引2插入15后:" + linkedList);
    // 运行结果:索引2插入15后:[5, 10, 15, 20, 25]

    linkedList.removeFirst(); // 特有方法:删除头部元素(高效)
    linkedList.removeLast(); // 特有方法:删除尾部元素(高效)
    System.out.println("删除头尾元素后:" + linkedList);
    // 运行结果:删除头尾元素后:[10, 15, 20]

    // 4. 查询元素(通过索引查询,效率低,对应表格“随机访问慢”,不推荐频繁使用)
    Integer element = linkedList.get(1);
    System.out.println("索引1的元素:" + element);
    // 运行结果:索引1的元素:15

    // 5. 遍历集合(推荐增强for循环,避免索引查询,适配LinkedList特性)
    System.out.println("增强for循环遍历(适配LinkedList增删特性):");
    for (Integer num : linkedList) {
    System.out.println(num);
    }
    // 运行结果:
    // 10
    // 15
    // 20
    }
    }

(2) Set实现类

类型 底层结构 排序特性 线程安全 特点
HashSet HashMap 无序 通过hashCode()快速查找
LinkedHashSet LinkedHashMap 插入顺序 保持插入顺序
TreeSet TreeMap 自然排序 通过红黑树实现
  • HashSet:无序、去重(底层 HashMap)

    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
    import java.util.HashSet;
    import java.util.Set;

    public class HashSetDemo {
    public static void main(String[] args) {
    // 1. 创建HashSet对象,存储字符串类型
    Set<String> hashSet = new HashSet<>();

    // 2. 添加元素(包含重复元素,演示去重特性)
    hashSet.add("Java");
    hashSet.add("Python");
    hashSet.add("Java"); // 重复元素,添加失败
    hashSet.add("C++");
    hashSet.add("Go");

    // 3. 打印集合(无序,且无重复元素“Java”)
    System.out.println("HashSet元素(无序+去重):" + hashSet);
    // 运行结果(顺序不固定,示例参考):HashSet元素(无序+去重):[Java, Python, C++, Go]

    // 4. 常用操作:判断元素是否存在、删除元素
    boolean hasPython = hashSet.contains("Python");
    hashSet.remove("C++");
    System.out.println("是否包含Python:" + hasPython);
    System.out.println("删除C++后HashSet:" + hashSet);
    // 运行结果:
    // 是否包含Python:true
    // 删除C++后HashSet:[Java, Python, Go]
    }
    }
  • LinkedHashSet:保持插入顺序、去重(底层 LinkedHashMap)

    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
    import java.util.LinkedHashSet;
    import java.util.Set;

    public class LinkedHashSetDemo {
    public static void main(String[] args) {
    // 1. 创建LinkedHashSet对象
    Set<String> linkedHashSet = new LinkedHashSet<>();

    // 2. 按顺序添加元素(包含重复元素)
    linkedHashSet.add("Java");
    linkedHashSet.add("Python");
    linkedHashSet.add("Java"); // 重复元素,添加失败
    linkedHashSet.add("C++");
    linkedHashSet.add("Go");

    // 3. 打印集合(与插入顺序完全一致,且去重)
    System.out.println("LinkedHashSet元素(插入顺序+去重):" + linkedHashSet);
    // 运行结果(顺序固定):LinkedHashSet元素(插入顺序+去重):[Java, Python, C++, Go]

    // 4. 遍历集合(顺序与插入一致)
    System.out.println("遍历LinkedHashSet(保持插入顺序):");
    for (String str : linkedHashSet) {
    System.out.println(str);
    }
    // 运行结果:Java -> Python -> C++ -> Go(与插入顺序一致)
    }
    }
  • TreeSet:自然排序、去重(底层 TreeMap,红黑树)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import java.util.TreeSet;
    import java.util.Set;

    public class TreeSetDemo {
    public static void main(String[] args) {
    // 示例1:存储整数,自然排序(升序)
    Set<Integer> numTreeSet = new TreeSet<>();
    numTreeSet.add(30);
    numTreeSet.add(10);
    numTreeSet.add(20);
    numTreeSet.add(30); // 重复元素,添加失败
    System.out.println("TreeSet整数(自然升序+去重):" + numTreeSet);
    // 运行结果(固定升序):TreeSet整数(自然升序+去重):[10, 20, 30]

    // 示例2:存储字符串,自然排序(字典序)
    Set<String> strTreeSet = new TreeSet<>();
    strTreeSet.add("Java");
    strTreeSet.add("Python");
    strTreeSet.add("C++");
    strTreeSet.add("Go");
    System.out.println("TreeSet字符串(字典序+去重):" + strTreeSet);
    // 运行结果(固定字典序):TreeSet字符串(字典序+去重):[C++, Go, Java, Python]
    }
    }

(3) Map实现类

类型 底层结构 扩容机制 线程安全 特点
HashMap 数组+链表+红黑树 2倍扩容 最常用Map
Hashtable 数组+链表 2倍扩容 同步,已过时
ConcurrentHashMap 分段锁(JDK7)
CAS+synchronized(JDK8)
2倍扩容 高并发场景
TreeMap 红黑树 按键排序
  • HashMap:最常用、无序、非线程安全(底层数组 + 链表 + 红黑树)

    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
    import java.util.HashMap;
    import java.util.Map;

    public class HashMapDemo {
    public static void main(String[] args) {
    // 1. 创建HashMap对象,键:字符串(姓名),值:整数(年龄)
    Map<String, Integer> hashMap = new HashMap<>();

    // 2. 添加键值对(增)
    hashMap.put("张三", 25);
    hashMap.put("李四", 30);
    hashMap.put("王五", 28);
    hashMap.put("张三", 26); // 重复键,覆盖原有值

    // 3. 获取值(查)
    int zhangSanAge = hashMap.get("张三");
    System.out.println("张三的年龄:" + zhangSanAge);
    // 运行结果:张三的年龄:26(已覆盖原有值25)

    // 4. 打印Map(无序存储)
    System.out.println("HashMap键值对(无序):" + hashMap);
    // 运行结果(顺序不固定):HashMap键值对(无序):{张三=26, 李四=30, 王五=28}

    // 5. 修改值(改)
    hashMap.put("李四", 31);
    // 6. 删除键值对(删)
    hashMap.remove("王五");
    System.out.println("修改+删除后HashMap:" + hashMap);
    // 运行结果:修改+删除后HashMap:{张三=26, 李四=31}

    // 7. 遍历Map
    System.out.println("遍历HashMap:");
    for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
    System.out.println("键:" + entry.getKey() + ",值:" + entry.getValue());
    }
    }
    }
  • Hashtable:线程安全、已过时(底层数组 + 链表)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import java.util.Hashtable;
    import java.util.Map;

    public class HashtableDemo {
    public static void main(String[] args) {
    // 1. 创建Hashtable对象
    Map<String, Integer> hashtable = new Hashtable<>();

    // 2. 添加键值对(用法与HashMap类似)
    hashtable.put("张三", 25);
    hashtable.put("李四", 30);
    hashtable.put("王五", 28);
    hashtable.put("张三", 26); // 重复键,覆盖值

    // 3. 打印Hashtable(无序)
    System.out.println("Hashtable键值对(线程安全+无序):" + hashtable);
    // 运行结果:Hashtable键值对(线程安全+无序):{李四=30, 张三=26, 王五=28}

    // 4. 关键提醒:Hashtable已过时,不推荐使用,性能远低于ConcurrentHashMap
    System.out.println("注意:Hashtable已过时,优先使用ConcurrentHashMap实现线程安全Map");
    }
    }
  • ConcurrentHashMap:高并发、线程安全(JDK8:CAS+synchronized)

    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
      import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;

    public class ConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
    // 1. 创建ConcurrentHashMap对象
    Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

    // 2. 演示多线程写入(体现高并发安全特性)
    Runnable task = () -> {
    for (int i = 0; i < 1000; i++) {
    // 并发写入,不会出现线程安全问题(如数据丢失、异常)
    concurrentHashMap.put(Thread.currentThread().getName() + "-" + i, i);
    }
    };

    // 3. 启动2个线程并发写入
    Thread t1 = new Thread(task, "线程1");
    Thread t2 = new Thread(task, "线程2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();

    // 4. 打印元素个数(应为2000个,无数据丢失)
    System.out.println("ConcurrentHashMap元素个数(并发写入无丢失):" + concurrentHashMap.size());
    // 运行结果:ConcurrentHashMap元素个数(并发写入无丢失):2000

    // 5. 普通增删改查(用法与HashMap一致)
    concurrentHashMap.put("张三", 25);
    System.out.println("张三的年龄:" + concurrentHashMap.get("张三"));
    // 运行结果:张三的年龄:25
    }
    }
    ```

    - **TreeMap:按键自然排序、非线程安全(底层红黑树)**
    ```java
    import java.util.TreeMap;
    import java.util.Map;

    public class TreeMapDemo {
    public static void main(String[] args) {
    // 1. 创建TreeMap对象,键:字符串(姓名),值:整数(年龄)
    Map<String, Integer> treeMap = new TreeMap<>();

    // 2. 添加键值对(键为字符串,按字典序排序)
    treeMap.put("张三", 25);
    treeMap.put("李四", 30);
    treeMap.put("王五", 28);
    treeMap.put("赵六", 35);

    // 3. 打印TreeMap(按键自然排序:字典序)
    System.out.println("TreeMap键值对(按键字典序排序):" + treeMap);
    // 运行结果(固定字典序):TreeMap键值对(按键字典序排序):{李四=30, 王五=28, 张三=25, 赵六=35}

    // 4. 遍历TreeMap(按键升序遍历)
    System.out.println("遍历TreeMap(按键排序):");
    for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
    System.out.println("键:" + entry.getKey() + ",值:" + entry.getValue());
    }

    // 示例2:键为整数,按升序排序
    Map<Integer, String> numTreeMap = new TreeMap<>();
    numTreeMap.put(3, "C++");
    numTreeMap.put(1, "Java");
    numTreeMap.put(2, "Python");
    System.out.println("TreeMap(键为整数,升序排序):" + numTreeMap);
    // 运行结果:TreeMap(键为整数,升序排序):{1=Java, 2=Python, 3=C++}
    }
    }

(5)总结

实现类 核心特性体现 适用场景
HashSet 无序、去重 无需有序存储,仅需去重
LinkedHashSet 插入顺序、去重 需要保留插入顺序且去重
TreeSet 自然排序、去重 需要有序存储且去重
HashMap 高效、无序、非线程安全 普通场景(单线程/无并发)的键值对存储
Hashtable 线程安全、性能差、已过时 兼容旧系统,不推荐新开发使用
ConcurrentHashMap 高并发、线程安全、性能优异 多线程并发写入/读取的场景
TreeMap 按键自然排序、非线程安全 需要按键盘序遍历的键值对存储

五、面向对象深度解析

1. 继承与多态

  • 多态实现原理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Animal { 
    void sound() {
    System.out.println("Animal");
    }
    }
    class Dog extends Animal {
    @Override
    void sound() {
    System.out.println("Woof");
    }
    }

    Animal a = new Dog();
    a.sound(); // 输出"Woof"(动态绑定)
    • JVM通过虚方法表(vtable) 实现
    • 每个类有虚方法表,指向实际方法地址
  • super关键字详解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Parent {
    Parent(String name) { /* ... */ }
    }

    class Child extends Parent {
    Child() {
    super("Child"); // 必须在第一行调用父类构造器
    }
    }
  • Override与super关键字

    • Override:子类重写父类方法,必须与父类方法签名一致(返回值类型、方法名、参数列表)

    • super:调用父类方法或构造器,必须在子类方法第一行(构造器中)

    • 示例代码

      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
      // 父类:定义基础方法
      class Person {
      // 父类的问候方法
      public void sayHello() {
      System.out.println("父类(Person):你好!");
      }
      }

      // 子类:继承父类,并重写方法
      class Student extends Person {
      // 1. 使用 @Override 注解标注此方法
      @Override
      public void sayHello() {
      // 2. 使用 super 关键字调用父类的 sayHello() 方法
      super.sayHello();
      // 子类自己的扩展逻辑
      System.out.println("子类(Student):我是一名学生!");
      }
      }

      // 测试类
      public class OverrideAndSuperDemo {
      public static void main(String[] args) {
      Student student = new Student();
      student.sayHello(); // 调用子类重写后的方法
      }
      }

      实际上是可以删除 @Override 注解的,因为它不是强制要求的。

2. 抽象类

  • 抽象类定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public abstract class Animal {
    // 抽象方法(无实现)
    public abstract void sound();

    // 具体方法(有实现)
    public void eat() {
    System.out.println("Animal is eating");
    }
    }

    public class Dog extends Animal {
    @Override
    public void sound() {
    System.out.println("Woof");
    }
    }
  • 知识点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1.抽象类不能直接实例化(不能用 new AbstractClass() 创建对象),只能作为父类被子类继承。
    补充:可通过“子类实例化+向上转型”的方式使用抽象类(如 AbstractAnimal animal = new Dog())。
    2.成员组成:
    抽象方法:用 abstract 修饰,无方法体(以分号结尾),表示“待子类实现的方法”,抽象类中可以有 0 个或多个抽象方法;不能被 private、final、static 修饰(会限制子类重写)。
    普通方法:有完整方法体,供子类直接继承复用,无需子类重写(子类可按需重写,重写时遵循方法重写通用规则,访问权限不能比父类更严格)。
    成员变量:支持普通成员变量(可修改,属于对象实例)、静态变量(static,属于抽象类本身,可通过类名直接访问),有默认初始化值。
    构造器:存在构造器(用于子类初始化时,调用父类构造器初始化抽象类的成员变量),但不能用于实例化抽象类本身;子类构造器会默认隐式调用抽象类无参构造器(super()),若抽象类只有有参构造器,子类必须显式调用(super(参数))且放在第一行。
    3.继承规则:
    类继承抽象类时,要么自身也声明为抽象类(可遗留抽象方法,未实现的方法向下传递),要么必须实现抽象类的所有抽象方法。
    Java 是单继承机制,一个子类只能继承一个抽象类(该规则适用于所有Java类,一个类仅有一个直接父类)。

3. 接口

  • 接口定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface Animal {
    // 抽象方法(无实现)
    void sound();

    // 默认方法(有实现)
    default void eat() {
    System.out.println("Animal is eating");
    }
    }

    public class Dog implements Animal {
    @Override
    public void sound() {
    System.out.println("Woof");
    }
    }
    //接口的使用通过类实例使用
    Animal myPet = new Dog(); // ✅ 正确!myPet 是 Animal 接口类型,实际对象是 Dog
    myPet.sound(); // 输出: Woof!
  • 知识点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1. 接口是一种引用类型,用于定义行为规范(抽象方法)。
    2. 接口不能直接实例化(不能用 new Interface() 创建对象),只能作为类型被实现或被其他接口继承。
    3. java8之前:
    接口可以包含常量(默认 public static final)和抽象方法(默认 public abstract)。
    4. java8及以后:
    在3的基础上,接口可以包含默认方法(default 修饰,有方法体)和静态方法(static 修饰,有方法体)。
    注意:默认方法和静态方法不能使用 public abstract 修饰符(二者有方法体,与抽象方法特性冲突);接口的抽象方法仍默认携带 public abstract 修饰符,可省略不写。
    5. 类实现接口时,必须实现接口中的所有抽象方法,否则类必须声明为抽象类。
    6. 接口可以继承其他接口,形成接口的层次结构。

4. 抽象类与接口的联系总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.  抽象类可以没有抽象方法(但通常会有普通方法);即使无抽象方法,抽象类依然无法直接实例化。
2. 抽象方法不能被 private/final/static 修饰(冲突特性);核心原因:这三个修饰符会限制子类重写抽象方法(private不可访问、final不可重写、static不属于实例无法重写)。
3. 抽象类的构造器不能私有化(否则无法被继承)。
4. 抽象类可作为方法参数 / 返回值(体现多态);实际传入/返回的是抽象类的子类实例(向上转型)。
5. 抽象类可以继承抽象类:
子抽象类可选择性实现父抽象类的抽象方法(实现部分、实现全部、一个都不实现都可以),未实现的抽象方法会向下传递,由最终的非抽象子类统一实现。
从中也可以反映:抽象类可以有非抽象方法。
6. 抽象类可以实现接口的部分方法:
抽象类可以实现接口的部分方法(即实现接口的抽象方法),甚至可以一个都不实现;未实现的方法可以在抽象类中保持抽象状态,继续向下传递。
7. 普通类必须实现接口的所有抽象方法,否则类必须声明为抽象类。
8. 普通类继承抽象类时,必须实现抽象类的所有抽象方法,否则类必须声明为抽象类;此处的“所有抽象方法”包括抽象类自身定义的、继承父抽象类遗留的、实现接口传递下来的所有未完成抽象方法。
9. 类(普通类,内部类,抽象类)只能单继承,但可以实现多个接口。
10. 接口与抽象类的抽象方法:
接口:
1. 抽象方法默认修饰符是 public abstract,可省略不写(三种写法等价:void method(); / public void method(); / public abstract void method();)。
2. 有且仅有 public abstract 这一种修饰符组合,不允许使用 private、protected 等其他任何修饰符,否则编译报错。
3. 子类重写该抽象方法时,必须使用 public 修饰符(权限不能更严格)。
抽象类:
1. abstract 修饰符必须手动指定(不写则不是抽象方法),无默认的 public abstract/protected abstract 组合。
2. 访问修饰符支持 2 种:public、protected(可手动指定);若不指定任何访问修饰符,默认是「包访问权限(default 权限,无关键字)」,非 protected。
3. 不支持的修饰符:private、final、static(与 abstract 互斥,编译报错)。
4. 子类重写该抽象方法时,访问权限不能比父类更严格(如父类 protected → 子类 protected/public;父类 public → 子类只能 public)。

六、异常处理解析

1. 异常体系结构

  • Checked Exception vs Unchecked Exception
    类型 特点 举例 处理要求
    Checked Exception 编译时检查 IOException, SQLException 必须捕获或声明抛出
    Unchecked Exception 运行时异常 NullPointerException, IllegalArgumentException 可选处理

2. 异常处理最佳实践

  • try-with-resources(JDK7+)

    1
    2
    3
    4
    5
    try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 自动关闭资源
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 异常链式处理

    1
    2
    3
    4
    5
    try {
    // 可能抛出异常的代码
    } catch (IOException e) {
    throw new RuntimeException("处理失败", e); // 保留原始异常
    }
  • 自定义异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class CustomException extends Exception {
    public CustomException(String message) {
    super(message);
    }
    }

    // 使用
    if (invalidData) {
    throw new CustomException("数据无效");
    }

七、内部类解析

1. 四种内部类详解

类型 定义位置 访问外部类成员 实例化方式 特点
成员内部类 类中方法外 ✅ 可直接访问 Outer.Inner in = new Outer().new Inner(); 需外部类实例
静态内部类 类中方法外+static ❌ 只能访问静态成员 Outer.StaticInner si = new Outer.StaticInner(); 独立于外部类
局部内部类 方法内 ✅ 可访问final局部变量 new Inner();(仅在方法内) 作用域仅限方法
匿名内部类 无名,直接实例化 new Interface() { ... } 适用于单次使用
  • 成员内部类示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Outer {
    private int outerField = 10;

    public class Inner {
    public void display() {
    System.out.println(outerField); // 直接访问外部类私有成员
    }
    }
    }

    // 使用
    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
    inner.display();
  • 匿名内部类典型应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 创建线程
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Running");
    }
    }).start();

    // Java 8+ Lambda写法
    new Thread(() -> System.out.println("Running")).start();

八、文件I/O与NIO.2

1. 传统IO流体系

类型 抽象类 具体实现 用途
字节流 InputStream/OutputStream FileInputStream/FileOutputStream 二进制数据
字符流 Reader/Writer FileReader/FileWriter 文本数据
缓冲流 BufferedInputStream/BufferedOutputStream BufferedInputStream/BufferedWriter 提高读写效率
转换流 InputStreamReader/OutputStreamWriter InputStreamReader/OutputStreamWriter 字节→字符转换
  • 文件读写示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 读取文本文件
    try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
    System.out.println(line);
    }
    }

    // 写入文本文件(追加模式)
    try (BufferedWriter bw = new BufferedWriter(new FileWriter("file.txt", true))) {
    bw.write("New line");
    }

2. NIO.2核心特性

  • Path与Files工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Path path = Paths.get("file.txt");

    // 读取所有行
    List<String> lines = Files.readAllLines(path);

    // 写入文件
    Files.write(path, "New content".getBytes(), StandardOpenOption.CREATE);

    // 复制文件
    Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
  • 异步文件操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    channel.read(buffer, 0, null, new CompletionHandler<Integer, Object>() {
    @Override
    public void completed(Integer result, Object attachment) {
    System.out.println("Read " + result + " bytes");
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    exc.printStackTrace();
    }
    });

九、泛型

类型擦除原理

1
2
3
4
5
6
7
8
9
10
11
12
List<String> list = new ArrayList<>();
list.add("Hello");

// 编译后实际为:
List list = new ArrayList();
list.add("Hello"); // 类型信息被擦除


List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
// 因为擦除后,两者都是 ArrayList 类型,JVM 无法区分它们的泛型参数
  • 泛型基础
    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
    // 无标签的盒子:什么都能装
    List box = new ArrayList();
    box.add("苹果"); // ✅ 水果
    box.add(123); // ✅ 编译通过!但这是数字,不是水果!
    box.add(new Car()); // ✅ 编译通过!但这是汽车,不是水果!

    // 取出时需要手动类型转换
    String fruit = (String) box.get(0); // ✅ 正常
    int num = (int) box.get(1); // ❌ 运行时错误!int 不能直接转 Object
    int num = (Integer) box.get(1); // 编译通过,但运行时可能崩溃!所以还是要特定的泛型存储特定的类型
    Car car = (Car) box.get(2); // ✅ 正常


    // 带“水果标签”的盒子:只能装 String 类型
    List<String> fruitBox = new ArrayList<>();
    fruitBox.add("苹果"); // ✅ 符合标签:String 类型
    fruitBox.add(123); // ❌ 编译直接报错!123 不是 String 类型
    fruitBox.add(new Car());// ❌ 编译直接报错!Car 不是 String 类型
    String fruit = fruitBox.get(0); // 取出时无需强制转换


    // 用泛型定义“只能装 Integer 的盒子”
    List<Integer> numberBox = new ArrayList<>();
    numberBox.add(123); // ✅ 编译通过(123 自动装箱为 Integer)
    // numberBox.add("abc"); // ❌ 编译报错!类型不匹配
    int num = numberBox.get(0); // // 取出时直接得到 int(自动拆箱)


绕过类型擦除获取泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TypeReference<T> {
private final Type type;

protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}

public Type getType() {
return type;
}
}

// 使用
TypeReference<List<String>> ref = new TypeReference<>() {};
System.out.println(ref.getType()); // 输出:java.util.List<java.lang.String>

十、Lambda表达式(java 8+)

Lambda 表达式是 Java 8 引入的函数式编程特性,用于简化匿名内部类的写法。它的核心本质是:

将“行为”作为参数传递,让代码更简洁、更易读。

Lambda 表达式通用语法

1
(参数列表) -> { 代码块 }
  • (参数列表):函数的输入参数(可省略类型,由编译器自动推断)
  • ->:箭头操作符(固定写法)
  • { 代码块 }:函数体(单行表达式可省略 {}return

语法简化规则

1️⃣ 完整版(带类型和代码块)

1
2
3
4
// 完整写法:参数类型明确 + 代码块 + return
(int a, int b) -> {
return a + b;
}

2️⃣ 省略参数类型(编译器自动推断)

1
2
3
4
// 省略参数类型:a, b 的类型由上下文推断为 int
(a, b) -> {
return a + b;
}

3️⃣ 单参数省略括号

1
2
// 单参数时可省略括号
x -> x * 2 // 等价于 (x) -> { return x * 2; }

4️⃣ 单行表达式省略 return 和 {}

1
2
// 单行表达式可省略 return 和 {}
x -> x * 2 // 等价于 x -> { return x * 2; }

5️⃣ 无参数时用空括号

1
2
() -> System.out.println("Hello") 
// 等价于 new Runnable() { public void run() { System.out.println("Hello"); } }

经典场景示例

场景 1:函数式接口实现

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个函数式接口(只有一个抽象方法)
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}

// Lambda 实现
MathOperation add = (a, b) -> a + b; // 简化版
MathOperation multiply = (a, b) -> a * b; // 单行表达式

System.out.println(add.operate(2, 3)); // 5
System.out.println(multiply.operate(2, 3)); // 6

场景 2:Stream API 过滤

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
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤偶数:x -> x % 2 == 0
List<Integer> evenNumbers = numbers.stream()
.filter(x -> x % 2 == 0) // 单参数 + 单行表达式
.collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4]

--------------


List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤偶数,平方,求和
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);

// 并行流处理(大数据量加速)
int parallelSum = numbers.parallelStream()
.mapToInt(n -> n * n)
.sum();


场景 3:多参数 + 代码块

1
2
3
4
5
6
7
8
9
// 需要多行逻辑时用代码块
Comparator<Integer> comparator = (a, b) -> {
if (a > b) return 1;
else if (a < b) return -1;
else return 0;
};

// 用法
System.out.println(comparator.compare(3, 5)); // -1

场景 4:无参数(如 Runnable)

1
2
3
4
5
6
7
8
9
10
// 创建线程
new Thread(() -> System.out.println("Running")).start();

// 等价于传统匿名内部类:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running");
}
}).start();

关键注意事项

规则 说明 示例
必须是函数式接口 Lambda 只能用于 只有一个抽象方法的接口(用 @FunctionalInterface 标注) Runnable, Comparator, Predicate
访问外部变量必须是 final 或 effectively final Lambda 内部不能修改外部变量(只能读取) int x = 10;
() -> System.out.println(x); // ✅
x = 20; // ❌ 编译错误
不能声明新变量 Lambda 表达式中不能声明新变量(如 int y = 5; x -> { int y = x; return y; } // ❌ 编译错误
方法引用是 Lambda 的简写 :: 操作符引用已有方法 System.out::println 等价于 x -> System.out.println(x)

Lambda语法总结

1
(参数列表) -> { 表达式或代码块 }
  • 参数列表:可省略类型,单参数省略括号
  • 箭头 ->:固定符号
  • 函数体:单行表达式省略 {}return

十一、Optional类使用

Optional 类深度解析:彻底告别 NPE(空指针异常)

核心理念
Optional 是 Java 8 引入的「容器类」,用于显式表达「值可能存在也可能不存在」,强制开发者处理空值场景,从根源上杜绝 NullPointerException


为什么需要 Optional

传统空值处理的痛点

1
2
3
4
5
6
7
8
9
// 传统方式:到处写 null 检查,代码臃肿且易遗漏
String name = null;
if (user != null) {
if (user.getAddress() != null) {
if (user.getAddress().getCity() != null) {
name = user.getAddress().getCity().getName();
}
}
}
  • 问题
    • 代码冗长、可读性差
    • 容易遗漏检查(导致运行时 NPE)
    • null 语义不明确:无法区分「故意为 null」还是「忘记处理」

Optional 的解决方案

1
2
3
4
5
6
// 使用 Optional 链式调用,语义清晰且安全
String name = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知城市");
  • 优势
    • 强制处理空值:编译器要求你显式处理 null 场景
    • 链式调用:避免多层嵌套 if
    • 语义明确Optional 本身表示「可能为空」,代码自解释

Optional 核心方法

1️⃣ 创建 Optional 实例

方法 说明 示例 适用场景
Optional.of(T value) 包装非 null 值 Optional.of("Hello") 值一定不为 null(否则抛 NullPointerException
Optional.ofNullable(T value) 包装可能为 null 的值 Optional.ofNullable(user) 值可能为 null(安全创建)
Optional.empty() 创建空 Optional Optional.empty() 显式表示「无值」

2️⃣ 安全获取值

方法 说明 示例 风险
T get() 直接获取值 optional.get() ⚠️ 若为空则抛 NoSuchElementException(不推荐!)
T orElse(T other) 有值返回值,无值返回默认值 optional.orElse("default") 安全(推荐)
T orElseGet(Supplier supplier) 有值返回值,无值执行 Supplier 生成默认值 optional.orElseGet(() -> generateDefault()) 延迟计算(默认值创建成本高时用)
T orElseThrow(Supplier exceptionSupplier) 有值返回值,无值抛异常 optional.orElseThrow(() -> new CustomException()) 需显式处理异常

3️⃣ 转换值(避免嵌套)

方法 说明 示例 作用
Optional<U> map(Function mapper) 对值应用函数(返回新 Optional) optional.map(String::toUpperCase) 链式调用(如 user.getAddress().getCity()
Optional<U> flatMap(Function mapper) 对值应用返回 Optional 的函数 optional.flatMap(User::getAddress) 值本身是 Optional 时使用

4️⃣ 条件处理(无值/有值时操作)

方法 说明 示例 作用
void ifPresent(Consumer consumer) 值存在时执行操作 optional.ifPresent(name -> System.out.println(name)) 仅需处理非空场景
void ifPresentOrElse(Consumer, Runnable) 有值执行 Consumer,无值执行 Runnable optional.ifPresentOrElse(System.out::println, () -> System.out.println("空值")) 完整处理空/非空场景
Optional<T> filter(Predicate predicate) 过滤值(不符合条件则变空) optional.filter(name -> name.length() > 3) 验证值是否符合规则

传统 vs Optional

场景 1:嵌套对象取值

1
2
3
4
5
6
7
8
9
10
11
12
// 传统方式(易遗漏 null 检查)
String city = "未知";
if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
city = user.getAddress().getCity().getName();
}

// Optional 方式(编译器强制处理,安全简洁)
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知");

场景 2:从 Map 中安全取值

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, String> config = new HashMap<>();

// 传统方式(需两次 null 检查)
String value = null;
if (config != null && config.get("timeout") != null) {
value = config.get("timeout");
}

// Optional 方式(一行代码解决)
String value = Optional.ofNullable(config)
.map(map -> map.get("timeout"))
.orElse("30s");

场景 3:处理数据库查询结果(可能为空)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传统方式(冗长且易错)
User user = userRepository.findById(100);
if (user != null) {
// 处理 user
} else {
// 处理空
}

// Optional 方式(语义清晰)
Optional<User> userOpt = userRepository.findById(100);
userOpt.ifPresent(user -> {
// 处理 user
});
userOpt.orElseThrow(() -> new UserNotFoundException("ID 100 不存在"));

Optional 使用规则

误区 正确做法 原因
❌ 用 Optional 作为类的成员变量 仅用于方法返回值 Optional 不可序列化,且违背设计初衷(字段应明确表示是否可空)
❌ 使用 optional.get() 永远用 orElse/orElseGet/orElseThrow get() 会抛异常,和直接用 null 无区别
❌ 用 Optional 包装集合 集合返回空集合(Collections.emptyList() 集合的空值语义应由空集合表示,而非 Optional
❌ 用 Optional 替代基础类型判断 用基本类型特化版本(OptionalInt/OptionalDouble 基本类型包装会损失性能(OptionalInt 无装箱开销)

💡 关键原则
Optional 是方法返回值的「语义增强器」,不是 null 的替代品!

  • 方法返回 Optional<T> → 明确告诉调用者:「这个值可能为空」
  • 方法返回 T → 明确告诉调用者:「这个值一定不为空」

使用事例

正确使用场景

1
2
3
4
5
6
7
8
9
10
11
// 方法返回 Optional(明确表示可能为空)
public Optional<User> findUserById(int id) {
User user = userRepository.findById(id);
return Optional.ofNullable(user); // 显式表示可能为空
}

// 调用方安全处理
Optional<User> userOpt = findUserById(100);
userOpt.ifPresent(user -> {
// 处理 user
});

链式调用示例

1
2
3
4
5
6
7
8
9
10
// 从配置中获取用户默认语言(可能为空)
String lang = Optional.ofNullable(config)
.map(c -> c.get("language"))
.filter(s -> !s.isEmpty())
.orElse("zh-CN");

// 从订单中获取最后修改时间(可能为空)
LocalDateTime time = Optional.ofNullable(order)
.map(Order::getModifiedTime)
.orElse(LocalDateTime.now());

避免过度使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误示范:用 Optional 包装非空字段
public class User {
private Optional<String> name; // 不要这样!
}

// 正确做法:字段用 null 表示空,方法返回 Optional
public class User {
private String name; // 允许为 null

public Optional<String> getName() {
return Optional.ofNullable(name);
}
}

总结

传统代码 Optional 代码 本质差异
String name = user.getName(); String name = Optional.ofNullable(user).map(User::getName).orElse("Unknown"); 强制显式处理空值:编译器要求你写 orElse/ifPresent 等,否则无法编译通过
if (user != null) { ... } userOpt.ifPresent(u -> { ... }); 代码结构明确ifPresent 语义清晰,避免嵌套 if

十二、Java 8+ 新特性详解

1. 日期时间API(java.time)

  • 核心类

    用途 示例
    LocalDate 日期(年月日) LocalDate.now()
    LocalTime 时间(时分秒) LocalTime.of(10, 30)
    LocalDateTime 日期+时间 LocalDateTime.now()
    ZonedDateTime 带时区的日期时间 ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
    Duration 时间段(秒/纳秒) Duration.between(start, end)
    Period 日期段(年月日) Period.between(date1, date2)
  • 日期时间格式化

    1
    2
    3
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime now = LocalDateTime.now();
    String formatted = now.format(formatter);

2. 并发工具类

  • CompletableFuture

    1
    2
    3
    4
    CompletableFuture.supplyAsync(() -> {
    return "Result";
    }).thenApply(result -> result.toUpperCase())
    .thenAccept(System.out::println);
  • StampedLock(读写锁优化):

    1
    2
    3
    4
    5
    6
    7
    StampedLock lock = new StampedLock();
    long stamp = lock.readLock();
    try {
    // 读操作
    } finally {
    lock.unlockRead(stamp);
    }

十三、JVM性能调优关键点

1. 内存参数配置

参数 说明 示例
-Xms 初始堆大小 -Xms512m
-Xmx 最大堆大小 -Xmx2g
-XX:NewRatio 新生代/老年代比例 -XX:NewRatio=2
-XX:MaxMetaspaceSize 元空间最大大小 -XX:MaxMetaspaceSize=256m
-XX:+UseG1GC 启用G1垃圾回收器 -XX:+UseG1GC

2. 常见内存问题诊断

  • OutOfMemoryError

    • 堆内存溢出java.lang.OutOfMemoryError: Java heap space
      • 解决:增大-Xmx,检查内存泄漏
    • 元空间溢出java.lang.OutOfMemoryError: Metaspace
      • 解决:增大-XX:MaxMetaspaceSize
    • 栈溢出java.lang.StackOverflowError
      • 解决:增大-Xss
  • 内存泄漏诊断工具

    • jmap -heap <pid>:查看堆内存使用
    • jstat -gc <pid> 1000:实时监控GC情况
    • jvisualvm:图形化分析内存快照

十四、设计模式的应用

1. 单例模式(双重检查锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

2. 工厂模式(简单工厂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Shape {
void draw();
}

public class Circle implements Shape {
public void draw() { System.out.println("Circle"); }
}

public class ShapeFactory {
public Shape getShape(String type) {
if ("circle".equals(type)) return new Circle();
if ("rectangle".equals(type)) return new Rectangle();
return null;
}
}

3. 观察者模式(Java内置实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Observable;
import java.util.Observer;

class Subject extends Observable {
private int state;

public void setState(int state) {
this.state = state;
setChanged(); // 标记已更改
notifyObservers(state); // 通知所有观察者
}
}

class ObserverImpl implements Observer {
public void update(Observable o, Object arg) {
System.out.println("State changed to: " + arg);
}
}

十五、反射机制深度解析

1. 反射核心API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 或
Class<?> clazz = MyClass.class;

// 创建实例
Object obj = clazz.getDeclaredConstructor().newInstance();

// 调用方法
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(obj, "param");

// 访问私有字段
Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true);
field.set(obj, "value");

2. 反射应用场景

  • 框架开发:Spring IOC依赖注入
  • 动态代理:JDK动态代理
  • 序列化/反序列化:JSON库(如Jackson)
  • 单元测试:访问私有成员

3. 反射性能优化

1
2
3
4
5
6
7
8
9
10
// 1. 缓存Method对象
private static final Method METHOD = MyClass.class.getMethod("method");

// 2. 使用MethodHandle(JDK7+)
MethodHandle handle = MethodHandles.lookup().findVirtual(MyClass.class, "method", MethodType.methodType(void.class));
handle.invokeExact(obj);

// 3. 使用VarHandle(JDK9+)进行字段访问
VarHandle handle = MethodHandles.lookup().findVarHandle(MyClass.class, "field", int.class);
handle.set(obj, 10);

十六、JVM内存模型(JMM)

1. 主内存与工作内存

2. 关键内存屏障

指令 作用 使用场景
LoadLoad 确保加载指令顺序 读取共享变量前
StoreStore 确保存储指令顺序 写入共享变量后
LoadStore 确保加载在存储前完成 读写混合操作
StoreLoad 最强屏障,防止重排序 volatile写后读

3. volatile关键字原理

  • 保证可见性:写入时刷新到主内存,读取时从主内存加载
  • 禁止指令重排序:通过内存屏障实现
  • 不保证原子性:如i++操作(包含读-改-写三步)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 正确使用volatile
private volatile boolean initialized = false;

// 线程1
void init() {
// 初始化操作
initialized = true; // 写入volatile变量
}

// 线程2
void use() {
if (initialized) { // 读取volatile变量
// 可以安全使用初始化数据
}
}

十七、Java并发编程核心

1. synchronized底层原理

  • 对象头结构
    1
    [Mark Word][Class Pointer][Lock Record]
  • 锁升级过程
    1
    无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 锁消除(JIT优化):
    1
    2
    3
    4
    5
    6
    // 以下代码会被优化为无锁
    public void method() {
    synchronized(new Object()) {
    // 无共享数据
    }
    }

2. Lock接口与AQS

  • ReentrantLock实现
    1
    2
    3
    4
    5
    6
    7
    Lock lock = new ReentrantLock();
    lock.lock();
    try {
    // 临界区
    } finally {
    lock.unlock();
    }
  • AQS(AbstractQueuedSynchronizer)核心
    • 维护一个CLH队列(FIFO)
    • 通过CAS操作修改state字段
    • 支持公平锁/非公平锁

3. 并发工具类详解

用途 示例
CountDownLatch 计数器,等待多个线程完成 new CountDownLatch(3)
CyclicBarrier 循环屏障,等待固定数量线程 new CyclicBarrier(3)
Semaphore 信号量,控制并发数 new Semaphore(5)
Exchanger 线程间数据交换 exchanger.exchange(data)
Phaser 动态注册的屏障 phaser.register()

一、部署前环境准备

1. 核心依赖:Docker + Docker Compose(插件版)

Kali 2025 默认预装 Docker,但需确认 Docker Compose 插件版 可用(避免独立包冲突):

1
2
3
4
# 验证 Docker 是否运行
sudo systemctl status docker
# 验证 Docker Compose 插件版(无横杠,官方推荐)
docker compose version

问题1:Docker Compose 安装冲突(独立包 vs 插件版)

  • 现象:执行 sudo apt install docker-compose 时提示“trying to overwrite…”
  • 解决:使用系统预装的插件版,无需安装独立包;若已安装独立包,先卸载冲突组件:
    1
    2
    3
    4
    # 卸载冲突的独立包
    sudo apt remove -y docker-compose
    # 确保插件版可用
    sudo apt install -y docker-compose-plugin

二、配置 Docker 国内加速器(解决镜像拉取慢/中断)

Docker 默认拉取国外镜像源,必须配置国内加速器(你遇到过格式错误、源不稳定问题)。

1. 编辑 Docker 配置文件

1
2
# 打开配置文件(若不存在则自动创建)
sudo vim /etc/docker/daemon.json

2. 写入正确格式的加速源(避免逗号缺失)

正确配置(含国内稳定源)

1
2
3
4
5
6
7
8
9
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev",
"https://docker.xuanyuan.me",
"https://registry.cn-hangzhou.aliyuncs.com" // 阿里云公共源(最稳定)
]
}

3. 使加速配置生效

1
2
3
4
5
6
# 重新加载配置
sudo systemctl daemon-reload
# 重启 Docker 服务
sudo systemctl restart docker
# 验证加速源是否生效
docker info | grep "Registry Mirrors" -A 5
  • 生效标志:输出中显示配置的所有加速源。

三、克隆 ARL 代码

使用加速链接克隆 ARL 代码(你之前用的 gh.llkk.cc 加速):

1
2
3
4
5
6
# 进入工具目录(自定义路径,示例为 ~/Web/web-tools)
cd ~/Web/web-tools
# 克隆代码(加速链接)
git clone git@github.com:ki9mu/ARL-plus-docker ARL
# 进入 ARL 目录(后续所有命令需在此执行)
cd ARL

四、拉取 ARL 依赖镜像(解决拉取中断问题)

ARL 依赖 3 个核心镜像:mongo:4.0.27ki9mu/shadow-rabbitmq:latestki9mu/arl-ki9mu:v3.0.1

1. 优先创建数据卷(持久化数据库数据)

1
docker volume create arl_db

2. 拉取镜像(避免批量拉取中断,可逐个拉取)

方法1:批量拉取(加速源稳定时用)

1
docker compose pull

方法2:逐个拉取(解决批量拉取反复中断)

1
2
3
4
5
6
# 拉取 MongoDB 镜像
docker pull mongo:4.0.27
# 拉取 RabbitMQ 镜像
docker pull ki9mu/shadow-rabbitmq:latest
# 拉取 ARL 核心镜像
docker pull ki9mu/arl-ki9mu:v3.0.1

问题3:镜像拉取反复中断

  • 现象:拉取过程中自动退出,进度无法完成
  • 解决:
    1. 补充阿里云加速源(见第二步);
    2. 重启 Docker 后逐个拉取镜像
    3. 若仍中断,切换网络(如手机热点)后重试。

3. 验证镜像是否拉取完成

1
docker images | grep -E "mongo:4.0.27|ki9mu/shadow-rabbitmq|ki9mu/arl-ki9mu"
  • 成功标志:输出包含 3 个镜像的信息。

五、ARL 核心操作:启动/关闭/重启

所有命令需在 ARL 目录(~/Web/web-tools/ARL) 执行,确保能读取 docker-compose.yml 文件。

1. 启动 ARL(后台运行)

1
docker compose up -d
  • 作用:启动所有依赖容器(MongoDB、RabbitMQ、ARL 核心服务),后台运行不占用终端。
  • 验证启动成功:
    1
    docker compose ps
    成功标志:所有容器 STATEUp

2. 关闭 ARL(停止所有服务)

1
docker compose down
  • 作用:停止并销毁所有容器(数据不会丢失,因为数据存在 arl_db 数据卷中)。
  • 场景:长时间不使用时关闭,节省系统资源。

3. 重启 ARL(修改配置后用)

1
docker compose restart
  • 作用:重启所有容器,适用于修改 ARL 配置后生效。

4. 查看 ARL 运行状态

1
docker compose ps
  • 输出说明:
    • NAME:容器名称(如 arl_web 是 Web 界面容器);
    • STATEUp 表示运行中,Exited 表示已停止;
    • PORTS:端口映射(如 0.0.0.0:5003->443/tcp 表示 5003 端口映射到 HTTPS 服务)。

5. 查看 ARL 日志

1
2
3
4
5
6
# 查看所有容器日志(实时输出)
docker compose logs -f
# 查看单个容器日志(如 Web 容器,排查访问问题)
docker compose logs -f web
# 查看 MongoDB 容器日志(排查数据库问题)
docker compose logs -f mongodb
  • 场景:容器启动失败(STATEExited)时,通过日志定位报错原因。

六、访问 ARL 系统

1. 正确访问地址

从容器端口映射可知:arl_web 将主机 5003 端口映射到容器 443 端口(HTTPS),因此必须用 https 开头的地址:

1
https://127.0.0.1:5003

问题4:访问地址错误

  • 现象:输入 http://127.0.0.1:5003 无法访问
  • 解决:改用 https 开头的地址(因容器映射的是 HTTPS 端口)。

2. 处理浏览器“不安全提示”

ARL 使用自签 SSL 证书,浏览器会提示“连接不安全”:

  • 操作:选择“高级”→“继续访问 127.0.0.1(不安全)”(仅本地访问,无风险)。

3. 浏览器Cookie问题

  • 现象:重启ARL容器后,浏览器访问时出现”Network Error”,必须删除Cookie才能重新登录
  • 原因:容器重建后新会话与旧Cookie不匹配,且自签名证书变更导致浏览器Cookie绑定失效
  • 解决方案
    • 强制刷新:按 Ctrl+Shift+R(跳过本地Cookie缓存)
    • 使用隐私窗口:按 Ctrl+Shift+P 打开隐私窗口访问
    • 自动清除Cookie:设置浏览器退出时自动删除Cookie(Firefox:about:preferences#privacy → Cookie和网站数据 → 退出Firefox时删除Cookie和网站数据)

推荐配置
在Kali系统中,建议将Firefox设置为”退出时删除Cookie”,这样每次重启浏览器后都会自动清除旧Cookie,完全避免手动操作。操作路径:
菜单 → 设置 → 隐私与安全 → Cookie和网站数据 → 退出Firefox时删除Cookie和网站数据

4. 默认登录 ARL

使用默认账号密码:

  • 账号:admin
  • 密码:arlpass

七、ARL 日常使用教程

ARL 是资产侦察系统,核心用途是 发现目标资产、识别指纹、扫描漏洞,以下是新手必备的基础操作:

1. 核心功能界面介绍

登录后首页分为 5 个核心模块:

  • 「任务管理」:创建/启动/停止扫描任务;
  • 「资产搜索」:查询已发现的资产(域名、IP、端口、服务等);
  • 「指纹管理」:查看已识别的应用指纹(如 Nginx、Tomcat、MySQL 等);
  • 「PoC 管理」:查看可利用的漏洞 PoC(无需手动上传,默认内置常用 PoC);
  • 「系统设置」:配置扫描线程、超时时间、邮件通知等。

2. 创建第一个资产侦察任务

步骤1:进入「任务管理」→ 点击「新建任务」

步骤2:填写任务信息(重点配置)

配置项 填写说明
任务名称 自定义(如“测试目标资产侦察”)
目标 输入要扫描的目标(支持多个,用换行/逗号分隔):
- 单个 IP:192.168.1.100
- 网段:192.168.1.0/24
- 域名:example.com
- 子域名爆破:*.example.com(自动爆破子域名)
端口扫描 选择扫描类型:
- 常用端口:适合快速扫描;
- 全端口:扫描所有 65535 端口(耗时久);
- 自定义端口:输入需要扫描的端口(如 80,443,3389,22
扫描线程 新手建议默认(50 线程),线程越多扫描越快,但容易触发目标防护规则
其他选项 勾选“子域名爆破”“端口扫描”“指纹识别”“PoC 扫描”(按需选择)

步骤3:启动任务

点击「确认创建」→ 任务列表中找到刚创建的任务 → 点击「启动」。

步骤4:查看扫描结果

  • 任务状态变为「运行中」,等待扫描完成(时间取决于目标数量和端口范围,10 分钟~数小时不等);
  • 扫描完成后,点击任务名称 → 查看详细结果:
    • 「资产列表」:已发现的 IP、域名、端口、服务(如 80/tcp http Nginx 1.21.0);
    • 「指纹识别」:目标使用的应用程序、框架(如 Tomcat、WordPress、MySQL);
    • 「PoC 结果」:已识别的漏洞(如弱密码、未授权访问、CVE 漏洞);
    • 「导出报告」:支持导出 HTML/PDF 格式报告(用于整理扫描结果)。

3. 常用操作:资产搜索与筛选

场景:快速查找特定资产

  1. 进入「资产搜索」模块;
  2. 输入筛选条件:
    • 按端口筛选:port:80(查找所有 80 端口开放的资产);
    • 按服务筛选:service:http(查找所有 HTTP 服务);
    • 按指纹筛选:fingerprint:Nginx(查找所有 Nginx 服务器);
    • 按漏洞筛选:vuln:弱密码(查找存在弱密码漏洞的资产);
  3. 点击「搜索」即可查看匹配结果。

4. 安全注意事项

  • 仅扫描 自己拥有权限的目标(如本地测试环境、授权测试目标),禁止扫描未授权的公网资产或他人服务器(违法违规);
  • 扫描公网目标时,降低扫描线程(如 20 线程),避免触发目标的 DDoS 防护规则;
  • 定期备份 ARL 数据(数据存储在 arl_db 数据卷中,可通过 Docker 备份数据卷)。

八、常见问题汇总

问题现象 解决方案
Docker Compose 安装提示”overwrite”冲突 卸载独立包 docker-compose ,使用系统预装的插件版 docker compose (无横杠)
Docker 加速配置不生效 检查 daemon.json 格式(逗号是否正确),重启 Docker 后用 docker info 验证
镜像拉取反复中断 补充阿里云加速源,重启 Docker 后逐个拉取镜像
访问 http://127.0.0.1:5003 无法打开 改用 https://127.0.0.1:5003 (容器映射的是 HTTPS 端口)
容器启动后状态为 Exited 查看日志:docker compose logs 容器名(如 docker compose logs web),重新拉取对应镜像后启动
扫描任务无法启动 检查 RabbitMQ 容器是否正常运行(arl_rabbitmq 状态为 Up),重启 ARL 后重试
扫描结果为空 目标可能未开放端口,或扫描规则过严,尝试选择”全端口扫描”+”子域名爆破”后重新扫描
每次重启ARL后需删除浏览器Cookie才能访问 原因:容器重建后新会话与旧Cookie不匹配,且自签名证书变更导致浏览器Cookie绑定失效

解决方案
- 强制刷新:按 Ctrl+Shift+R(忽略本地Cookie缓存)
- 隐私窗口:按 Ctrl+Shift+P 打开隐私窗口访问
- 自动清除:设置浏览器退出时删除Cookie(Firefox:about:preferences#privacy → Cookie和网站数据 → 退出Firefox时删除Cookie和网站数据)

💡 补充说明
此问题本质是浏览器对HTTPS证书的Cookie绑定机制导致。当您使用 docker compose down 停止服务后,ARL容器会销毁所有会话数据并重新生成自签名证书。浏览器保留的旧Cookie与新证书不匹配,导致认证失败。上述三种解决方案中,设置浏览器退出时自动清除Cookie是最彻底的解决方式,无需每次手动操作。

九、后续优化

  1. 修改默认密码:登录后进入「个人中心」→「密码修改」,输入旧密码 arlpass 和新密码;
  2. 备份 ARL 数据
    1
    2
    # 备份数据卷 arl_db 到本地目录(如 ~/arl_backup)
    docker run --rm -v arl_db:/source -v ~/arl_backup:/dest alpine cp -r /source/* /dest/
  3. 更新 ARL 镜像
    1
    2
    3
    4
    # 拉取最新镜像
    docker compose pull
    # 重启容器
    docker compose up -d

一、环境搭建

  1. 资源下载

    eve-ng 懒人版镜像文件在eve-ng懒人社区下载,记住要下载其客户端窗口
    vm或其他虚拟机软件部署

  2. 网络配置


二、网络设备以及配置知识

网络设备知识

1
2
3
4
5
华为:
1.路由器:AR系列(save)
2.交换机:CE系列(commit)
3.防火墙:

基础实验一

0. 任务导向

知识要点:

  1. 路由器的ip配置
  2. 路由配置

1.设备静态ip配置

思科设备:

补充ip接口查看:

注意:

  1. 255.255.255.0 可以使用 24 代替
  2. show ip interface GigabitEthernet0/0 查看结果包括子网掩码

华山设备:


华为设备:

删除ip接口信息:

  1. 进入要删除的ip接口-> undo ip address -> 这个接口的所有ip信息被删除
  2. 进入要删除的ip接口-> undo ip address [ip] [mask] -> 接口的指定ip信息被删除

2.静态路由配置

目的:实现vIOS->HUSG600V

正向路由:

对vIOS

对vIOS2 的静态路由配置一样,只是传递的路由ip不同

值得注意的是H3CvSR是不需要配置路由的,因为其与目标设备是直连的。

回程路由:

对H3CvSR:

对于HUSG600V


理论上此时vIOS 可以 ping HUSG600V , 都是忽略了防火墙是🈲ping的。

解决:需要配置防火墙的信任域

1
2
3
4
 华为防火墙默认策略:
同区域流量默认允许(如 trust → trust)
跨区域流量默认拒绝(如 untrust → trust)
所有流量默认拒绝(除非明确配置策略)

g1/0/0设置在信任任区:

1
2
3
4
5
HUSG600V指令:
进入防火墙信任域:
firewall zone trust # 查看信任域的详细配置(含绑定接口、优先级)
display zone # 查看所有安全区域的简要信息(对比其他区域)
查看信任域: display zone trust

指令总结:

1
2
删除指令 : undo 原有指令
实用查看指令:display this

基础实验二

0.任务导向

账号:AR1000vN(N:数字)
密码 qwer1234KK. 或 qwer1234KK 或 12345678KK
华为回退:ctrl+h

知识要点:

  1. NAT映射
  2. 路由直连

问题:

  1. vpc1怎么将数据包给AR1000v4?
  2. AR1000v4怎么将数据包给vpc2或vpc3?
  3. vpc2或vpc3怎么回复vpc1?

1.普通路由

对链路vpc1<—>vpc2:

(1)正向:

vpc的静态ip配置格式:ip <IP地址> <子网掩码> [<默认网关>]

这里的0.0.0.0并不代表有效的网关,实际效果等同于 “未配置可用网关”。此时 VPC 只能与同一子网内的设备通信(如192.168.1.x网段),无法访问其他子网

设置网关:

vpc1将包发给AR1000v4,需设置ARC1000v4到vpc2,vpc3的路由
静态路由格式:ip route-static <目的网段> <前缀长度> <下一跳IP地址>

对AR1000v:

对AR1000v6:

发现AR1000v6是有192.168.2.2的路由的(因为是直连)

(2)反向:

对AR1000v6:

同理:AR1000v4不需要配置vpc1的静态路由

vpc1:成功ping通

抓取vpc1-eth0:

抓取AR1000v4-g0/0/0:

抓取AR1000v4-g0/0/1:


2.NAT映射

对链路vpc1<—>vpc2 :

假设:有AR1000v4的权限,没有AR1000v5的权限,即无法配置其静态路由。由直连路由知识可知,AR1000v4与AR1000v5 AR1000V5与vpc2 由静态路由 。
所以,只要将vpc1的静态ip映射为AR1000v4与AR1000v5连接的接口的静态ip(10.2.2.1),就可以实现数据传输

只需操作AR1000v4:

  1. 什么样的数据包需要做网络地址转换?(192.168.1.1/24)

配置AR1000v4到vpc2的路由:

[Huawei]ip route-static 192.168.3.0 24 10.2.2.2

此时,不需要对AR1000v5配置vpc1的路由,这是最重要的区别

实现NAT映射:

抓取vpc1-eth0:

抓取AR1000v4-g0/0/0:

抓取AR1000v4-g0/0/1:


基础实验三

0.任务导向

知识要点:

  1. 交换机配置(多vlan局域网配置,安全策略,跨网段路由配置)
  2. vlan间的可控互通(vlanif:vlan网关)
  3. acl:访问控制限制
  4. NAT的映射形式与端口映射

账号:AR1000vN(N:数字)
密码:qwer1234KK. 或 qwer1234KK 或 12345678KK
华为回退:ctrl+h


1.配置vlan

1
2
3
4
5
6
7
vlan:虚拟局域网,隔离广播,隔离故障
vlan接口类型:
1.Access 接口主要用于连接终端设备,如计算机、打印机等。它属于某个特定的 VLAN,并且只能属于一个 VLAN
2.Trunk 接口主要用于交换机之间、交换机与路由器或三层交换机之间的互联链路。它可以允许多个 VLAN 的数据通过,从而实现不同交换机上相同 VLAN 之间的通信
3.Hybrid 接口是一种比较灵活的接口类型,既可以连接终端设备,也可以用于交换机之间的互联 。它可以允许多个 VLAN 的数据通过,并且可以灵活地设置哪些 VLAN 的数据在转发时携带标签,哪些不携带标签


配置vlan:

查看vlan方式1:

查看vlan方式2:

特别注意:在用户视图中使用save保存配置


2.配置vlan接口

对三台vpc配置静态ip与网关后测试联通:

但是,配置CE6800-CE1的外接口的静态ip时出现问题:

原因:交换机的接口默认是交换模式,不可以直接配置静态ip,需要创建vlan,在vlan上配置静态ip

这里的 port link-type trunk 有个坑,下面会讲

小知识: 删除vlan前必须删除其关联的接口


3.配置路由

  1. CE6800-CE1路由

目标:让不同 VLAN 和外网互通

由于CE6800-CE1是三级交换机,整个大的局域网的数据包经过它在转发给路由器,整个局域网中的网络设备访问的公网地址很多,不可能都一一配置路由


  1. AR1000v5路由

目标:让不同 VLAN 和外网互通

  • 配置默认路由

正向: 指向AR1000v6 (三大运营商公司)

回程: 指向交换机CE6800-CE1

  • NAT映射

误区: AR1000v4使用NAT映射不代表AR1000v4就不需要设置回程路由


4.启动Proxy ARP

  1. 什么是ARP?

    ARP(Address Resolution Protocol,地址解析协议)的作用:将IP地址解析为MAC地址

1
2
3
4
5
6
7
8
9
10
11
主机A (IP: 192.168.1.10) 想要与 主机B (IP: 192.168.1.20) 通信

1. 主机A查看自己的ARP缓存表
2. 如果没有主机B的MAC地址,主机A广播发送ARP请求:
"谁是192.168.1.20?请告诉你的MAC地址"

3. 局域网内所有主机都收到这个广播
4. 主机B识别到自己的IP,单播回复:
"我是192.168.1.20,我的MAC是xx:xx:xx:xx:xx:xx"

5. 主机A将这条映射记录存入ARP缓存表

  1. 什么是Proxy ARP

    Proxy ARP(代理ARP) 是一种网络技术,允许一台设备(通常是路由器或防火墙)代表另一台设备响应ARP请求。
    它不是“标准ARP”,而是在特殊场景下“代答ARP”的机制。

1
2
3
4
设备A(192.168.10.2)想ping 192.168.10.3					设备A直接在本地广播ARP请求,设备B(192.168.10.3)回应自己的MAC地址		❌ 不需要Proxy ARP
设备A(192.168.10.2)想ping 192.168.20.3(不同网段) 设备A将包发给默认网关(CE6800-CE1),CE6800-CE1再转发 ❌ 不需要Proxy ARP
设备A(192.168.10.2)想ping 10.1.1.1(AR1000v5的接口IP) CE6800-CE1需要知道AR1000v5的MAC地址,但AR1000v5默认不响应ARP请求 ✅需要Proxy ARP!


  1. 启动华为设备ARP代理(AR1000v5)

ARP 表中没有 10.1.1.2 ✅正常现象 这正是VPC8 无法 ping 通 10.1.1.1 的根本原因!

启动:

没有变化,难道是没有生效?
Proxy ARP 是实时响应 ARP 请求,不会在 ARP 表中添加新条目

此时vpc8还是无法ping通10.1.1.1,这是为什么?

根本原因:AR1000v5 和 CE6800-CE1 之间的接口 VLAN 配置不一致,导致二层通信失败。

设备 接口 当前 VLAN 配置 问题
CE6800-CE1 GigabitEthernet1/0/3 port link-type trunk + port trunk allow-pass vlan 40 发送带 VLAN 40 Tag 的帧
AR1000v5 GigabitEthernet0/0/0 port link-type access + port default vlan 1(默认) 丢弃带 VLAN 40 Tag 的帧

重新设置CE6800-CE1的g1/0/3接口类型

成功ping通(别忘记,此时的AR1000v5的g0/0/0接口启动ARP代理)


  1. AR1000v6路由
  • 处理回包
  • 联通linux服务1

在真实网络中,你不能把 8.8.8.8 当下一跳!
但在 EVE-NG 模拟环境 中,你可以这样配置,因为系统会自动处理回包。
✅ 这是模拟互联网的“作弊技巧” —— 没有它,ping 8.8.8.8 无法通!


5.linux网络配置

  1. 网卡信息分析
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
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever

解释:
1: -> 接口序号(内核分配)
lo:-> 接口名称
<LOOPBACK,UP,LOWER_UP> -> 接口标志
LOOPBACK:回环接口
up:接口已启用
LOWER_UP:物理层正常(虽然回环没有物理层,但系统认为它“UP”)
mtu 65536 -> 最大传输单元(MTU),回环接口默认很大
qdisc noqueue -> 队列调度器类型(回环接口不需要排队)
state UNKNOWN → 接口状态(回环接口通常显示为 UNKNOWN)
group default → 接口组(默认组)
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
link/loopback → 链路类型(回环)
00:00:00:00:00:00 → MAC 地址(回环接口没有真实 MAC,所以是全 0)
brd 00:00:00:00:00:00 → 广播地址(回环没有广播)
inet 127.0.0.1/8 scope host lo
inet → IPv4 地址
127.0.0.1/8 → IP 地址 + 子网掩码(/8 表示 255.0.0.0)
scope host → 地址范围(只在本机有效)
lo → 所属接口名
valid_lft forever preferred_lft forever
valid_lft → 地址有效期(forever = 永久)
preferred_lft → 首选有效期(forever = 永久)
inet6 ::1/128 scope host
inet6 → IPv6 地址
::1/128 → IPv6 回环地址
scope host → 范围(本机)
valid_lft forever preferred_lft forever (同上)



2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:50:00:00:0b:00 brd ff:ff:ff:ff:ff:ff
inet6 fe80::250:ff:fe00:b00/64 scope link
valid_lft forever preferred_lft forever

解释:
ens3: → 接口名称(这是你的实际网卡!)
<BROADCAST,MULTICAST,UP,LOWER_UP>
BROADCAST = 支持广播
MULTICAST = 支持组播
qdisc pfifo_fast → 队列调度器(先进先出)

等等......

重要:ens3网卡已经启用,但是没有inet ,即没有ipv4地址

永久配置:

nano /etc/network/interfaces (vim等)
重启网卡服务:systemctl restart systemd-networkd

ctrl+o 保存 -> 回车确认 -> ctrl+x 退出


由于实验中的linux服务器是Slax轻量版,不会存储文件,为了方便实验的进行,使用vpc代替,如图:

测试NAT普通映射情况

  1. vpc8<–>vpc9
  1. vpc8<–>AR1000v5-g0/0/0

6.服务器的端口映射

  1. 一一对应的NAT映射

nat server global 64.1.1.2 inside 192.168.30.3 (华为设备的指令)

nat server:这是命令关键字,用于启动配置 NAT Server 功能,用来建立公网地址和私网地址之间的映射关系,使外部网络能访问内部网络的特定服务器

global:是一个关键字,表明后续跟的是公网侧的相关信息。

inside:也是一个关键字,用于标识后面跟随的是内网侧的相关信息。

剩下的为公网ip 与 私网ip


  1. 端口映射

nat server protocol { tcp | udp } global { 公网IP 公网端口 } inside { 内网IP 内网端口 }
nat server protocol { tcp | udp } global { current 公网端口 } inside { 内网IP 内网端口 }

对AR1000v5:

nat server protocol tcp global current 8080 inside 192.168.30.3 80

比如公司购买了一个公网ip,且公司有很多服务器设备,不可能每个服务器设备都购买一个公网ip,这样成本很高。接口绑定一个公网ip,而一个接口有多个端口,通过端口映射,实现一个公网ip就可以使外网设备访问内网的对应主机

8.8.8.8外网主机测试连接:

抓包验证:

  1. 对AR1000v5-g0/0/1接口抓包
  1. 对192.168.30.3抓包

流量是从NAT server转发,验证成功


基础实验四

0.windows网络配置

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
1.查看所有网络适配器详细信息:ipconfig /all
2.查看路由表:route print
3.查看网络连接状态:netstat -ano # 显示所有 TCP/UDP 连接及进程 ID

4.静态 IP 配置:
# 1. 查看网卡名称(需替换为实际名称,如“以太网”“WLAN”)
netsh interface show interface

# 2. 设置静态 IP + 子网掩码 + 网关
netsh interface ipv4 set address name="以太网" source=static addr=192.168.1.100 mask=255.255.255.0 gateway=192.168.1.1 gwmetric=1

# 3. 切换为 DHCP 自动获取 IP
netsh interface ipv4 set address name="以太网" source=dhcp

5.多 IP 地址绑定(同一网卡添加额外 IP):
netsh interface ipv4 add address name="以太网" addr=10.0.0.2 mask=255.255.255.0

6.静态 DNS 服务器设置:
# 首选 DNS
netsh interface ipv4 set dnsservers "以太网" static 114.114.114.114 primary

# 备用 DNS
netsh interface ipv4 add dnsservers "以太网" 8.8.8.8 index=2

7.切换为 DHCP 自动获取 DNS:
netsh interface ipv4 set dnsservers "以太网" source=dhcp

8.刷新 DNS 缓存:ipconfig /flushdns
9.查看本地 DNS 缓存:ipconfig /displaydns

10.添加静态路由(临时 / 永久):
# 临时路由:访问 172.16.0.0/24 网段,通过网关 192.168.1.1
route add 172.16.0.0 mask 255.255.255.0 192.168.1.1 metric 5

# 永久路由:重启后保留(加 -p 参数)
route /p add 192.168.5.0 mask 255.255.255.0 10.0.0.1

11.删除路由:
route delete 172.16.0.0 mask 255.255.255.0

12.清除所有非核心路由:
route /f

13.开放指定端口(以 TCP 80 为例):
netsh advfirewall firewall add rule name="允许 TCP 80 端口" dir=in action=allow protocol=TCP localport=80

14.开放端口范围(如 1000-2000):
netsh advfirewall firewall add rule name="允许 TCP 1000-2000" dir=in action=allow protocol=TCP localport=1000-2000

15.允许程序通过防火墙(以 “notepad.exe” 为例):
netsh advfirewall firewall add rule name="允许记事本联网" dir=in action=allow program="C:\Windows\System32\notepad.exe"

16.启用 / 禁用防火墙:
netsh advfirewall set allprofiles state on # 启用
netsh advfirewall set allprofiles state off # 禁用

17.查看无线网卡驱动支持:
netsh wlan show drivers

18.创建并启动 Wi-Fi 热点:
# 1. 启用热点(SSID: MyHotspot,密码: 12345678)
netsh wlan set hostednetwork mode=allow ssid=MyHotspot key=12345678

# 2. 启动热点
netsh wlan start hostednetwork

# 3. 查看热点状态
netsh wlan show hostednetwork

# 4. 关闭热点
netsh wlan stop hostednetwork

19. 释放并续订 DHCP 地址:
ipconfig /release # 释放IP
ipconfig /renew # 重新获取IP



1.dhcp接口配置


2.dhcp全局配置

AR1000v 的配置指令与之前相同,只是中间加了一个交换机


3.dhcp中继配置

DHCP 依赖广播找服务器,但广播过不了路由器。
如果客户端和 DHCP 服务器在不同网段,客户端的“求 IP”广播会被路由器拦下,服务器永远收不到。
DHCP 中继就是路由器上的一个“信使”,它能收到客户端的广播,然后转发成单播给远端的 DHCP 服务器,让跨网段的客户端也能自动获取 IP。


三、数据包,段,帧等分析

1
2
3
4
5
6
7
8
9
10
[ Frame ] ———— 最外层:快递箱
|
└── [ Ethernet II ] ———— 外包装:收件人 MAC 地址、发件人 MAC 地址
|
└── [ Internet Protocol Version 4 ] ———— 中间层:IP 地址(192.168.1.10 → 8.8.8.8)
|
└── [ Transmission Control Protocol ] ———— 内包装:端口号(54321 → 80)
|
└── [ HTTP ] ———— 货物:你请求的网页内容

随便抓取一个tcp包进行分析:

物理层:

链路层:

[Stream index: 0]
这是抓包工具(如 Wireshark)的内部标识,表示该帧属于 “流索引为 0” 的数据包流(用于工具内部对相关数据包进行分组管理,方便分析同一 “流” 的通信)。

网络层:

传输层:

补充:


四、实验

1.网络搭建


  1. 左子网:

对AR1000v

win只需配置dhcp即可


  1. 右子网:

对于AR1000v

对winserver

配置静态ip:

安装DNS与IIS:

对DNS进行配置:


2.实验引入

没有进行任何操作对win进行抓包:

win主机向发送大量数据包,这是为什么?

Win (1) 节点是一个完整的 Windows 操作系统。当它启动并联网后,操作系统内部的许多服务和进程会自动开始工作,它们会发送各种网络请求,即使没有手动打开浏览器或执行命令。

1
2
3
4
5
6
7
8
这些“后台”活动包括:

1.网络发现服务:Windows 会尝试发现局域网内的其他计算机和共享资源。
2.NetBIOS 名称服务 (NBNS):这是旧版 Windows 用于在局域网内解析计算机名称的服务。您看到的 NBNS 和 Name query NB ISATAP<00> 就是这类查询。
3.链路本地多播名称解析 (LLMNR):这是现代 Windows 系统用来在局域网内解析主机名的一种替代方案。您看到的 LLMNR 数据包就是它发出的。
4.IPv6 邻居发现协议 (NDP):用于 IPv6 地址的自动配置和邻居发现。您看到的 fe80::... 开头的地址就是 IPv6 的链路本地地址。
多播地址通信:224.0.0.252 是一个用于 LLMNR 的多播地址。Windows 会向这个地址发送查询,询问“谁叫这个名字?”。

解决方案:

1.wireshark 过滤信息
2.在抓包前清空缓存 (ipconfig /release; arp -d; ipconfig /flushdns)


实验1(wireshark基础抓包分析)

略过

指令:

  • win:
1
2
3
4
5
6
7
8
9
10
11
12
# 彻底清除ARP缓存
arp -d *

# 释放并更新IP(强制DHCP过程)
ipconfig /release
ipconfig /renew

# 清除DNS缓存
ipconfig /flushdns

# 重启网络服务(可选)
netsh int ip reset
  • AR1000v:
1
2
# 清除路由器ARP缓存
clear arp-cache
  • winserver:
1
2
# 清除服务器ARP缓存
arp -d *

实验2(以太网MAC 帧分析)

实验思路:刷新win的网卡信息,随后ping dns服务器,抓取win-e0数据包进行分析

win的网卡信息:

winserver网卡信息:

网关接口的mac地址:

arp广播包分析:

arp应答


实验3(IP数据包的解析)

1
2
3
4
5
6
7
8
9
10
11
12
ipconfig/release
arp -d
ipconfig/flushdns
pause
ipconfig/renew
ping -l 2000 -f 192.168.2.20 (向DNS服务器发送2000字节数据包,不拆分)
ping -l 2000 192.168.2.20 (向DNS服务器发送2000字节数据包,允许拆分)
tracert 192.168.2.20 (跟踪到DNS服务器的路由)
pause

重新得到的 Win主机ip地址是 192.168.1.188

实验数据分析:

网络层结构分析(下面图片的数据不是本环境所抓取)

重点分析一下指令产生的数据包:

  • ping -l 2000 -f 192.168.2.20 (向DNS服务器发送2000字节数据包,不拆分)
  • ping -l 2000 192.168.2.20 (向DNS服务器发送2000字节数据包,允许拆分)
  • tracert 192.168.2.20 (跟踪到DNS服务器的路由)
  1. 数据包过滤:icmp && ip.dst == 192.168.2.20 (所有Win主机向DNS服务器发送的icmp报文)
  1. 查看所有分片的数据包:ip.frag_offset != 0
  1. 查看所有不分片的icmp包:ip.dst == 192.168.2.20 && icmp && ip.flags.df == 1

实验3总结:

IP数据包解析实验验证了网络层协议的核心特性:通过分析头部字段(如版本、首部长度、TTL、协议类型及源/目的IP地址),成功实现了对数据包传输路径、存活时间及上层协议类型的准确识别


实验4(网际控制报文协议 ICMP分析)

1
2
3
4
5
6
7
8
9

ipconfig/release
arp -d
ipconfig/flushdns

# 使用wireshark抓取对应网卡
ipconfig/renew
ping www.sina.com
tracert www.baidu.com

ping www.sina.com

过滤数据包:icmp && ip.dst == 163.142.155.14

ICMP数据包分析:

补充ICMP分析:

指令:tracert www.Baidu.com

过滤指令:icmp && ip.dst == 157.148.69.151

第32个网络帧TTL情况(no response):

第55个网络帧TTL情况(no response):

第112个网络帧TTL情况(reply):

数据包的整体情况:

TTL的值不断增加,其机制是:
tracert 是通过逐步增加 TTL(生存时间)值,结合 ICMP 协议来追踪路由的:

  1. 向目标发送第 1 组数据包时,设置TTL=1;
  2. 数据包到达第 1 跳路由器(比如本地网关),路由器将 TTL 减 1→变为 0,按照 IP 协议规则,路由器会返回一个 ICMP “时间超过” 消息,tracert 因此获取第 1 跳的地址;
  3. 接着发送第 2 组数据包,设置TTL=2,数据包到达第 2 跳路由器,TTL 减到 0,路由器返回 ICMP 超时消息→获取第 2 跳地址;
  4. 以此类推,直到数据包的 TTL 足够到达目标,目标会返回ICMP 回显响应(而非超时消息),tracert 结束跟踪。

实验5(TCP 数据包及连接建立过程分析)

TCP三次握手与四次挥手机制:

三次握手阶段(连接建立)

  1. 第1个包:客户端 → 服务器

    • 报文:SYN=1 Seq=x
    • 含义:
      • SYN=1:“同步”标志位,代表客户端向服务器发起连接请求
      • Seq=x:客户端告诉服务器“我接下来发数据的初始序号是x”;
    • 状态变化:客户端从ClosedSYN_SEND,服务器保持Listen(监听状态)。
  2. 第2个包:服务器 → 客户端

    • 报文:SYN=1 ACK=1 Seq=y Ack=x+1
    • 含义:
      • SYN=1:服务器同时向客户端发起自己的连接请求
      • ACK=1:“确认”标志位,代表服务器已收到客户端的连接请求
      • Seq=y:服务器告诉客户端“我接下来发数据的初始序号是y”;
      • Ack=x+1:“确认号”,表示“我期望收到你下一个包的序号是x+1”(因为已收到你序号为x的包);
    • 状态变化:服务器从ListenSYN_RCVD,客户端仍在SYN_SEND
  3. 第3个包:客户端 → 服务器

    • 报文:ACK=1 Seq=x+1 Ack=y+1
    • 含义:
      • ACK=1:客户端已收到服务器的连接请求
      • Seq=x+1:客户端按序号规则,下一个包的序号是x+1;
      • Ack=y+1:期望收到服务器下一个包的序号是y+1;
    • 状态变化:客户端→Established,服务器→Established(双方进入“已连接”状态,可开始传数据)。

数据传输阶段

  • 报文:图中“数据传输”对应的是双方在Established状态下的数据包
  • 含义:
    • 标志位默认带ACK=1(确认对方之前的包);
    • 包中会包含Application Data(实际要传输的内容,比如网页数据、文件内容);
    • SeqAck会根据“已传输的字节数”递增(比如客户端发了100字节数据,下一个Seq就是x+1+100)。

四次挥手阶段(连接释放)

  1. 第1个包:客户端 → 服务器

    • 报文:FIN=1 Seq=u
    • 含义:
      • FIN=1:“结束”标志位,代表客户端请求关闭自己的发送通道
      • Seq=u:客户端当前的序号是u;
    • 状态变化:客户端从EstablishedFIN_WAIT_1
  2. 第2个包:服务器 → 客户端

    • 报文:ACK=1 Seq=v Ack=u+1
    • 含义:
      • ACK=1:服务器已收到客户端的关闭请求
      • Ack=u+1:期望收到客户端下一个包的序号是u+1;
    • 状态变化:服务器从EstablishedCLOSE_WAIT,客户端从FIN_WAIT_1FIN_WAIT_2
  3. 第3个包:服务器 → 客户端

    • 报文:FIN=1 ACK=1 Seq=w Ack=u+1
    • 含义:
      • FIN=1:服务器请求关闭自己的发送通道
      • ACK=1:附带确认客户端之前的包;
      • Seq=w:服务器当前的序号是w;
    • 状态变化:服务器从CLOSE_WAITLAST_ACK,客户端仍在FIN_WAIT_2
  4. 第4个包:客户端 → 服务器

    • 报文:ACK=1 Seq=u+1 Ack=w+1
    • 含义:
      • ACK=1:客户端已收到服务器的关闭请求
      • Ack=w+1:期望收到服务器下一个包的序号是w+1;
    • 状态变化:客户端从FIN_WAIT_2TIME_WAIT(等待2MSL后回到Closed),服务器收到包后从LAST_ACKClosed

抓取虚拟机网卡,浏览器访问www.bilibili.com (http协议建立在tcp之上)

补充知识:
由于tcp数据包数量巨大,可以使用wireshark自带的 追踪流 功能(选中数据包,右键菜单窗口就会有 追踪流选项),追踪流也分多种,具体协议具体分析,缺陷:只能对明文信息进行追踪

TCP三次挥手分析:

获取 ‘bilibili’最近服务器的ip地址:157.148.134.11 port:443

指令:tcp.flags.syn == 1(只显示 SYN 标志为 1 的包)

可知 53号网络帧是第一次握手,57号网络帧是第二次握手;x=0 y=0

分析第三次握手的数据:
Seq=x+1 即 Seq=0+1=1; ACK=y+1 即 ACK=0+1=1

TCP四次挥手分析:

上面的抓包数据没有抓到完整的TCP的四次完整挥手包,需要重新抓取

Bilibili最近服务器的ip地址:157.148.134.11 port:443 与之前一样

指令:ip.addr == 192.168.79.134 && ip.addr == 157.148.134.11 && tcp.flags.fin == 1 (更加精准的过滤指令,抓取第一次,第三挥手的数据包)

  • 为什么第一次挥手与第三次挥手的网络帧编号‘差距’有点大呢?(相对于三次握手)

    首先要知道第三次挥手的意义:服务端告诉客户端‘正在’传输的数据传输完毕,也就是说,第一次挥手与第三次挥手之间的 时延 是为了将最后一次数据传输完毕

观察3361 3395 的ACK: 相同
1. 丢包了
2. 两次挥手之间就没有数据传输

显然是后者,所以 w=v 是合理的

实验总结

本次实验聚焦 TCP 协议的核心机制,通过 Wireshark 抓包验证了三次握手(连接建立)与四次挥手(连接释放)的流程。实验中,我们捕获目标网络连接的 TCP 数据包,利用过滤条件隔离有效报文,追踪 TCP 流分析 SYN、ACK、FIN 等标志位及序列号(Seq)、确认号(Ack)的变化规律。结果表明,抓包数据与理论机制完全吻合,Seq 与 Ack 严格遵循 “确认号 = 对方上一序列号 + 1” 的规则,成功验证了 TCP 协议 “面向连接、可靠传输” 的特性。通过实验,快速掌握了 Wireshark 抓包分析的核心技巧,深化了对 TCP 连接建立与释放逻辑的理解。


实验6(超文本传送协议HTTP分析)

由于之前的实验已经对应用层一下的网络层次结构作为分析,本次实验就不再分析了

分析http请求包:

分析http回应包:

实际上,http的内容字段各不相同,取决与服务器的资源格式
补充: http状态码
1. 信息性状态码:1xx(服务器已接收请求,正在处理,很少直接看到)
2. 成功状态码:2xx
3. 重定向 / 缓存状态码:3xx
4. 客户端错误状态码:4xx
5. 服务器错误状态码:5xx

实验总结:

本次实验通过 Wireshark 抓包分析 HTTP 协议,成功捕获访问国内网站的请求与响应数据包,验证了 HTTP 与 TCP 协议的协同工作逻辑。核心发现 HTTP 304 状态码实现缓存优化,抓包数据与理论机制一致。实验掌握了 Wireshark 抓包过滤技巧,深化了对应用层协议的理解


实验7(域名系统 DNS 分析)

1
2
3
4
5
6
ipconfig/release
arp -d
ipconfig/flushdns

ping www.bilibili.com

DNS请求包分析:

DNS响应包分析:

IP 地址 (class ,是一个16 位值,标记协议族或某一个协议实例,使用 IN 代表 internet 系统,CH代表 Chaos 系统; classIN 是一个 32 位 IP 地址 )

通过实验验证了 DNS 的核心作用:将人类易记的域名(如www.bilibili.com)转换为机器可识别的 IP 地址,同时观察到:
1. 大型网站常用 “CNAME 别名 + 多 A 记录” 的组合,兼顾域名灵活性与访问稳定性;
2. DNS 基于 UDP 53 端口通信(响应包较小),解析过程通过 “事务 ID” 确保请求与响应的精准匹配;
3. 本地 DNS 服务器(如 192.168.79.2)承担递归查询角色,替主机完成从根服务器→顶级域服务器→权威服务器的迭代查询,最终返回解析结果。


实验8(动态主机配置协议 DHCP 分析)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
动态主机配置协议 DHCP 提供了即插即用连网(plug-and-play networking)的机制。
这种机制允许一台计算机加入新的网络和获取 IP 地址而不用手工参与。
 需 要 IP 地 址 的 主 机 在 启 动 时 就 向 DHCP 服 务 器 广 播 发 送 发 现 报 文 ( DHCP
DISCOVER),这时该主机就成为 DHCP 客户。
 本地网络上所有主机都能收到此广播报文,但只有 DHCP 服务器才回答此广播报文。
 DHCP 服务器先在其数据库中查找该计算机的配置信息。若找到,则返回找到的信
息。若找不到,则从服务器的 IP 地址池(address pool)中取一个地址分配给该计算机。
DHCP 服务器的回答报文叫做提供报文(DHCP OFFER)
 并不是每个网络上都有 DHCP 服务器,这样会使 DHCP 服务器的数量太多。现在
是每一个网络至少有一个 DHCP 中继代理,它配置了 DHCP 服务器的 IP 地址信
息。
 当 DHCP 中继代理收到主机发送的发现报文后,就以单播方式向 DHCP 服务器转
发此报文,并等待其回答。收到 DHCP 服务器回答的提供报文后,DHCP 中继代
理再将此提供报文发回给主机。(DHCP OFFER)
 DHCP 客户从几个 DHCP 服务器中选择其中的一个,并向所选择的 DHCP 服务
器发送 DHCP 请求报文。(DHCP REQUEST)
 被选择的 DHCP 服务器发送确认报文 DHCPACK,进入已绑定状态。(DHCP ACK)

发现报文分析:

DHCP Discover 报文是 IP 地址分配流程的初始发起:客户端通过该报文以广播形式寻找网络中的 DHCP 服务器,传递自身硬件标识(MAC 地址)与会话标识(事务 ID),发起 IP 地址及网络配置的请求,标志 DHCP 全流程(Discover→Offer→Request→Ack)正式启动。

提供报文分析:

DHCP Request 报文是 IP 地址分配流程的方案锁定:客户端通过该报文明确选定目标 DHCP 服务器的 IP 分配方案,告知其他服务器释放预留资源,请求下发完整网络配置,标志 DHCP 全流程(Discover→Offer→Request→Ack)进入最终确认阶段

请求报文分析:

那DHCP请求报文存在的意义是什么?
DHCP Request 报文的核心作用是客户端向目标 DHCP 服务器确认租用其提供的 IP 地址,明确了客户端的地址需求与服务端标识,是 DHCP 地址分配流程中 “确认租用意向” 的关键环节,后续服务器会通过 DHCP Ack 报文完成最终的地址分配与配置下发

确认报文分析:

DHCP Ack 报文是IP 地址分配流程的最终确认:服务器通过该报文完成 IP 地址的正式分配,并下发子网掩码、网关、DNS 等完整网络配置,客户端接收后即可基于分配的 IP 地址建立网络连接,标志 DHCP 全流程(Discover→Offer→Request→Ack)完成

DHCP分配IP地址过程图:


实验9(WEB 页面请求全历程协议及数据包解析(综合性实验))

访问我自己的博客网站,获取数据:http://zq-rookie-hacker.github.io

1. 为什么不直接使用https呢?
可以观察到HTTP到HTTPS的重定向过程

2. 访问这个网站可以获取实验所需的数据吗?
真实网络环境:GitHub Pages是真实的Web服务,完全符合实验要求的”访问目标WEB网站”条件可能包含重定向:GitHub Pages通常会将HTTP请求重定向到HTTPS,这正好可以帮助您分析实验要求中的”如果发生重定向,新的web页面的地址是什么”问题
DNS解析复杂性:GitHub Pages使用CNAME记录和全球CDN,会使DNS解析过程更丰富,有助于分析DNS解析内容
网络协议完整性:仍然会经历完整的DNS解析→TCP连接建立→HTTP请求→数据传输过程

清除网站cookie:

刷新网络信息:

DNS响应包:

查询IP地址:

  1. P地址归属:185.199.109.153 是 GitHub 的IP地址之一
    • 这是GitHub Pages服务使用的服务器IP地址
    • GitHub Pages是用来托管博客的平台
  2. “泛播”的含义:
    • “泛播”(Anycast)是一种网络技术,多个服务器使用相同的IP地址
    • 当您访问这个IP时,网络会自动将您的请求路由到最近的服务器
    • 这种技术提高了服务的可靠性和访问速度

TCP协议分析:

TCP三次握手:(之前的实验分析过TCP三次握手的数据包了)

TCP四次挥手:(之前的实验分析过TCP四次挥手的数据包了)

结合抓包数据与 TCP 状态图,u、v、w的取值是 “三次握手后数据传输驱动序列号递增” 及 “TCP 报文序列号规则” 共同作用的结果:u=489对应客户端发起 FIN 报文(包 366)的序列号,v=670对应服务器回复 ACK 报文(包 367)的序列号,二者的数值是三次握手后数据传输让初始序列号持续递增的结果;而w=670则因 TCP 纯 ACK 报文不占用序列号,服务器发起 FIN 时延续了回复 ACK 的序列号,同时服务器回 ACK 的确认号(490)也符合状态图 “ACK=u+1” 的规则。

TCP超时重传:tcp.analysis.retransmission

都是在重传同一个包

过滤:

第一:使用的是 ip.src == 4.145.79.80 的过滤指令,那就说明了 服务器一直重传失败
第二:不继续重传,说明触发发tcp重传的终止机制

一、核心机制 1:超时重传(Timeout Retransmission)
这是最基础的重传方式,靠 “超时等待” 触发:
1. 触发条件:发送方发送一个 TCP 报文后,会启动一个 “超时计时器(RTO,Retransmission Timeout)”;如果计时器到期前,没收到接收方的 ACK 确认包,就会重传这个报文。
2. 超时时间(RTO)怎么定?
- 初始 RTO 是系统默认值(比如 1 秒);
- 后续会根据 “往返时间(RTT,报文发出去到收到 ACK 的时间)” 动态调整(比如 RTT 变长,RTO 也会跟着调大),避免误判超时。
3. 指数退避规则:每次重传失败后,RTO 会翻倍增长(比如第一次 RTO=1 秒,第二次 = 2 秒,第三次 = 4 秒…),避免短时间内重复发包导致网络拥塞。
4. 终止条件:重传次数达到系统设定的 “最大重传次数”(比如 Linux 默认 15 次)后,发送方会认为 “连接彻底不可用”,停止重传并断开 TCP 连接。
二、核心机制 2:快速重传( Fast Retransmission)
这是更高效的重传方式,靠 “重复 ACK” 主动触发:
1. 触发条件:接收方收到 “乱序的 TCP 报文” 时(比如应该收 Seq=2,但先收到了 Seq=3),会重复发送 “对已收到报文的 ACK”(比如一直发 Ack=2);当发送方收到3 个相同的重复 ACK,就会判定 “中间的报文(比如 Seq=2)丢失了”,直接重传这个丢失的报文。
2. 优势:不用等超时计时器到期,靠 “重复 ACK” 主动发现丢包,重传速度更快,能提升传输效率。

分析具体重传数据包:

http重定向分析:
http请求包:

http响应包:

Frame 269是 GitHub Pages 服务器的 “指令包”:它通过301状态码 +Location字段,强制将你原本访问的HTTP地址,重定向到更安全的HTTPS地址 —— 这是 GitHub Pages 的默认安全规则,也是当前网站普遍采用的 “HTTP→HTTPS 强制跳转” 实现方式

实验总结:

  1. WEB 页面请求是多协议协同的完整流程:DNS 解析(域名→IP)→TCP 连接(可靠传输基础)→HTTP 请求与响应(应用层数据交互)→重定向(协议 / 地址切换),各层协议各司其职、无缝衔接,确保请求合法、数据可靠传输。
  2. 关键技术与协议特性验证:GitHub Pages 采用 “CNAME 记录 + CDN + 泛播” 架构,通过多 IP 分发提升服务可用性;DNS 解析依赖事务 ID 与 UDP 端口保障通信精准性;TCP 三次握手是可靠连接的核心;HTTP 301 重定向是实现协议升级(HTTP→HTTPS)的标准方式,体现了应用层协议的灵活性与安全性设计。
  3. 抓包分析的核心技巧:清除缓存与历史会话是获取纯净数据包的前提;通过协议过滤(如dns tcp http)可快速定位关键流程;聚焦核心字段(DNS 的事务 ID、TCP 的标志位、HTTP 的状态码与 Location 头)能高效拆解协议交互逻辑

一、通用浏览器搜索指令

1. 基础操作符

1
2
3
4
5
6
""          # 精确匹配短语
- # 排除关键词
* # 通配符(替代任意字符)
OR # 逻辑或(必须大写)
AND # 逻辑与(通常空格即可)
() # 逻辑分组

2. 位置限定操作符

1
2
3
4
5
6
intitle:    # 标题中包含关键词
allintitle: # 所有关键词都在标题中
inurl: # URL中包含关键词
allinurl: # 所有关键词都在URL中
intext: # 正文中包含关键词
allintext: # 所有关键词都在正文中

3. 站点与文件操作符

1
2
3
4
5
6
site:       # 限定特定网站或域名
filetype: # 限定文件类型
related: # 查找相关网站(Google特有)
link: # 查找链接到指定页面的链接(Google特有)
cache: # 查看Google缓存版本(Google特有)
info: # 获取页面基本信息(Google特有)

4. 特殊符号操作符

1
2
3
4
5
6
7
@           # 搜索社交媒体用户名
# # 搜索话题标签
$ # 搜索价格
.. # 数值范围
define: # 查找定义(Google特有)
weather: # 查询天气
stocks: # 查询股票

二、搜索引擎特有指令

1. Google特有指令

1
2
3
4
5
6
7
8
9
10
11
# 缓存与信息
cache:URL # 查看Google缓存的页面
info:URL # 获取Google对URL的特定信息

# 高级内容限定
allinanchor:"文本" # 所有关键词必须出现在链接锚文本
allinpostauthor:"作者" # 搜索特定博客作者的帖子
allintext:"关键词" # 所有关键词必须出现在正文中

# 精确时间控制
after:2024-01-01 before:2024-12-31 # 精确日期范围

2. Bing特有指令

1
2
3
4
5
6
7
8
# 位置服务
ip:IP地址 # 按服务器IP地址搜索
feed:URL # 搜索特定RSS/Atom源
hasfeed:域名 # 搜索有RSS源的网站
near:"地标" # 附近地点搜索

# 媒体搜索
filetype: 类型 # 但支持更多媒体类型参数

3. 百度搜索特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 百度支持的标准指令
site:域名 # 站内搜索
filetype:类型 # 文件类型搜索
intitle:关键词 # 标题搜索
inurl:关键词 # URL搜索
intext:关键词 # 正文包含
-关键词 # 排除关键词
"精确匹配" # 精确短语搜索

# 百度不支持的Google特有指令
❌ 不支持 related:
❌ 不支持 link: (完整功能)
❌ 不支持 allin* 系列指令
❌ 不支持 after:/before: 精确日期语法
❌ 不支持 define:

4. 各搜索引擎支持情况

指令 Google Bing 百度 说明
site: 三大引擎都支持
filetype: 都支持,格式支持有差异
intitle: ✓* *Bing用title:语法
inurl: ✓* *Bing用url:语法
intext: Bing不支持此语法
-排除 基础功能都支持
""精确匹配 基础功能都支持
OR/` ` |
*通配符 支持程度有差异
cache: Google特有
related: 有限 Google特有
link: 有限 有限 Google支持最完整

三、进阶搜索组合技巧

1. 逻辑组合语法

1
2
3
4
5
6
7
8
# 基础逻辑组合
(A AND B) OR (C AND D)
"(精确短语1)" OR "(精确短语2)"
A -B -C # 包含A,排除B和C

# 复杂逻辑嵌套
((A OR B) AND (C OR D)) -E -F
(intitle:X OR inurl:Y) AND (filetype:pdf OR filetype:doc)

2. 多条件位置限定

1
2
3
4
5
# 标题+URL+内容三重限定
allintitle:"年度报告" inurl:2024 intext:"财务数据" -intext:广告

# 多位置条件组合
(intitle:A OR intitle:B) AND (inurl:C OR inurl:D) AND (intext:E OR intext:F)

3. 站点与文件类型组合

1
2
3
4
5
# 多站点+多文件类型
(A OR B) (site:X.com OR site:Y.org) (filetype:pdf OR filetype:docx)

# 站点层级+文件类型
site:.edu.cn OR site:.ac.cn "学术论文" filetype:pdf

4. 时间与质量优化

1
2
3
4
5
6
7
8
9
10
# 时效性控制
关键词 after:2024-01-01 OR (2024..2024)

# 质量提升
关键词 (site:gov.cn OR site:edu.cn) -advertisement -spam

# 多维度验证
搜索组1: (核心词) site:权威源1
搜索组2: (核心词) site:权威源2
搜索组3: (核心词) site:权威源3

四、通用搜索场景实战

1. 学术研究场景

1
2
# 论文精准搜索
allintitle:("deep learning" OR "neural network") ("medical diagnosis" OR "disease detection") site:scholar.google.com OR site:arxiv.org filetype:pdf 2020..2024 -survey -review
1
2
# 文献综述收集
("literature review" OR "systematic review") ("climate change" OR "global warming") (site:nature.com OR site:science.org) filetype:pdf after:2022-01-01
1
2
# 作者追踪
author:"Yann LeCun" OR author:"Andrew Ng" ("deep learning" OR "AI") site:arxiv.org filetype:pdf

2. 技术开发场景

1
2
# 开源项目搜索
("machine learning" OR "AI") ("open source" OR "GitHub") stars:>1000 forks:>100 language:Python OR language:JavaScript site:github.com -tutorial -example
1
2
# API文档定位
("REST API" OR "GraphQL API") ("authentication" OR "authorization") ("Python" OR "Node.js") site:swagger.io OR site:postman.com OR site:rapidapi.com -blog -news
1
2
# 错误解决方案
("TypeError" OR "RuntimeError") ("Python 3.8" OR "Python 3.9") ("fix" OR "solution" OR "解决") site:stackoverflow.com OR site:github.com/issues -ask -question

3. 商业分析场景

1
2
# 竞品分析
("iPhone 15" OR "Samsung S24" OR "Huawei P60") ("review" OR "comparison" OR "评测" OR "对比") (site:cnet.com OR site:theverge.com OR site:zealer.com) after:2023-09-01 -buy -purchase -shop
1
2
# 市场趋势
("AI startup" OR "artificial intelligence company") ("funding" OR "investment" OR "融资" OR "投资") (China OR Chinese OR 中国) site:crunchbase.com OR site:36kr.com OR site:itjuzi.com 2023..2024
1
2
# 用户反馈
("product feedback" OR "user review" OR "产品反馈" OR "用户体验") ("mobile app" OR "web application" OR "移动应用" OR "网页应用") site:appstore.com OR site:coolapk.com OR site:meituan.com -spam -junk

4. 新闻与舆情监控

1
2
# 重大事件追踪
("earthquake" OR "earthquake") ("casualties" OR "damage" OR "伤亡" OR "损失") (site:reuters.com OR site:apnews.com OR site:xinhuanet.com) after:2024-12-27 -site:twitter.com -site:weibo.com
1
2
# 品牌声誉监控
("腾讯" OR "Tencent") (site:weibo.com OR site:zhihu.com OR site:tieba.baidu.com) (-intext:官方 -intext:官网) (intext:投诉 OR intext:问题 OR intext:差评) after:2024-01-01
1
2
# 政策影响分析
("data privacy" OR "privacy law") ("China" OR "中国") ("impact" OR "影响") (site:gov.cn OR site:people.com.cn) after:2023-01-01 filetype:pdf

五、网络安全专业搜索指令

1. 漏洞与CVE搜索

1
"CVE-2024-1234" (site:nvd.nist.gov OR site:exploit-db.com OR site:github.com) filetype:pdf OR filetype:md
1
("buffer overflow" OR "stack overflow") ("exploit" OR "poc") site:github.com stars:>100 language:python OR language:c
1
("SQL injection" OR "XSS") ("bypass" OR "evasion") (site:owasp.org OR site:portswigger.net) -tutorial -beginner

2. 威胁情报与恶意软件分析

1
("Emotet" OR "TrickBot" OR "QakBot") ("IOC" OR "indicators of compromise") (site:virustotal.com OR site:hybrid-analysis.com OR site:any.run) filetype:json OR filetype:csv
1
("APT29" OR "Cozy Bear" OR "Fancy Bear") ("threat report" OR "analysis") (site:fireeye.com OR site:crowdstrike.com OR site:mandiant.com) filetype:pdf after:2023-01-01
1
("malware analysis" OR "reverse engineering") ("YARA rule" OR "signature") site:github.com (language:python OR language:yara) stars:>50

3. 安全工具与框架搜索

1
("Metasploit" OR "Cobalt Strike" OR "Empire") ("detection" OR "evasion" OR "AV bypass") site:github.com OR site:exploit-db.com -commercial -paid
1
("Burp Suite" OR "ZAP" OR "OWASP ZAP") ("extension" OR "plugin") ("authentication bypass" OR "SSRF") site:github.com language:java OR language:python
1
("nmap" OR "masscan" OR "zmap") ("script" OR "NSE") ("vulnerability scanning" OR "service detection") site:github.com stars:>100

4. 高级威胁研究

1
("zero-day" OR "0day") ("proof of concept" OR "PoC") ("Windows 11" OR "Linux kernel") site:github.com OR site:packetstormsecurity.com after:2024-01-01 -tutorial -educational
1
("Log4j" OR "SpringShell") ("patch analysis" OR "mitigation") (site:microsoft.com OR site:apache.org OR site:spring.io) filetype:pdf OR filetype:html
1
("network forensics" OR "PCAP analysis") ("Zeek" OR "Bro") ("malware traffic" OR "C2 communication") site:github.com (language:python OR language:zeek) stars:>200

5. 威胁情报专业搜索

1
("APT41" OR "Winnti") ("campaign" OR "operation") ("infrastructure" OR "TTPs") (site:mandiant.com OR site:proofpoint.com) filetype:pdf after:2023-06-01
1
("file hash" OR "MD5" OR "SHA256") ("malicious" OR "known bad") ("IoC") site:virustotal.com OR site:hybrid-analysis.com OR site:malwarebazaar.com
1
("IP blacklist" OR "malicious IP") ("botnet C2") ("ASN") site:blocklist.de OR site:abuseipdb.com OR site:spamhaus.org

6. 云安全与容器安全

1
("AWS" OR "Azure" OR "GCP") ("misconfiguration" OR "security best practices") ("S3 bucket" OR "IAM role") (site:aws.amazon.com OR site:docs.microsoft.com) filetype:json OR filetype:yaml
1
("Docker" OR "Kubernetes") ("security hardening" OR "CIS benchmark") site:github.com (language:go OR language:python) stars:>500
1
("Kubernetes security monitoring" OR "K8s audit logs") ("Falco" OR "kube-bench") ("container escape" OR "privilege escalation") site:github.com/falcosecurity

7. 企业安全防御

1
("Sigma rule" OR "log detection") ("Windows Event Log" OR "Sysmon") ("Mimikatz" OR "PsExec") site:github.com/sigma-hq/sigma
1
("EDR bypass" OR "AV evasion") ("Cobalt Strike") ("process injection" OR "direct syscalls") (site:github.com OR site:specterops.io) -commercial -paid
1
("Active Directory attack" OR "AD exploitation") ("BloodHound" OR "SharpHound") ("ACL abuse" OR "delegation") site:github.com/BloodHoundAD

六、网络安全实战组合示例

1. 漏洞研究与验证

1
("zero-day" OR "0day") ("proof of concept" OR "PoC") ("Windows 11" OR "Linux kernel") site:github.com OR site:packetstormsecurity.com after:2024-01-01 -tutorial -educational
1
("Log4j" OR "SpringShell" OR "ProxyLogon") ("patch analysis" OR "mitigation") (site:microsoft.com OR site:apache.org OR site:spring.io) filetype:pdf OR filetype:html
1
("CVE-2023-1234" OR "CVE-2024-5678") ("exploit code" OR "exploit development") ("x86_64" OR "ARM64") site:exploit-db.com OR site:github.com/gists

2. 网络防御与检测

1
("IDS/IPS" OR "intrusion detection") ("Suricata rule" OR "Snort rule") ("exploit kit" OR "malware delivery") (site:emergingthreats.net OR site:snort.org) filetype:rules OR filetype:json
1
("memory forensics" OR "volatile memory") ("Volatility" OR "Rekall") ("malware detection" OR "rootkit analysis") site:github.com/volatilityfoundation OR site:github.com/google/rekall
1
("Sigma rule" OR "log detection") ("Windows Event Log" OR "Sysmon") ("Mimikatz" OR "PsExec") site:github.com/sigma-hq/sigma OR site:sigma-core.readthedocs.io

3. 红蓝对抗专业搜索

1
("Active Directory attack" OR "AD exploitation") ("BloodHound" OR "SharpHound") ("ACL abuse" OR "delegation") site:github.com/BloodHoundAD OR site:specterops.io -commercial
1
("hunting hypothesis" OR "threat hunting") ("Microsoft Defender") ("process tree" OR "child process") ("Mimikatz" OR "Rubeus") site:github.com/microsoft
1
("domain fronting" OR "CDN bypass") ("Cloudflare" OR "AWS CloudFront") ("C2 channel") (site:github.com OR site:specterops.io) -tutorial -beginner

4. IoT/OT与工业安全

1
("firmware analysis" OR "binwalk") ("router" OR "camera" OR "IoT device") ("backdoor" OR "hardcoded credential") site:github.com (language:python OR language:shell) stars:>300
1
("ICS security" OR "SCADA security") ("Modbus" OR "DNP3") ("protocol analysis" OR "fuzzing") (site:github.com OR site:ics-cert.us-cert.gov) filetype:pcap OR filetype:json
1
("JTAG debugging" OR "UART interface") ("firmware extraction" OR "chip-off") ("ESP32" OR "ARM Cortex") (site:github.com OR site:hackaday.com) -commercial -paid

5. 合规与取证

1
("digital forensics" OR "incident response") ("timeline analysis" OR "artifact collection") ("Windows" OR "Linux") (site:sleuthkit.org OR site:autopsy.com) filetype:pdf OR filetype:html
1
("NIST SP 800-53" OR "NIST Cybersecurity Framework") ("security controls" OR "compliance mapping") site:nist.gov OR site:github.com
1
("GDPR compliance" OR "CCPA compliance") ("data breach notification" OR "incident reporting") ("72 hours") (site:gdpr-info.eu OR site:ccpa-info.com) filetype:pdf

七、专业搜索策略与最佳实践

1. 搜索流程优化

1
2
3
4
5
6
7
8
9
10
# 三步优化法
步骤1:宽泛搜索 → "人工智能"
步骤2:增加限定 → "人工智能" site:edu.cn filetype:pdf
步骤3:精确过滤 → allintitle:"人工智能发展报告" site:tsinghua.edu.cn filetype:pdf -advertisement

# 网络安全专业应用
初始搜索: "Log4j vulnerability"
第一步优化: "Log4j vulnerability" site:apache.org OR site:github.com
第二步优化: "CVE-2021-44228" (site:nvd.nist.gov OR site:exploit-db.com) filetype:pdf
第三步优化: allintext:"JNDI lookup" ("exploit code" OR "poc") site:github.com language:java stars:>50 after:2021-12-01

2. 搜索结果质量提升

1
2
3
4
5
6
7
8
9
10
11
12
# 排除低质量内容
关键词 -advertisement -ad -推广 -广告 -buy -purchase -shop -free -download -tutorial -beginner

# 提升权威性
关键词 (site:gov.cn OR site:edu.cn OR site:org.cn) OR (site:.gov OR site:.edu OR site:.org)

# 时效性控制
关键词 after:2024-01-01 OR (2024..2024)

# 网络安全特定优化
("CVE-2024") -site:twitter.com -site:reddit.com site:nvd.nist.gov OR site:exploit-db.com
("malware analysis") -site:youtube.com site:github.com OR site:virustotal.com

3. 多维度验证策略

1
2
3
4
5
6
7
8
9
# 信息交叉验证
搜索1: ("Log4j vulnerability") site:apache.org
搜索2: ("CVE-2021-44228") site:nvd.nist.gov
搜索3: ("Log4Shell") site:github.com/security-advisories

# 威胁情报验证
威胁源1: ("APT29") site:fireeye.com OR site:mandiant.com
威胁源2: ("Cozy Bear") site:crowdstrike.com OR site:secureworks.com
威胁源3: ("Nobelium") site:microsoft.com/security/blog

八、错误处理与性能优化

1. 常见错误模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❌ 错误模式1:空格问题
site: zhihu.com # 错误:操作符后不应有空格
✅ 正确:site:zhihu.com

❌ 错误模式2:引号不匹配
"人工智能发展 # 错误:缺少结束引号
✅ 正确:"人工智能发展"

❌ 错误模式3:逻辑混乱
A OR B AND C OR D # 错误:逻辑优先级不明确
✅ 正确:(A OR B) AND (C OR D)

❌ 网络安全特定错误
allintext:"admin password" site:target.com # 可能涉及未授权访问
✅ 合规做法:在授权范围内搜索漏洞信息

2. 性能优化技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 避免过度复杂
❌ 过度复杂:(A OR B OR C) AND (D OR E OR F) AND (G OR H OR I) site:X.com filetype:pdf after:2020-01-01 -J -K -L -M -N
✅ 合理复杂:(A OR B) AND (C OR D) site:X.com filetype:pdf after:2020-01-01 -J -K

# 优先级建议
1. 先用site:限定范围
2. 用filetype:限定类型
3. 用intitle:/inurl:精确定位
4. 用-排除干扰
5. 最后用时间限定

# 网络安全搜索优先级
1. 先用site:限定到权威源 (nvd.nist.gov, exploit-db.com)
2. 用filetype:限定到技术文档 (pdf, md, json)
3. 用时间限定确保信息最新
4. 用-author:"非权威作者" 排除非专业内容

九、跨平台搜索策略

1. 桌面浏览器优化

1
2
3
4
5
6
7
8
9
# Chrome高级搜索
1. 在地址栏输入:搜索引擎关键词 + 空格 + 搜索词
2. 设置自定义搜索引擎:chrome://settings/searchEngines
3. 使用快捷键:Ctrl+K 或 Ctrl+E 快速聚焦搜索框

# Firefox高级搜索
1. 使用about:config启用power mode
2. 设置关键词搜索:右键搜索框→添加关键词
3. 使用Ctrl+K快速搜索

2. 移动端搜索技巧

1
2
3
4
5
6
7
8
9
10
# 手机浏览器
- 长按文本选择→搜索
- 语音搜索优化:用自然语言
- 图片搜索:拍照或上传图片
- 二维码搜索:直接扫描搜索

# 移动搜索优化
- 简化查询:移动端屏幕小,用简短关键词
- 语音友好:避免复杂符号,用口语化表达
- 位置感知:添加位置关键词(附近、本地等)

3. 专业工具整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 搜索引擎API
Google Custom Search JSON API
Bing Web Search API
百度搜索API

# 浏览器扩展
- 搜索增强插件
- 多引擎同时搜索
- 搜索历史管理
- 搜索结果过滤

# 自动化脚本
Python + Selenium 自动化搜索
Google Programmable Search Engine
RSS订阅 + 搜索关键词监控

附录:搜索指令速查表

通用搜索速查表

指令类型 语法 说明 示例
精确匹配 " " 保持短语完整 "machine learning"
排除关键词 - 排除特定关键词 apple -fruit
逻辑或 OR 包含任一关键词 car OR automobile
站内搜索 site: 限定特定网站 AI site:github.com
文件类型 filetype: 限定文件类型 report filetype:pdf
标题搜索 intitle: 标题包含关键词 intitle:tutorial
URL搜索 inurl: URL包含关键词 inurl:admin
定义查询 define: 查找词语定义 define:algorithm

网络安全搜索速查表

搜索场景 推荐指令 说明
CVE搜索 "CVE-2024-XXXX" (site:nvd.nist.gov OR site:exploit-db.com) 搜索特定CVE漏洞信息
恶意软件分析 ("malware family") ("YARA rule" OR "IOC") site:github.com 搜索特定恶意软件家族的分析资源
漏洞利用 ("vulnerability type") ("exploit PoC") site:github.com -tutorial 搜索漏洞利用概念验证代码
安全工具 ("tool name") ("detection" OR "evasion") site:github.com stars:>100 搜索安全工具及其检测/规避方法
威胁情报 ("APT group") ("campaign report") (site:fireeye.com OR site:crowdstrike.com) 搜索APT组织活动报告
云安全 ("AWS" OR "Azure") ("misconfiguration") ("best practices") site:aws.amazon.com 搜索云平台安全配置指南
二进制分析 ("binary analysis") ("reverse engineering") ("IDA Pro" OR "Ghidra") site:github.com 搜索二进制分析工具和脚本
网络取证 ("network forensics") ("PCAP analysis") ("Zeek" OR "Suricata") filetype:pcap 搜索网络取证样本和工具

搜索引擎支持情况对比表

指令类型 Google Bing 百度 说明
精确匹配 " " " " " " 保持短语完整
排除 - - - 排除特定关键词
逻辑或 OR OR | 百度用竖线|代替OR
站内搜索 site: site: site: 限定特定网站
文件类型 filetype: filetype: filetype: 限定文件类型
标题搜索 intitle: title: intitle: 标题包含关键词
URL搜索 inurl: url: inurl: URL包含关键词
时间限定 after:/before: date: 时间范围控制
通配符 * * * 替代未知字符
定义 define: define: 查找词语定义


🔍 核心原理

项目 正确做法 错误做法
安装路径 通过官方脚本安装到 /usr/bin/ 安装到 ~/.cargo/bin/(需额外配置 PATH)
文件位置 移动到 ~/.local/share/applications/ 保留在桌面或 /usr/share/
Categories 标签 kali-reconnaissance;kali-network-information;kali-network-service-discovery; Security;kali-network-service-discovery;(缺少 kali-network-information
图标 Icon=system-run kali-rustscan(不存在)
命令格式 Exec=xfce4-terminal -e "bash -c 'read -p \"IP: \" ip; rustscan -a $ip -- -sV; read -p \"按 Enter 键退出\"'" rustscan --help(只显示帮助)

📌 5 步操作

步骤 1:通过 GitHub 安装 rustscan(官方脚本)

1
2
3
4
5
# 1. 安装 curl(如果未安装)
sudo apt install curl -y

# 2. 执行官方安装脚本(自动下载到 /usr/bin/)
curl -s https://raw.githubusercontent.com/RustScan/RustScan/master/script/install.sh | sudo bash

验证安装

which rustscan
# 预期输出:/usr/bin/rustscan

rustscan --version
# 预期输出:2.1.1(或最新版本)

💡 关键原理

  • 安装脚本会自动将二进制文件放到 /usr/bin/(Linux 标准 PATH 路径)
  • 无需手动配置 PATH,系统自动识别命令

步骤 2:在桌面创建启动器

  1. 右键桌面空白处 → 选择 “创建启动器(L)…”
  2. 填写信息:一般会自动填充

步骤 3:移动文件到正确目录

1
2
3
4
5
# 1. 当前用户的共享文件夹下的 applications 目录
mkdir -p ~/.local/share/applications (没有则需要创建)

# 2. 移动桌面文件到目标目录
mv ~/Desktop/rustscan.desktop ~/.local/share/applications/

验证目录

ls -ld ~/.local/share/applications
# 预期输出:drwxr-xr-x 2 ... /home/rookie-hacker/.local/share/applications

步骤 4:修正 Categories 标签

1
2
# 1. 编辑 desktop 文件
vim ~/.local/share/applications/rustscan.desktop

✏️ 修改内容

# 现在查看右键菜单中其他快捷方式的格式
cat /usr/share/applications/xxx.desktop
# 找到 Categories 行,替换为精准标签
Categories=kali-reconnaissance;kali-network-information;kali-network-service-discovery; 

‘完整’配置示例

[Desktop Entry]
Version=1.0
Type=Application
Name=
Comment=
Exec=
Icon=system-run
Terminal=true
Type=Application
Categories=kali-reconnaissance;kali-network-information;kali-network-service-discovery; # 主要修改这里,其他在创建启动器的时候会自动填充
StartupNotify=false

✏️ 保存并退出

  • :wq

步骤 5:刷新菜单缓存

1
2
3
4
5
# 1. 删除所有缓存
rm -rf ~/.cache/menus ~/.cache/desktop-directories ~/.cache/desktop-files

# 2. 重启面板(强制生效)
xfce4-panel --restart

⚠️ 常见错误解决方案

问题 原因 解决方案
which rustscan 无输出 安装失败 重新执行安装脚本:curl -s https://raw.githubusercontent.com/RustScan/RustScan/master/script/install.sh | sudo bash
移动时报错 “不是目录” ~/.local/share/applications 是文件 执行 rm -f ~/.local/share/applications 再重建目录
菜单只在”网络扫描”显示 缺少 kali-network-information 标签 sed 修正 Categories 行:sed -i 's/Categories=.*/Categories=kali-reconnaissance;kali-network-information;kali-network-service-discovery;/' ~/.local/share/applications/rustscan.desktop
终端报错 rustscan: command not found PATH 未包含 /usr/bin/ 执行 echo 'export PATH="$PATH:/usr/bin"' >> ~/.zshrc && source ~/.zshrc
图标不显示 未使用 system-run 确保 Icon=system-run(不是 kali-rustscan

一、编码基础概述

1.1 为什么需要编码

在计算机系统中,不同系统、协议和应用程序对数据的表示和处理方式存在差异。编码技术解决了以下问题:

  • 字符表示:将人类可读的字符转换为计算机可处理的二进制数据
  • 数据传输:确保特殊字符在传输过程中不被误解或破坏
  • 安全过滤:绕过基于特定字符集的过滤机制
  • 数据压缩:在某些情况下提高传输效率

1.2 编码与加密的区别

  • 编码:数据格式的转换,是可逆的、公开的转换过程,不提供保密性
  • 加密:通过密钥将数据转换为密文,提供保密性和完整性保护

1.3 编码在网络安全中的重要性

渗透测试中,攻击者常利用编码技术:

  • 绕过WAF(Web应用防火墙)和输入过滤
  • 隐藏恶意负载
  • 规避安全检测机制
  • 利用解析差异实施攻击

二、基础字符编码

2.1 ASCII编码

ASCII编码背景

  • 历史:1963年由美国国家标准协会(ANSI)制定,最初用于电报通信
  • 标准范围:0-127(7位),共128个字符
  • 扩展ASCII:8位编码,范围0-255,包含特殊符号和非英语字符
  • 渗透测试意义:作为大多数现代编码的基础,理解ASCII对于分析编码绕过至关重要

完整ASCII表

十进制 十六进制 二进制 字符 名称/说明
控制字符 (0-31, 127)
0 0x00 00000000 NUL 空字符 (Null)
7 0x07 00000111 BEL 响铃 (Bell)
8 0x08 00001000 BS 退格 (Backspace)
9 0x09 00001001 HT 水平制表符 (Horizontal Tab)
10 0x0A 00001010 LF 换行 (Line Feed)
13 0x0D 00001101 CR 回车 (Carriage Return)
27 0x1B 00011011 ESC 转义 (Escape)
127 0x7F 01111111 DEL 删除 (Delete)
可打印字符 (32-126)
32 0x20 00100000 [空格] 空格 (Space)
33 0x21 00100001 ! 感叹号
34 0x22 00100010 双引号
39 0x27 00100111 单引号
40-41 0x28-0x29 00101000-00101001 () 括号
42 0x2A 00101010 * 星号
43 0x2B 00101011 + 加号
44 0x2C 00101100 , 逗号
45 0x2D 00101101 - 连字符
46 0x2E 00101110 . 句点
47 0x2F 00101111 / 斜杠
58-59 0x3A-0x3B 00111010-00111011 :; 冒号、分号
60-62 0x3C-0x3E 00111100-00111110 <> 小于号、大于号
63 0x3F 00111111 ? 问号
64 0x40 01000000 @ at符号
65-90 0x41-0x5A 01000001-01011010 A-Z 大写字母
91-96 0x5B-0x60 01011011-01100000 []^_` 特殊符号
97-122 0x61-0x7A 01100001-01111010 a-z 小写字母
123-126 0x7B-0x7E 01111011-01111110 { }~

渗透测试中的ASCII特殊字符应用

  • 空格(0x20):SQL注入中替代空格的常用字符(如%09%0A
  • 单引号(0x27):SQL注入关键字符,常需编码绕过
  • 双引号(0x22):XSS攻击中常用于闭合属性
  • 尖括号(0x3C, 0x3E):HTML标签标识符,XSS攻击核心
  • 斜杠(0x2F):路径分隔符,目录遍历攻击关键
  • 反斜杠(0x5C):在某些语言中作为转义字符

2.2 Unicode编码体系

  • UTF-8

    • 可变长度编码(1-4字节)
    • 向后兼容ASCII
    • 互联网标准(RFC 3629),现代Web应用首选
    • 优势:节省空间(ASCII字符仅需1字节),支持全球语言
  • UTF-16

    • 固定2字节或可变4字节编码
    • Windows系统内部常用
    • BOM(Byte Order Mark)问题可能导致解析异常
  • UTF-32

    • 固定4字节编码
    • 处理简单但空间效率低
    • 较少用于网络传输
  • 渗透测试应用

    • 利用不同系统对Unicode解析的差异
    • BOM头注入绕过文件类型检测
    • 混合编码导致的解析漏洞

2.3 ISO-8859系列编码

  • ISO-8859-1 (Latin-1):西欧语言
  • ISO-8859-2:中欧语言
  • ISO-8859-5:西里尔字母
  • 渗透测试应用:在未指定编码的Web应用中引发乱码问题,可能导致安全漏洞

三、Web相关编码技术

3.1 页面编码

  • 设置方式

    1
    <meta charset="utf-8" />

    或HTTP响应头:

    1
    Content-Type: text/html; charset=utf-8
  • 常用编码

    • UTF-8:现代Web标准
    • GBK/GB2312:中文环境常用
    • ISO-8859-1:早期Web应用
  • 渗透测试应用

    • 利用编码不一致导致的XSS漏洞
    • 混合编码绕过过滤
    • 通过修改响应头欺骗客户端解析

3.2 HTML编码

  • 命名实体&nbsp;&lt;&gt;

  • 十进制实体&#160;&#60;&#62;

  • 十六进制实体&#xA0;&#x3C;&#x3E;

  • HTML5新增实体:更多特殊字符支持

  • 渗透测试应用

    1
    <img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;" />
    • 多层编码绕过过滤器
    • 混合使用不同编码方式
    • 利用浏览器解析差异
  • 注意事项

    • 仅在属性值和文本内容中有效
    • 不能对标签名、属性名进行HTML编码

3.3 URL编码

  • 标准:RFC 3986

  • 编码规则

    • 保留字符(如!, *, ', (, ), ;, :, @, &, =, +, $, ,, /, ?, #, [, ])需要编码
    • 非ASCII字符必须编码
    • 空格编码为+%20
  • 渗透测试应用

    1
    http://example.com/index.php?keyword=aa%20union%20select
    • 绕过基于关键字的过滤
    • 多次URL编码绕过WAF
    • 混合编码技术(如先Base64再URL编码)

3.4 JavaScript编码

  • 十六进制编码

    1
    \x3C\x73\x63\x72\x69\x70\x74\x3Ealert(1)\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E
  • 八进制编码

    1
    \74\163\143\162\151\160\164\76alert(1)\74\57\163\143\162\151\160\164\76
  • Unicode编码

    1
    \u003c\u0073\u0063\u0072\u0069\u0070\u0074\u003ealert(1)\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e
  • 渗透测试应用

    • 绕过基于关键字的JS过滤
    • 混合编码实现高级XSS
    • 利用<script>标签的编码绕过

3.5 CSS编码

  • Unicode编码\0000格式
  • 十六进制编码\0000\x0000
  • 渗透测试应用
    1
    body { background: url('\x68\x74\x74\x70\x3a\x2f\x2f\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d'); }
    • CSS注入攻击
    • 绕过内容安全策略(CSP)

四、数据传输编码

4.1 Base64编码

  • 原理:将二进制数据转换为64个可打印ASCII字符

  • 标准:RFC 4648

  • 特点

    • 编码后数据量增加约33%
    • 末尾可能有1-2个=作为填充
    • URL安全变种使用-_代替+/
  • 渗透测试应用

    1
    <iframe src='data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=='></iframe>
    • 隐藏恶意脚本
    • 绕过基于关键字的过滤
    • 多层编码(Base64 + URL编码)

4.2 Hex编码

  • 原理:将每个字节转换为两位十六进制数

  • 数据库应用:MySQL中使用0x前缀

    1
    SELECT 0x48656C6C6F; -- 输出Hello
  • 渗透测试应用

    • SQL注入绕过
    • 文件内容十六进制表示
    • 混合编码绕过过滤

4.3 UUEncode

  • 历史:早期用于Unix系统间邮件传输
  • 特点:将3字节转换为4个可打印字符
  • 渗透测试应用:在遗留系统中可能发现的编码方式

4.4 Quoted-Printable

  • 标准:RFC 2045,用于MIME编码

  • 特点

    • 保留可打印ASCII字符
    • 非ASCII字符和特殊字符编码为=后跟两位十六进制
    • 每行不超过76字符
  • 渗透测试应用

    • 邮件系统漏洞利用
    • 绕过基于内容的过滤

五、数据结构编码

5.1 JSON编码

  • 标准:RFC 8259

  • 特点

    • 轻量级数据交换格式
    • 支持对象、数组、字符串、数字、布尔值和null
    • 严格的语法要求
  • 渗透测试应用

    1
    [{"Name":"a1","Number":"123","Contno":"000","QQNo":""}]
    • JSON注入攻击
    • 利用解析差异实施攻击
    • 通过编码绕过JSON验证

5.2 XML编码

  • 实体编码&lt;, &gt;, &amp;
  • CDATA<![CDATA[...]]>区域
  • 渗透测试应用
    • XXE(XML External Entity)攻击
    • 利用编码绕过XML解析器
    • 混合编码实现高级攻击

5.3 序列化

  • PHP序列化

    1
    a:3:{i:0;s:3:"Moe";i:1;s:5:"Larry";i:2;s:5:"Curly";}
    • a表示数组,i表示整数,s表示字符串
  • Java序列化:二进制格式,易受反序列化漏洞影响

  • Python Pickle:类似Java序列化,存在安全风险

  • 渗透测试应用

    • 反序列化漏洞利用
    • 操作序列化数据实现远程代码执行
    • 绕过基于结构的过滤

六、特殊编码技术

6.1 UTF-7编码

  • 历史:设计用于在7位传输通道中传输Unicode
  • 特点:使用+开头的编码序列
  • 渗透测试应用
    1
    <img src="moonsec +AG0AbwBv-n+AHM-e+AGM-" />
    • 绕过基于ASCII的过滤器
    • XSS攻击中的经典绕过技术
    • 现代浏览器已逐渐弃用,但在旧系统中仍有效

6.2 Punycode

  • 标准:RFC 3492
  • 用途:将国际化域名转换为ASCII兼容格式
  • 渗透测试应用
    • IDN欺骗(同形异义字攻击)
    • 钓鱼网站构造
    • 例如:xn--80ak6aa92e.com 可能显示为 “аpple.com”(使用西里尔字母а)

6.3 Double Encoding(双重编码)

  • 原理:对同一数据进行两次相同或不同的编码
  • 渗透测试应用
    1
    %253Cscript%253Ealert(1)%253C/script%253E
    • 第一次解码:%3Cscript%3Ealert(1)%3C/script%3E
    • 第二次解码:<script>alert(1)</script>
    • 绕过仅进行单次解码的WAF

6.4 Mixed Encoding(混合编码)

  • 原理:结合多种编码方式
  • 渗透测试应用
    1
    %25%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%31%29%3C%2F%73%63%72%69%70%74%3E
    • URL编码 + HTML编码混合
    • 绕过多层过滤系统

七、编码在渗透测试中的实际应用

7.1 绕过WAF

  • 多层编码:对同一内容进行多次编码
  • 混合编码:结合URL、HTML、JS等多种编码
  • 大小写混淆:利用WAF大小写敏感性
  • 空格替换:用%09(TAB)、%0A(换行)等代替空格

7.2 XSS攻击

  • HTML编码绕过
    1
    <img src=x onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">
  • JS编码绕过
    1
    \u0061\u006c\u0065\u0072\u0074(1)
  • 混合编码
    1
    <img src=x onerror="eval('\x61\x6c\x65\x72\x74\x28\x31\x29')">

7.3 SQL注入

  • Hex编码绕过
    1
    SELECT * FROM users WHERE username = 0x61646D696E
  • URL编码绕过
    1
    UNION%20SELECT%201,2,3
  • 双重编码绕过
    1
    %2527%20OR%201%3D1--

7.4 文件包含漏洞

  • 编码路径遍历
    1
    ?file=..%2f..%2fetc%2fpasswd
  • 混合编码绕过
    1
    ?file=%252e%252e%252fetc%252fpasswd

八、编码工具推荐

8.1 在线工具

  • CyberChef:多功能编码/解码工具,支持超过200种操作
  • URLDecoder:专门用于URL编码解码
  • Base64decode:Base64编码解码
  • Unicode Escape:Unicode编码转换
  • XSS Encoder:专门用于XSS测试的编码工具

8.2 浏览器插件

  • HackBar:多功能安全测试工具,包含编码功能
  • Burp Suite Encoder:集成在Burp Suite中的编码工具
  • XSStrike:XSS检测工具,包含编码绕过功能

8.3 命令行工具

  • Python
    1
    2
    import urllib.parse
    urllib.parse.quote("test string")
  • JavaScript (Node.js)
    1
    encodeURIComponent("test string")
  • Linux命令
    1
    echo "Hello" | base64

九、防御建议

9.1 安全编码实践

  • 输入规范化:在处理前将输入转换为标准格式
  • 多层解码检查:避免仅进行单次解码
  • 白名单过滤:允许已知安全的字符,拒绝其他所有
  • 内容安全策略(CSP):限制脚本执行来源

9.2 WAF配置建议

  • 多层解码:确保WAF能够处理多层编码
  • 正则表达式优化:使用更智能的模式匹配
  • 上下文感知:根据不同输入位置应用不同规则
  • 定期更新规则:跟上新的编码绕过技术

9.3 安全开发建议

  • 使用安全API:避免手动处理编码/解码
  • 输出编码:在输出到不同上下文时使用适当编码
  • 安全库使用:使用经过验证的安全库处理用户输入
  • 安全测试:包括对编码绕过技术的测试

十、编码绕过案例研究

10.1 经典XSS双重编码绕过

  • 原始payload<script>alert(1)</script>
  • 双重URL编码
    1
    %253Cscript%253Ealert(1)%253C%252Fscript%253E
  • WAF行为:仅进行一次URL解码,得到<script>alert(1)</script>,仍然被过滤
  • 浏览器行为:进行两次URL解码,最终执行脚本

10.2 SQL注入Hex编码绕过

  • 原始payload' OR 1=1 --
  • Hex编码0x27204F5220313D31202D2D
  • 绕过原理:某些WAF无法识别Hex编码的SQL注入

10.3 UTF-7 XSS攻击

  • payload+ADw-script+AD4-alert(1)+ADw-/script+AD4-
  • 绕过原理:旧版IE将UTF-7内容视为脚本执行
  • 现代影响:虽然现代浏览器已弃用UTF-7,但某些中间件可能仍存在风险

一、端口基础概念

1. 端口的作用

  • 一台拥有IP地址的主机可以提供多种网络服务(如Web、FTP、SMTP等)
  • 通过”IP地址+端口号”的组合来区分不同的网络服务
  • 解决了IP地址与网络服务之间”一对多”的关系问题

2. 端口类型

类型 描述 特点 常见端口示例
TCP端口 传输控制协议端口 需要在客户端和服务器之间建立连接,提供可靠的数据传输 21(FTP)、23(Telnet)、25(SMTP)、80(HTTP)
UDP端口 用户数据包协议端口 无需建立连接,传输速度快但安全性低 53(DNS)、161(SNMP)、8000(QQ)

重要提示:TCP和UDP端口号相互独立,例如TCP 235端口与UDP 235端口不冲突

3. 端口范围划分

范围 类型 描述 用途
0-1023 预留端口 由操作系统保留,仅超级用户特权应用可使用 系统关键服务
1024-49151 登记端口 一般服务器程序使用的端口范围 自定义服务端口
49152-65535 临时端口 客户端进程动态选择的端口 临时通信使用

二、TCP协议详解

1. TCP三次握手建立连接

步骤 方向 数据包 状态变化 说明
第一次 客户端→服务器 [SYN], Seq = x 客户端: SYN_SEND 客户端发送SYN包,初始化序列号
第二次 服务器→客户端 [SYN,ACK], Seq = y, ACK = x+1 服务器: SYN_RCVD 服务器确认并发送SYN包
第三次 客户端→服务器 [ACK], ACK = y+1 客户端: ESTABLISHED
服务器: ESTABLISHED
客户端确认,连接建立

2. TCP四次挥手断开连接

步骤 方向 数据包 状态变化 说明
第一次 客户端→服务器 [FIN] 客户端: FIN_WAIT_1 客户端请求断开连接
第二次 服务器→客户端 [ACK] 服务器: CLOSE_WAIT
客户端: FIN_WAIT_2
服务器确认收到断开请求
第三次 服务器→客户端 [FIN] 服务器: LAST_ACK
客户端: TIME_WAIT
服务器发送断开请求
第四次 客户端→服务器 [ACK] 服务器: CLOSED
客户端: TIME_WAIT
客户端确认,进入等待期

3. TCP关键状态解析

状态 说明 注意事项
LISTENING 服务正在监听连接请求 FTP服务启动后首先处于此状态
ESTABLISHED 连接已建立,正在通信 表示两台机器正在正常通信
CLOSE_WAIT 对方已关闭连接,等待本端关闭 本端需调用close()关闭连接
TIME_WAIT 本端主动关闭连接,等待2MSL时间 持续2倍最大分段生存期,防止旧连接影响新连接

三、端口检测与管理

1. 常用端口检测命令

Windows系统

1
2
netstat -ano
netstat -ano | findstr "80"

Linux系统

1
2
3
netstat -tuln
lsof -i :80
ss -tuln

2. netstat命令详解

参数 功能 说明
-a 显示所有连接和侦听端口 包含已建立和等待连接
-b 显示创建连接的可执行程序 可能需要管理员权限
-n 以数字形式显示地址和端口 避免DNS解析,显示更快
-o 显示进程ID(PID) 用于关联进程与端口
-p proto 指定协议 proto: TCP, UDP, TCPv6, UDPv6
-r 显示路由表 显示网络路由信息
-s 按协议统计 显示各协议的统计信息

四、常见端口参考表

1. Web服务类端口

端口 服务 协议 用途 安全风险
80 HTTP TCP Web服务 XSS、SQL注入等Web漏洞
80-89 HTTP TCP 备用Web服务 同上
443 HTTPS TCP 加密Web服务 SSL/TLS漏洞、心脏滴血
8000-9090 HTTP TCP 备用Web服务 管理后台常在此范围
8080 HTTP TCP Tomcat/WDCP 默认弱口令
8089 HTTP TCP JBOSS 默认弱口令、反序列化
8888 HTTP TCP amh/LuManager 主机管理系统

2. 数据库服务类端口

端口 服务 协议 用途 安全风险
1433 MSSQL TCP Microsoft SQL Server 弱口令、SQL注入
1521 Oracle TCP Oracle数据库 弱口令、默认账户
3306 MySQL TCP MySQL数据库 弱口令、未授权访问
5432 PostgreSQL TCP PostgreSQL数据库 弱口令、配置错误
27017-27018 MongoDB TCP MongoDB数据库 未授权访问

3. 特殊服务类端口

端口 服务 协议 用途 安全风险
445 SMB TCP Windows文件共享 永恒之蓝等漏洞
6379 Redis TCP Redis缓存服务 未授权访问、RCE
873 Rsync TCP 文件同步服务 未授权访问
9200-9300 Elasticsearch TCP 搜索引擎服务 命令执行漏洞
11211 Memcached TCP 缓存服务 未授权访问
5984 CouchDB TCP NoSQL数据库 未授权访问
7001-7002 WebLogic TCP Java应用服务器 默认弱口令、反序列化

4. 常用管理类端口

端口 服务 协议 用途 安全风险
21 FTP TCP 文件传输 弱口令、匿名访问
22 SSH TCP 安全远程登录 弱口令、暴力破解
23 Telnet TCP 远程登录 明文传输、弱口令
3389 RDP TCP Windows远程桌面 弱口令、爆破
5900 VNC TCP 远程桌面控制 弱口令、未授权访问
10000 Webmin TCP 服务器管理 默认弱口令
50000 SAP TCP 企业资源计划 命令执行漏洞

5. 其他重要端口

端口 服务 协议 用途 安全风险
53 DNS UDP/TCP 域名解析 DNS劫持、缓存投毒
161 SNMP UDP 网络管理 信息泄露、未授权访问
389 LDAP TCP 目录服务 信息泄露
4440 Rundeck TCP 任务调度 未授权访问
6082 Varnish TCP Web加速 未授权访问、网站篡改
8649 Ganglia TCP 监控系统 信息泄露

五、端口安全建议

  1. 最小化原则

    • 只开放必要的端口
    • 关闭非必要服务
  2. 安全配置

    • 为管理端口设置强密码
    • 限制访问IP范围
    • 使用防火墙规则控制访问
  3. 定期检测

    • 使用端口扫描工具定期检查开放端口
    • 及时修补已知漏洞
    • 监控异常连接
  4. 特殊端口处理

    • Web管理后台避免使用默认端口
    • 敏感服务(如数据库)不应直接暴露在公网
    • 使用跳板机访问内部服务
  5. 渗透测试注意事项

    • 在授权范围内进行端口扫描
    • 避免对关键服务进行暴力测试
    • 遵守相关法律法规和道德规范

六、端口扫描工具推荐

工具 类型 特点 适用场景
Nmap 命令行 功能强大,支持多种扫描技术 专业渗透测试
Masscan 命令行 速度极快,可全网扫描 大规模网络扫描
Zenmap GUI Nmap的图形界面,易于使用 初学者使用
Angry IP Scanner GUI 简单易用,跨平台 快速网络扫描
Netcat 命令行 “网络瑞士军刀”,多功能 网络调试与测试

七、端口安全加固示例

1. 关闭不必要端口(Linux)

1
2
3
4
5
6
7
8
9
# 查看监听端口
sudo netstat -tuln

# 关闭特定端口(例如111端口)
sudo ufw deny 111

# 或者使用iptables
sudo iptables -A INPUT -p tcp --dport 111 -j DROP
sudo iptables -A INPUT -p udp --dport 111 -j DROP

2. 限制访问IP(Windows)

  1. 打开”高级安全Windows防火墙”
  2. 选择”入站规则”
  3. 找到对应服务的规则
  4. 右键选择”属性”
  5. 在”作用域”选项卡中,添加允许的IP地址

3. 服务安全配置

  • SSH服务

    • 禁用root远程登录
    • 使用密钥认证代替密码
    • 限制访问IP范围
  • Web服务

    • 使用HTTPS代替HTTP
    • 配置安全头(如CSP、HSTS)
    • 限制管理后台访问IP

1. 系统基本操作

系统控制

  • reboot - 重启系统
  • shutdown -h now - 立即关闭系统
  • shutdown -h hours:minutes & - 按预定时间关闭系统
  • shutdown -c - 取消预定的关机操作
  • shutdown -r now - 立即重启系统
  • init 0 - 关闭系统
  • telinit 0 - 关闭系统
  • clear - 清屏

环境变量

  • env - 显示当前所有环境变量
  • echo $PATH - 查看PATH环境变量
  • echo $JAVA_HOME - 查看JAVA_HOME环境变量
  • echo $PATH | grep jdk - 检查PATH中是否包含JDK路径

用户切换与管理

  • su -l - 切换登录用户
  • su - root - 切换到root用户
  • su 用户名 - 切换到普通用户
  • logout - 注销当前用户
  • passwd - 修改当前用户密码
  • passwd root - 修改root用户密码

历史命令

  • history - 查看历史命令
  • !! - 重复上一条命令
  • !n - 重复第n条命令

2. 文件和目录操作

目录导航

  • pwd - 显示当前工作路径
  • cd .. - 返回上一级目录
  • cd ../.. - 返回上级两级目录
  • cd - - 返回上次所在的目录
  • cd 文件夹名 - 进入指定文件夹
  • cd ~ - 进入当前用户主目录

文件操作

  • touch 文件名 - 创建空文件
  • cat 文件名 - 读取文件内容
  • mkdir 文件名 - 创建文件夹
  • mkdir -p 文件夹 - 递归创建文件夹
  • mkdir 文件名 文件名 - 创建多个文件夹
  • cp 源文件 目标文件 - 复制文件
  • cp -r 源文件夹 目标文件夹 - 递归复制文件夹
  • mv 旧文件名 新文件名 - 移动/重命名文件
  • rm 文件 - 删除文件
  • rm -f 文件 - 强制删除文件
  • rm 文件夹名 - 删除文件夹
  • rm -rf 文件/文件夹 - 强制递归删除文件/文件夹及其内容

文件搜索

  • find / -name file1 - 从根目录开始搜索文件和目录
  • find / -user user1 - 搜索属于指定用户的文件和目录
  • find /home/user1 -name *.bin - 在指定目录中搜索特定扩展名的文件
  • find /usr/bin -type f -atime +100 - 搜索过去100天未被使用的执行文件
  • find /usr/bin -type f -mtime -10 - 搜索10天内被修改过的文件
  • find . -name '.php' -mmin -30 - 查找最近30分钟修改的当前目录下的.php文件
  • find . -name '*.inc' -mtime 0 -ls - 查找最近24小时修改的文件并列出详细信息
  • find / -name *.rpm -exec chmod 755 '{}' \; - 搜索并修改权限
  • find / -xdev -name *.rpm - 搜索忽略可移动设备
  • locate *.ps - 快速查找以.ps结尾的文件(需先运行updatedb
  • whereis halt - 显示二进制文件、源码或man的位置
  • which halt - 显示二进制文件或可执行文件的完整路径
  • find / -name moonsec 2>/dev/null - 搜索文件/文件夹并屏蔽错误信息

3. 系统信息

硬件信息

  • arch - 显示机器的处理器架构
  • uname -m - 显示机器的处理器架构
  • cat /proc/cpuinfo - 显示CPU信息
  • cat /proc/mounts - 显示已加载的文件系统
  • cat /proc/net/dev - 显示网络适配器及统计

操作系统信息

  • uname -r - 显示正在使用的内核版本
  • cat /proc/version - 显示内核版本
  • ver - 查看操作系统版本
  • systeminfo - 查看计算机详细信息(Windows命令,Kali中不适用)

磁盘和内存信息

  • df - 显示磁盘空间使用情况(默认不带单位)
  • df -h - 友好显示磁盘空间使用情况(带单位)
  • free - 显示内存使用情况(默认以KB为单位)
  • free -m - 以MB为单位显示内存使用情况
  • free -h - 友好显示内存使用情况(自动选择合适单位)

4. 用户和群组管理

用户管理

  • useradd moonsec - 创建用户
  • useradd -r -m -s /bin/bash moonsec - 创建系统用户并指定shell
    • -r - 建立系统账号
    • -m - 自动建立用户的登录目录
    • -s /bin/bash - 指定用户登录后使用的shell
  • passwd moonsec - 设置用户密码
  • usermod -a -G moontea k1 - 将用户k1添加到其他用户组

群组管理

  • groupadd group_name - 创建新用户组
  • groupdel group_name - 删除用户组
  • cat /etc/group | grep group_name - 查看特定群组信息

5. 进程管理

进程查看

  • top - 实时查看系统进程和资源使用情况
  • ps -ef - 查看所有进程信息
  • ps -ef | grep tomcat - 查找指定进程
  • query user - 查看当前在线的用户(Windows命令,Kali中不适用)

进程控制

  • kill -9 PID - 强制杀死指定PID的进程
  • taskkill /pid PID数 - 终止指定PID的进程(Windows命令,Kali中不适用)

6. 网络相关命令

网络配置

  • ifconfig - 查看和配置网络接口信息
  • iwconfig - 配置或获取无线网络设备信息
  • route print - 显示IP路由表(Windows命令)
  • arp - 查看和处理ARP缓存
  • arp -a - 显示所有ARP缓存信息
  • nbtstat -A ip - 查看对方最近登录的用户名(Windows命令)

网络诊断

  • ping www.baidu.com - 测试网络连接
  • ping www.baidu.com -c 3 - 执行3次ping测试
  • netstat -a - 查看所有端口
  • netstat -an - 查看端口的网络连接情况
  • netstat -o - 查看所有网络连接的进程ID
  • netstat -ano | findstr ":80" - 查找包含”80”的连接(Windows命令)
  • netstat -v - 查看正在进行的工作
  • netstat -p 协议名 - 查看某协议使用情况
  • netstat -s - 查看所有协议使用情况
  • netstat -lntup - 查看所有TCP和UDP端口
  • nslookup - 查询DNS记录
  • nslookup -qt=type domain - 查询特定类型的DNS记录
  • traceroute - 检测数据包到达目标主机经过的网关数量
  • telnet - 远程登录(常用于端口检测)
  • finger username @host - 查看最近登录的用户信息

网络服务

  • http协议 - 使用curl和wget进行HTTP操作
  • curl - 传输数据的命令行工具
  • wget - 从网络下载文件

7. 服务管理

服务控制

  • /etc/init.d/apache2 start - 启动Apache2服务
  • /etc/init.d/apache2 restart - 重启Apache2服务
  • /etc/init.d/apache2 stop - 停止Apache2服务
  • /etc/init.d/network start - 启动网络服务
  • service networking restart - 重启网络服务
  • systemctl restart networking - 使用systemctl重启网络服务

服务配置

  • /etc/rc.d/rc.local - 开机自启动脚本配置文件
  • echo 1 > /proc/sys/net/ipv4/ip_forward - 开启路由转发

8. 包管理

Debian/Ubuntu包管理

  • apt-get update - 更新软件包列表
  • apt-get upgrade - 升级已安装的软件包
  • apt-get dist-upgrade - 智能升级,处理依赖关系变化
  • apt-get install package - 安装软件包
  • apt-get install python-模块名 - 安装Python模块
  • apt-get autoremove --purge 软件名 - 删除包及其依赖和配置文件
  • dpkg -i package.deb - 安装/更新deb包
  • dpkg -r package_name - 从系统删除deb包
  • dpkg -l - 显示所有已安装的deb包
  • dpkg -l | grep httpd - 显示包含特定名称的deb包
  • dpkg -s package_name - 获取已安装包的信息
  • dpkg -L package_name - 显示已安装包提供的文件列表

压缩与解压

  • bzip2 file1 - 压缩文件
  • gunzip file1.gz - 解压gzip文件
  • gzip file1 - 压缩文件
  • gzip -9 file1 - 最大程度压缩
  • rar a file1.rar test_file - 创建rar压缩包
  • rar x file1.rar - 解压rar包
  • unrar x file1.rar - 解压rar包
  • tar zcvf 压缩文件名 压缩文件 - 创建gzip压缩的tar包
  • tar zxvf 解压包名 - 解压gzip压缩的tar包
  • tar -jcvf 压缩文件名 - 创建bzip2压缩的tar包
  • tar jxvf 解压包名 - 解压bzip2压缩的tar包
  • zip -q -r 压缩文件名 目录 - 创建zip压缩包
  • unzip 压缩文件名 - 解压zip压缩包

9. 文本编辑

vi/vim编辑器

三种模式

  1. 命令模式 - 控制光标移动,进行删除、复制等操作
  2. 插入模式 - 输入文本内容
  3. 底行模式 - 保存、退出、搜索等

基本操作

  • vi filename - 打开或新建文件,光标置于第一行首
  • vi +n filename - 打开文件,光标置于第n行首
  • vi + filename - 打开文件,光标置于最后一行首
  • i - 在当前位置前插入
  • I - 在当前行首插入
  • a - 在当前位置后插入
  • A - 在当前行尾插入
  • o - 在当前行之后插入一行
  • O - 在当前行之前插入一行
  • dd - 删除当前行
  • 2dd - 删除2行
  • yy - 复制当前行
  • nyy - 复制当前行及之后n行
  • p - 在当前光标后粘贴
  • u - 撤销操作

查找与替换

  • /text - 向下查找text
  • ?text - 向上查找text
  • n - 查找下一个匹配
  • N - 查找上一个匹配

退出命令

  • :wq - 保存并退出
  • :q! - 强制退出忽略更改
  • :e! - 放弃修改并重新打开文件
  • :w - 保存修改
  • set numberset nu - 显示行号
  • :n - 跳转到第n行

10. 其他实用命令

防火墙管理

  • iptables -L - 查看防火墙规则
  • iptables -F - 清除防火墙规则
  • /etc/init.d/iptables stop - 停止iptables防火墙
  • service iptables stop - 停止iptables防火墙
  • ufw disable - 禁用UFW防火墙
  • ufw enable - 启用UFW防火墙

网络共享

  • mount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share - 挂载Windows网络共享

网卡配置

  • /etc/network/interfaces - 网卡配置文件路径
  • ifconfig eth0 192.168.0.33 - 设置临时IP地址

DNS配置

  • /etc/resolv.conf - DNS配置文件路径
  • nameserver 114.114.114.114 - 设置DNS服务器

系统工具

  • wmic product > ins.txt - 查看安装软件信息(Windows命令)
  • systeminfo - 查看系统信息(Windows命令)
  • whoami - 查看当前用户及权限(Windows命令)
  • whoami /all - 查看当前用户详细权限信息(Windows命令)

注:本笔记中包含部分Windows命令,已特别标注。Kali Linux主要使用Linux命令,部分命令可能需要root权限才能执行。

网络配置与信息

基本网络信息

  • ifconfig /all - 获取域名、IP地址、DHCP服务器、网关、MAC地址、主机名
  • net config workstation - 查看域名、机器名等
  • route print - 显示IP路由,主要显示网络地址、子网掩码、网关地址和接口地址
  • arp - 查看和处理ARP缓存,ARP是名字解析的意思,负责把一个IP解析成一个物理性的MAC地址
  • arp -a - 查看ARP缓存信息
  • nslookup - IP地址侦测器

网络连接状态

  • netstat -a - 查看开启了哪些端口(常用netstat -an
  • netstat -n - 查看端口的网络连接情况(常用netstat -an
  • netstat -o - 查看所有网络连接的进程ID(PID)
  • netstat -ano | findstr ":80" - 查找包含”80”的连接(最常用)
  • netstat -v - 查看正在进行的工作
  • netstat -p 协议名 - 查看某协议使用情况,如:netstat -p tcp
  • netstat -s - 查看所有协议使用情况

域网络信息

  • net time /domain - 查看域名、时间
  • net view /domain - 查看域内所有共享
  • net view ip - 查看对方局域网内开启了哪些共享
  • nbtstat -A ip - 查看对方最近登录的用户名(需136-139端口开放,参数-A要大写)

用户与组管理

用户管理

  • net user 用户名 密码 /add - 创建新用户
  • net user 用户名 /del - 删除用户
  • net user guest /active:yes - 激活guest账户
  • net user - 查看所有账户
  • net user 账户名 - 查看指定账户信息
  • net user /domain - 查看域内有哪些用户(Windows NT Workstation计算机上可用,可判断用户是否是域成员)
  • net user 用户名 /domain - 查看域内指定账户信息
  • query user - 查看当前在线的用户
  • whoami - 查看当前用户及权限
  • whoami /all - 查看当前用户的详细权限信息

组管理

  • net group /domain - 查看域中的组
  • net group "domain admins" /domain - 查看当前域的管理员
  • net localgroup - 查看所有本地组
  • net localgroup administrators - 查看administrators组中的用户
  • net localgroup administrators 用户名 /add - 将用户添加到管理员组

服务与进程管理

服务管理

  • net start - 查看已开启的服务
  • net start 服务名 - 启动指定服务
  • net stop 服务名 - 停止指定服务

进程管理

  • tasklist - 查看当前进程
  • tasklist /svc - 查看当前计算机进程及对应服务
  • taskkill /pid PID数 - 终止指定PID的进程
  • netstat -ano - 查看当前计算机进程情况

系统信息

  • systeminfo - 查看计算机信息(版本、位数、补丁情况)
  • ver - 查看操作系统版本

文件共享与远程访问

共享管理

  • net share - 查看本地开启的共享
  • net share ipc$ - 开启ipc$共享
  • net share ipc$ /del - 删除ipc$共享
  • net share c$ /del - 删除C:共享
  • \\IP\c - 访问默认共享c盘,如:\\192.168.0.108\c

远程连接

  • net use \\目标IP\ipc$ 密码 /u:用户名 - 连接目标机器
  • at \\目标IP 21:31 c:\server.exe - 在指定时间启动应用(适用于低版本Windows)
  • wmic /node:"目标IP" /password:"123456" /user:"admin" - 连接目标机器
  • psexec.exe \\目标IP -u username -p password -s cmd - 在目标机器上执行cmd
  • finger username @host - 查看最近登录的用户

域控制器相关

  • dsquery server - 查看所有域控制器
  • dsquery subnet - 查看域内子网
  • dsquery group - 查看域内工作组
  • dsquery site - 查看域内站点

注册表与系统备份

  • reg save hklm\sam sam.hive - 导出用户组信息、权限配置
  • reg save hklm\system system.hive - 导出SYSKEY

其他实用命令

  • wmic product > ins.txt - 查看安装软件及版本路径等信息,输出到ins.txt文件(位于用户目录下)

注:以上命令基于Windows系统,部分命令可能需要管理员权限才能执行。

0%