docs(代码解释): 更新代码解释文档

- 重新组织代码解释结构,按功能模块划分
- 补充了部分函数和操作的详细说明
- 调整了部分描述,使其更加准确和易于理解
This commit is contained in:
fly6516 2025-01-06 14:49:56 +08:00
parent 300f42fd8f
commit 0a2ace4916
2 changed files with 273 additions and 135 deletions

74
main.py
View File

@ -8,6 +8,12 @@ import re
# 获取本机信息 # 获取本机信息
def get_local_info(): def get_local_info():
"""
获取本机的主机名MAC地址和IP地址列表
Returns:
tuple: 包含主机名MAC地址和IP地址列表的元组
"""
hostname = socket.gethostname() hostname = socket.gethostname()
local_ips = socket.gethostbyname_ex(hostname)[-1] local_ips = socket.gethostbyname_ex(hostname)[-1]
mac = ':'.join(re.findall('..', '%012x' % uuid.getnode())) mac = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
@ -15,16 +21,46 @@ def get_local_info():
# 验证 IP 地址或域名 # 验证 IP 地址或域名
def validate_ip_or_domain(value): def validate_ip_or_domain(value):
"""
验证输入值是否为有效的IP地址或域名
Args:
value (str): 待验证的IP地址或域名
Returns:
bool: 如果输入值有效返回True否则返回False
"""
ip_pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$') ip_pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$')
domain_pattern = re.compile(r'^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$') domain_pattern = re.compile(r'^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$')
return ip_pattern.match(value) or domain_pattern.match(value) return ip_pattern.match(value) or domain_pattern.match(value)
# 验证端口号 # 验证端口号
def validate_port(port): def validate_port(port):
"""
验证输入的端口号是否有效
Args:
port (str): 待验证的端口号
Returns:
bool: 如果端口号有效返回True否则返回False
"""
return port.isdigit() and 1 <= int(port) <= 65535 return port.isdigit() and 1 <= int(port) <= 65535
# 构造 IP 头部 # 构造 IP 头部
def construct_ip_header(src_ip, dest_ip, payload_length, protocol): def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
"""
构造IP头部
Args:
src_ip (str): 源IP地址
dest_ip (str): 目标IP地址
payload_length (int): 负载长度
protocol (int): 协议号
Returns:
bytes: 构造的IP头部
"""
version = 4 version = 4
ihl = 5 ihl = 5
tos = 0 tos = 0
@ -45,6 +81,12 @@ def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
# 构造 ICMP 报文 # 构造 ICMP 报文
def construct_icmp(): def construct_icmp():
"""
构造ICMP报文
Returns:
bytes: 构造的ICMP报文
"""
icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1)
data = b'Ping' data = b'Ping'
checksum = calculate_checksum(icmp_header + data) checksum = calculate_checksum(icmp_header + data)
@ -53,11 +95,31 @@ def construct_icmp():
# 构造 UDP 报文 # 构造 UDP 报文
def construct_udp(src_port, dest_port): def construct_udp(src_port, dest_port):
"""
构造UDP报文
Args:
src_port (int): 源端口号
dest_port (int): 目标端口号
Returns:
bytes: 构造的UDP报文
"""
udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0) udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
return udp_header return udp_header
# 构造 TCP 报文SYN # 构造 TCP 报文SYN
def construct_tcp(src_port, dest_port): def construct_tcp(src_port, dest_port):
"""
构造TCP报文SYN
Args:
src_port (int): 源端口号
dest_port (int): 目标端口号
Returns:
bytes: 构造的TCP报文
"""
seq = 0 seq = 0
ack_seq = 0 ack_seq = 0
offset = 5 offset = 5
@ -75,6 +137,15 @@ def construct_tcp(src_port, dest_port):
# 计算校验和 # 计算校验和
def calculate_checksum(data): def calculate_checksum(data):
"""
计算校验和
Args:
data (bytes): 待计算校验和的数据
Returns:
int: 计算得到的校验和
"""
checksum = 0 checksum = 0
n = len(data) n = len(data)
for i in range(0, n - 1, 2): for i in range(0, n - 1, 2):
@ -88,6 +159,9 @@ def calculate_checksum(data):
# 发送报文 # 发送报文
def send_packet(): def send_packet():
"""
发送报文
"""
try: try:
src_ip = ip_combobox.get() src_ip = ip_combobox.get()
dest_host = dest_entry.get() dest_host = dest_entry.get()

