From e8e2953750d939069cacb40179a4b0f6949d30b5 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Sun, 8 Dec 2024 23:15:32 +0800 Subject: [PATCH] update beta main --- .idea/.gitignore | 8 + .idea/Python-web-communicate.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + main.py | 76 +++++++ 代码解释.md | 191 ++++++++++++++++++ 7 files changed, 305 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/Python-web-communicate.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 main.py create mode 100644 代码解释.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Python-web-communicate.iml b/.idea/Python-web-communicate.iml new file mode 100644 index 0000000..8a32af1 --- /dev/null +++ b/.idea/Python-web-communicate.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..aa1fe6c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..9128c73 --- /dev/null +++ b/main.py @@ -0,0 +1,76 @@ +import tkinter as tk +from tkinter import messagebox +import socket +import struct +import re +import time + + +# 构造 ICMP 报文 +def construct_icmp(): + icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) + checksum = ~sum(icmp_header) & 0xFFFF + icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) + return icmp_header + + +# 构造 UDP 报文 +def construct_udp(src_port, dest_port): + udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0) + return udp_header + + +# 验证输入 +def validate_ip(ip): + pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$') + return pattern.match(ip) is not None + + +# 发送报文 +def send_packet(): + try: + target_ip = ip_entry.get() + target_port = int(port_entry.get()) + packet_type = var.get() + if not validate_ip(target_ip): + raise ValueError("目标 IP 地址无效") + + sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, + socket.IPPROTO_ICMP if packet_type == "ICMP" else socket.IPPROTO_UDP) + + if packet_type == "ICMP": + packet = construct_icmp() + elif packet_type == "UDP": + packet = construct_udp(12345, target_port) + + start_time = time.time() + sock.sendto(packet, (target_ip, target_port)) + end_time = time.time() + + result.set(f"报文发送成功!类型: {packet_type}, 往返时间: {end_time - start_time:.2f} 秒") + except Exception as e: + messagebox.showerror("错误", f"报文发送失败: {e}") + + +# GUI 界面设计 +window = tk.Tk() +window.title("网络通信软件") + +tk.Label(window, text="目标 IP 地址:").grid(row=0, column=0) +ip_entry = tk.Entry(window) +ip_entry.grid(row=0, column=1) + +tk.Label(window, text="目标端口号:").grid(row=1, column=0) +port_entry = tk.Entry(window) +port_entry.grid(row=1, column=1) + +tk.Label(window, text="报文类型:").grid(row=2, 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) + +result = tk.StringVar() +tk.Label(window, textvariable=result).grid(row=4, column=0, columnspan=3) + +tk.Button(window, text="发送报文", command=send_packet).grid(row=3, column=1) +window.mainloop() diff --git a/代码解释.md b/代码解释.md new file mode 100644 index 0000000..937d805 --- /dev/null +++ b/代码解释.md @@ -0,0 +1,191 @@ +下面是任务 2 代码的逐行详细解释: + +--- + +```python +import tkinter as tk +from tkinter import messagebox +import socket +import struct +import re +import time +``` +1. **`tkinter`**:Python 的内置 GUI 库,用于创建图形用户界面。 +2. **`messagebox`**:`tkinter` 模块中的组件,用于显示错误或提示信息。 +3. **`socket`**:提供网络编程接口,用于构造和发送报文。 +4. **`struct`**:用于将数据打包成特定格式(二进制结构),例如构造报文头。 +5. **`re`**:正则表达式模块,用于验证 IP 地址的格式。 +6. **`time`**:用于记录和计算时间,测量报文往返的时间。 + +--- + +### 构造 ICMP 报文 +```python +def construct_icmp(): + icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1) + checksum = ~sum(icmp_header) & 0xFFFF + icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1) + return icmp_header +``` +1. **构造 ICMP 报文的头部**: + - 使用 `struct.pack` 打包数据为二进制格式。 + - `!` 表示网络字节序(大端模式)。 + - `BBHHH` 定义字段类型:2 个 8 位整数,3 个 16 位整数。 + - **8** 表示 ICMP 类型(8 = 回显请求)。 + - **0** 表示代码字段。 + - **0** 表示校验和(暂时填充 0)。 + - **1, 1** 表示标识符和序列号。 +2. **计算校验和**: + - 使用 `~sum(...)` 计算报文的校验和并取反,确保数据完整性。 + - 结果为 16 位,使用 `& 0xFFFF` 截断为无符号整数。 +3. **重新打包报文**: + - 将计算后的校验和填入头部。 +4. **返回构造好的 ICMP 报文**。 + +--- + +### 构造 UDP 报文 +```python +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 报文头**。 + +--- + +### 验证 IP 地址 +```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`。 + +--- + +### 发送报文 +```python +def send_packet(): + try: + target_ip = ip_entry.get() + target_port = int(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` 异常。 + +--- + +```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 协议。 + +--- + +```python + if packet_type == "ICMP": + packet = construct_icmp() + elif packet_type == "UDP": + packet = construct_udp(12345, target_port) +``` +1. **选择报文类型**: + - 如果选择 ICMP,则调用 `construct_icmp` 构造报文。 + - 如果选择 UDP,则调用 `construct_udp`,源端口固定为 12345。 + +--- + +```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} 秒") + except Exception as e: + messagebox.showerror("错误", f"报文发送失败: {e}") +``` +1. **显示结果**: + - 使用 `result.set` 更新界面上的结果显示。 + - 如果发送成功,显示报文类型和往返时间。 +2. **错误处理**: + - 捕获异常并通过 `messagebox` 提示错误信息。 + +--- + +### GUI 界面 +```python +window = tk.Tk() +window.title("网络通信软件") + +tk.Label(window, text="目标 IP 地址:").grid(row=0, column=0) +ip_entry = tk.Entry(window) +ip_entry.grid(row=0, column=1) + +tk.Label(window, text="目标端口号:").grid(row=1, column=0) +port_entry = tk.Entry(window) +port_entry.grid(row=1, column=1) + +tk.Label(window, text="报文类型:").grid(row=2, 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) + +result = tk.StringVar() +tk.Label(window, textvariable=result).grid(row=4, column=0, columnspan=3) + +tk.Button(window, text="发送报文", command=send_packet).grid(row=3, column=1) +window.mainloop() +``` +1. **窗口初始化**: + - 创建 `window` 窗口对象。 + - 设置窗口标题为“网络通信软件”。 +2. **界面布局**: + - 使用 `grid` 方法设置控件布局,分别添加: + - 文本标签:提示用户输入目标 IP 和端口号。 + - 输入框:用于输入目标 IP 和端口号。 + - 单选框:选择报文类型(ICMP 或 UDP)。 + - 按钮:点击后调用 `send_packet` 函数发送报文。 + - 结果标签:显示发送结果或错误信息。 +3. **事件循环**: + - `window.mainloop()` 启动 GUI 主循环,等待用户交互。 + +--- + +### 总结 +这段代码实现了一个功能完备的网络通信工具,具备以下特点: +- **GUI 界面**:直观的输入与反馈。 +- **多协议支持**:可发送 ICMP 和 UDP 报文。 +- **输入验证**:保证用户输入的有效性。 +- **异常处理**:确保程序运行稳定。 \ No newline at end of file