Python-web-communicate/代码解释.md
fly6516 0a2ace4916 docs(代码解释): 更新代码解释文档
- 重新组织代码解释结构,按功能模块划分
- 补充了部分函数和操作的详细说明
- 调整了部分描述,使其更加准确和易于理解
2025-01-06 14:49:56 +08:00

9.0 KiB
Raw Blame History

该代码是一个使用 Tkinter 库制作的网络通信软件通过图形界面GUI允许用户发送不同类型的网络报文如 ICMP、UDP、TCP。以下是逐行解释代码

导入必要的库

import tkinter as tk
from tkinter import ttk, messagebox
import socket
import struct
import time
import uuid
import re
  • tkinter 用于创建 GUI 界面。
  • ttk 提供更丰富的部件,如下拉框(Combobox)。
  • messagebox 用于显示消息框。
  • socket 用于创建网络连接。
  • struct 用于处理二进制数据的打包和解包(比如构造报文头)。
  • timeuuid 用于获取当前时间戳和生成唯一的 MAC 地址。
  • re 用于正则表达式操作,主要用于验证 IP 地址或域名的格式。

获取本机信息

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 地址或域名

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 地址或域名。

验证端口号

def validate_port(port):
    return port.isdigit() and 1 <= int(port) <= 65535
  • 确保端口号是一个数字并在合法范围1-65535内。

构造 IP 头部

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 报文

def construct_icmp():
    icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1)
    data = b'Ping'
    checksum = calculate_checksum(icmp_header + data)
    icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1)
    return icmp_header + data
  • ICMP 报文的类型为 8Echo 请求)。
  • 使用 struct.pack() 构造 ICMP 头部,并计算校验和。
  • 校验和通过 calculate_checksum() 函数来计算,确保报文的完整性。

构造 UDP 报文

def construct_udp(src_port, dest_port):
    udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
    return udp_header
  • 构造一个简单的 UDP 报文头,包含源端口、目标端口、长度和校验和。

构造 TCP 报文SYN

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 标志位以启动连接。
  • 包含源端口、目标端口、序列号等信息。

计算校验和

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 位的校验和。

发送报文

def send_packet():
    try:
        src_ip = ip_combobox.get()
        dest_host = dest_entry.get()
        dest_port = port_entry.get()
        packet_type = var.get()

        # 验证目标地址
        if not validate_ip_or_domain(dest_host):
            raise ValueError("目标地址无效,必须为有效的 IP 或域名")

        # 验证端口号
        if not validate_port(dest_port):
            raise ValueError("端口号无效必须是有效的正整数1-65535")

        dest_ip = socket.gethostbyname(dest_host)
        src_port = 12345
        dest_port = int(dest_port)

        # 构造报文并发送
        if packet_type == "ICMP":
            sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
            packet = construct_icmp()
        elif packet_type == "UDP":
            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  # 结束函数

        # 发送报文
        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}")
  • 根据用户选择的目标地址、端口号和报文类型,构造并发送相应的网络报文。
  • 对地址和端口进行验证若无误则根据选择的报文类型ICMP、UDP、TCP构造报文并发送。
  • 如果是 TCP 报文,使用 socket.SOCK_STREAM 进行连接并发送数据。
  • 若发送成功,通过 messagebox.showerror() 显示错误信息。

GUI 界面设计

window = tk.Tk()
window.title("网络通信软件")
  • 创建一个 Tkinter 窗口,设置标题为“网络通信软件”。
hostname, mac, local_ips = get_local_info()
  • 获取本机的主机名、MAC 地址和可用的 IP 地址。
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 地址。
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 地址供用户选择。
tk.Label(window, text="目标地址:").grid(row=2, column=0)
dest_entry = tk.Entry(window)
dest_entry.grid(row=2, column=1)
  • 提供一个文本框供用户输入目标地址。
tk.Label(window, text="目标端口:").grid(row=3, column=0)
port_entry = tk.Entry(window)
port_entry.grid(row=3, column=1)
  • 提供一个文本框供用户输入目标端口。
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=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
result = tk.StringVar()
tk.Label(window, textvariable=result).grid(row=6, column=0, columnspan=3)
  • 显示报文发送结果。
tk.Button(window, text="发送报文", command=send_packet).grid(row=5, column=1)
  • 创建一个按钮,点击后调用 send_packet() 函数发送报文。
window.mainloop()
  • 启动 GUI 窗口的主循环,使窗口保持显示状态。