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

View File

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