View File

@ -1,48 +1,90 @@
下面是任务 2 代码的逐行详细解释:
---
该代码是一个使用 `Tkinter` 库制作的网络通信软件通过图形界面GUI允许用户发送不同类型的网络报文如 ICMP、UDP、TCP。以下是逐行解释代码
### 导入必要的库
```python ```python
import tkinter as tk import tkinter as tk
from tkinter import messagebox from tkinter import ttk, messagebox
import socket import socket
import struct import struct
import re
import time import time
import uuid
import re
``` ```
1. **`tkinter`**Python 的内置 GUI 库,用于创建图形用户界面。 - `tkinter` 用于创建 GUI 界面。
2. **`messagebox`**`tkinter` 模块中的组件,用于显示错误或提示信息。 - `ttk` 提供更丰富的部件,如下拉框(`Combobox`)。
3. **`socket`**:提供网络编程接口,用于构造和发送报文。 - `messagebox` 用于显示消息框。
4. **`struct`**:用于将数据打包成特定格式(二进制结构),例如构造报文头。 - `socket` 用于创建网络连接。
5. **`re`**:正则表达式模块,用于验证 IP 地址的格式。 - `struct` 用于处理二进制数据的打包和解包(比如构造报文头)。
6. **`time`**:用于记录和计算时间,测量报文往返的时间。 - `time``uuid` 用于获取当前时间戳和生成唯一的 MAC 地址。
- `re` 用于正则表达式操作,主要用于验证 IP 地址或域名的格式。
--- ### 获取本机信息
```python
def get_local_info():
hostname = socket.gethostname()
local_ips = socket.gethostbyname_ex(hostname)[-1]
mac = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
return hostname, mac, local_ips
```
- `gethostname()` 获取当前计算机的主机名。
- `gethostbyname_ex(hostname)` 返回主机名对应的 IP 地址列表,选择最后一个(所有可用的 IP 地址)。
- 使用 `uuid.getnode()` 获取 MAC 地址,使用正则表达式将其格式化为 `XX:XX:XX:XX:XX:XX` 的格式。
### 验证 IP 地址或域名
```python
def validate_ip_or_domain(value):
ip_pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$')
domain_pattern = re.compile(r'^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$')
return ip_pattern.match(value) or domain_pattern.match(value)
```
- 使用正则表达式验证输入的值是否是有效的 IP 地址或域名。
### 验证端口号
```python
def validate_port(port):
return port.isdigit() and 1 <= int(port) <= 65535
```
- 确保端口号是一个数字并在合法范围1-65535内。
### 构造 IP 头部
```python
def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
version = 4
ihl = 5
tos = 0
total_length = 20 + payload_length
identification = 54321
flags_offset = 0
ttl = 64
header_checksum = 0
src_ip_bytes = socket.inet_aton(src_ip)
dest_ip_bytes = socket.inet_aton(dest_ip)
ip_header = struct.pack('!BBHHHBBH4s4s',
(version << 4) + ihl, tos, total_length,
identification, flags_offset, ttl, protocol,
header_checksum, src_ip_bytes, dest_ip_bytes)
return ip_header
```
- 构造一个 IPv4 头部,使用 `struct.pack()` 将数据打包为二进制格式。
- `socket.inet_aton()` 用于将 IP 地址转换为二进制格式。
- 该函数生成一个标准的 IPv4 头部,包含源 IP、目的 IP 和协议类型等信息。
### 构造 ICMP 报文 ### 构造 ICMP 报文
```python ```python
def construct_icmp(): def construct_icmp():
icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1)
checksum = ~sum(icmp_header) & 0xFFFF data = b'Ping'
checksum = calculate_checksum(icmp_header + data)
icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1)
return icmp_header return icmp_header + data
``` ```
1. **构造 ICMP 报文的头部** - ICMP 报文的类型为 8Echo 请求)。
- 使用 `struct.pack` 打包数据为二进制格式。 - 使用 `struct.pack()` 构造 ICMP 头部,并计算校验和。
- `!` 表示网络字节序(大端模式)。 - 校验和通过 `calculate_checksum()` 函数来计算,确保报文的完整性。
- `BBHHH` 定义字段类型2 个 8 位整数3 个 16 位整数。
- **8** 表示 ICMP 类型8 = 回显请求)。
- **0** 表示代码字段。
- **0** 表示校验和(暂时填充 0
- **1, 1** 表示标识符和序列号。
2. **计算校验和**
- 使用 `~sum(...)` 计算报文的校验和并取反,确保数据完整性。
- 结果为 16 位,使用 `& 0xFFFF` 截断为无符号整数。
3. **重新打包报文**
- 将计算后的校验和填入头部。
4. **返回构造好的 ICMP 报文**
---
### 构造 UDP 报文 ### 构造 UDP 报文
```python ```python
@ -50,142 +92,164 @@ def construct_udp(src_port, dest_port):
udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0) udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
return udp_header return udp_header
``` ```
1. **打包 UDP 报文头** - 构造一个简单的 UDP 报文头,包含源端口、目标端口、长度和校验和。
- `HHHH` 表示 4 个 16 位整数(源端口、目标端口、长度、校验和)。
- **src_port**:源端口号。
- **dest_port**:目标端口号。
- **8**UDP 报文长度(仅头部时固定为 8 字节)。
- **0**:校验和(暂时不计算,系统会自动填充)。
2. **返回 UDP 报文头**
--- ### 构造 TCP 报文SYN
### 验证 IP 地址
```python ```python
def validate_ip(ip): def construct_tcp(src_port, dest_port):
pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$') seq = 0
return pattern.match(ip) is not None ack_seq = 0
``` offset = 5
1. **正则表达式验证** reserved = 0
- 匹配 IP 地址格式:四段数字,每段 0-255 范围。 flags = 0b000010 # SYN flag
- `^\d{1,3}`:每段由 1 到 3 位数字组成。 window = socket.htons(5840)
- `(\.\d{1,3}){3}`:重复三次的“点+数字”模式。 checksum = 0
2. **返回结果** urgent_ptr = 0
- 如果匹配成功,返回 `True`
- 如果失败,返回 `False`
--- tcp_header = struct.pack('!HHLLBBHHH',
src_port, dest_port, seq, ack_seq,
(offset << 4) + reserved, flags, window,
checksum, urgent_ptr)
return tcp_header
```
- 构造 TCP 头部,设置 SYN 标志位以启动连接。
- 包含源端口、目标端口、序列号等信息。
### 计算校验和
```python
def calculate_checksum(data):
checksum = 0
n = len(data)
for i in range(0, n - 1, 2):
chunk = (data[i] << 8) + data[i + 1]
checksum += chunk
if n % 2 == 1:
checksum += data[-1] << 8
checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)
return ~checksum & 0xFFFF
```
- 计算校验和,保证数据的完整性。
- 将数据按 2 字节分组求和,最终计算出一个 16 位的校验和。
### 发送报文 ### 发送报文
```python ```python
def send_packet(): def send_packet():
try: try:
target_ip = ip_entry.get() src_ip = ip_combobox.get()
target_port = int(port_entry.get()) dest_host = dest_entry.get()
dest_port = port_entry.get()
packet_type = var.get() packet_type = var.get()
if not validate_ip(target_ip):
raise ValueError("目标 IP 地址无效")
```
1. **获取用户输入**
- `ip_entry.get()`:从文本框中获取目标 IP 地址。
- `port_entry.get()`:从文本框中获取目标端口号并转为整数。
- `var.get()`:从单选框中获取选择的报文类型。
2. **验证 IP 地址**
- 使用 `validate_ip` 函数检查 IP 格式。
- 如果无效,抛出 `ValueError` 异常。
--- # 验证目标地址
if not validate_ip_or_domain(dest_host):
raise ValueError("目标地址无效,必须为有效的 IP 或域名")
```python # 验证端口号
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP if packet_type == "ICMP" else socket.IPPROTO_UDP) if not validate_port(dest_port):
``` raise ValueError("端口号无效必须是有效的正整数1-65535")
1. **创建套接字**
- `socket.AF_INET`:使用 IPv4 地址。
- `socket.SOCK_RAW`:使用原始套接字,允许手动构造报文。
- `socket.IPPROTO_ICMP`ICMP 协议。
- `socket.IPPROTO_UDP`UDP 协议。
--- dest_ip = socket.gethostbyname(dest_host)
src_port = 12345
dest_port = int(dest_port)
```python # 构造报文并发送
if packet_type == "ICMP": if packet_type == "ICMP":
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
packet = construct_icmp() packet = construct_icmp()
elif packet_type == "UDP": elif packet_type == "UDP":
packet = construct_udp(12345, target_port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
``` packet = construct_udp(src_port, dest_port)
1. **选择报文类型** elif packet_type == "TCP":
- 如果选择 ICMP则调用 `construct_icmp` 构造报文。 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- 如果选择 UDP则调用 `construct_udp`,源端口固定为 12345。 sock.connect((dest_ip, dest_port))
packet = b"Hello, TCP!" # TCP 是面向连接的,这里发送简单的字符串
sock.sendall(packet)
sock.close()
result.set(f"TCP 连接成功,数据已发送!")
return # 结束函数
--- # 发送报文
sock.sendto(packet, (dest_ip, dest_port))
```python result.set(f"报文发送成功!类型: {packet_type}")
start_time = time.time() except socket.gaierror:
sock.sendto(packet, (target_ip, target_port)) messagebox.showerror("网络错误", "无法解析目标地址,请检查输入的 IP 或域名是否正确。")
end_time = time.time() except socket.error as e:
``` messagebox.showerror("网络错误", f"网络错误: {e.strerror}")
1. **发送报文并测量时间** except ValueError as e:
- 记录发送开始时间 `start_time` messagebox.showerror("输入错误", f"输入无效: {e}")
- 使用 `sendto` 方法将报文发送到目标地址和端口。
- 记录发送结束时间 `end_time`
---
```python
result.set(f"报文发送成功!类型: {packet_type}, 往返时间: {end_time - start_time:.2f} 秒")
except Exception as e: except Exception as e:
messagebox.showerror("错误", f"报文发送失败: {e}") messagebox.showerror("未知错误", f"发送报文时发生未知错误: {e}")
``` ```
1. **显示结果** - 根据用户选择的目标地址、端口号和报文类型,构造并发送相应的网络报文。
- 使用 `result.set` 更新界面上的结果显示。 - 对地址和端口进行验证若无误则根据选择的报文类型ICMP、UDP、TCP构造报文并发送。
- 如果发送成功,显示报文类型和往返时间。 - 如果是 TCP 报文,使用 `socket.SOCK_STREAM` 进行连接并发送数据。
2. **错误处理** - 若发送成功,通过 `messagebox.showerror()` 显示错误信息。
- 捕获异常并通过 `messagebox` 提示错误信息。
--- ### GUI 界面设计
### GUI 界面
```python ```python
window = tk.Tk() window = tk.Tk()
window.title("网络通信软件") window.title("网络通信软件")
```
- 创建一个 Tkinter 窗口,设置标题为“网络通信软件”。
tk.Label(window, text="目标 IP 地址:").grid(row=0, column=0) ```python
ip_entry = tk.Entry(window) hostname, mac, local_ips = get_local_info()
ip_entry.grid(row=0, column=1) ```
- 获取本机的主机名、MAC 地址和可用的 IP 地址。
tk.Label(window, text="目标端口号:").grid(row=1, column=0) ```python
tk.Label(window, text="本机信息:").grid(row=0, column=0)
info_text = tk.Text(window, height=4, width=40)
info_text.grid(row=0, column=1, columnspan=2)
info_text.insert(tk.END, f"主机名: {hostname}\nMAC 地址: {mac}\n可用 IP: {', '.join(local_ips)}")
info_text.config(state=tk.DISABLED)
```
- 显示本机的相关信息如主机名、MAC 地址和可用 IP 地址。
```python
tk.Label(window, text="本机 IP 地址:").grid(row=1, column=0)
ip_combobox = ttk.Combobox(window, values=local_ips)
ip_combobox.grid(row=1, column=1)
ip_combobox.current(0)
```
- 提供一个下拉框,显示本机的 IP 地址供用户选择。
```python
tk.Label(window, text="目标地址:").grid(row=2, column=0)
dest_entry = tk.Entry(window)
dest_entry.grid(row=2, column=1)
```
- 提供一个文本框供用户输入目标地址。
```python
tk.Label(window, text="目标端口:").grid(row=3, column=0)
port_entry = tk.Entry(window) port_entry = tk.Entry(window)
port_entry.grid(row=1, column=1) port_entry.grid(row=3, column=1)
```
- 提供一个文本框供用户输入目标端口。
tk.Label(window, text="报文类型:").grid(row=2, column=0) ```python
tk.Label(window, text="报文类型:").grid(row=4, column=0)
var = tk.StringVar(value="ICMP") var = tk.StringVar(value="ICMP")
tk.Radiobutton(window, text="ICMP", variable=var, value="ICMP").grid(row=2, column=1) tk.Radiobutton(window, text="ICMP", variable=var, value="ICMP").grid(row=4, column=1)
tk.Radiobutton(window, text="UDP", variable=var, value="UDP").grid(row=2, column=2) tk.Radiobutton(window, text="UDP", variable=var, value="UDP").grid(row=4, column=2)
tk.Radiobutton(window, text="TCP", variable=var, value="TCP").grid(row=4, column=3)
```
- 提供单选按钮供用户选择报文类型ICMP、UDP、TCP
```python
result = tk.StringVar() result = tk.StringVar()
tk.Label(window, textvariable=result).grid(row=4, column=0, columnspan=3) tk.Label(window, textvariable=result).grid(row=6, column=0, columnspan=3)
```
- 显示报文发送结果。
tk.Button(window, text="发送报文", command=send_packet).grid(row=3, column=1) ```python
tk.Button(window, text="发送报文", command=send_packet).grid(row=5, column=1)
```
- 创建一个按钮,点击后调用 `send_packet()` 函数发送报文。
```python
window.mainloop() window.mainloop()
``` ```
1. **窗口初始化** - 启动 GUI 窗口的主循环,使窗口保持显示状态。
- 创建 `window` 窗口对象。
- 设置窗口标题为“网络通信软件”。
2. **界面布局**
- 使用 `grid` 方法设置控件布局,分别添加:
- 文本标签:提示用户输入目标 IP 和端口号。
- 输入框:用于输入目标 IP 和端口号。
- 单选框选择报文类型ICMP 或 UDP
- 按钮:点击后调用 `send_packet` 函数发送报文。
- 结果标签:显示发送结果或错误信息。
3. **事件循环**
- `window.mainloop()` 启动 GUI 主循环,等待用户交互。
---
### 总结
这段代码实现了一个功能完备的网络通信工具,具备以下特点:
- **GUI 界面**:直观的输入与反馈。
- **多协议支持**:可发送 ICMP 和 UDP 报文。
- **输入验证**:保证用户输入的有效性。
- **异常处理**:确保程序运行稳定。