实验1python基本网络应用
一、实验目的和任务
- 掌握Python网络编程的基本应用
- 掌握Python Socket编程方法
- 掌握TCP和UDP的Python编程模型
二、实验内容
1.编写Python程序获取本机的主机名称和IP地址(GUI界面)
2.编写Python程序实现UDP网络聊天(多线程GUI版)
3.(选做题)编写Python程序实现TCP文件传输
三、实验步骤
实验1.1 获取本机网络信息(GUI版)
程序结构:使用tkinter创建GUI界面,包含以下组件:
1.主机名显示标签、2.IP地址列表框、3.查询按钮
程序主要流程:
1.创建主窗口和界面布局、
2.实现"查询网络信息"按钮功能
3.使用socket.gethostname()获取主机名
4.使用socket.gethostbyname_ex()获取IP列表
5.将结果显示在GUI界面上
代码参考:
def get_network_info(self):"""获取网络信息并更新界面"""try:# 清空旧数据self.ip_listbox.delete(0, tk.END)# 获取主机名hostname = socket.gethostname()self.hostname_label.config(text=hostname)# 获取IP地址ip_list = socket.gethostbyname_ex(hostname)[2]for ip in ip_list:self.ip_listbox.insert(tk.END, ip)except Exception as e:tk.messagebox.showerror("错误", f"获取网络信息失败: {str(e)}")
完整python脚本如下:
import tkinter as tk
import tkinter.messagebox
import socketclass NetworkInfoApp:def __init__(self, master):self.master = mastermaster.title("本机网络信息查询")# 创建界面组件self.hostname_label = tk.Label(master, text="主机名: 未查询", font=('Arial', 12))self.hostname_label.pack(pady=10)# 网络信息显示区域info_frame = tk.Frame(master)info_frame.pack(pady=10)# IP地址列表tk.Label(info_frame, text="IP地址列表:").pack()self.ip_listbox = tk.Listbox(info_frame, width=40, height=5)self.ip_listbox.pack()# 端口信息tk.Label(info_frame, text="开放端口:").pack()self.port_listbox = tk.Listbox(info_frame, width=40, height=5)self.port_listbox.pack()# 查询按钮self.query_button = tk.Button(master, text="查询网络信息", command=self.get_network_info)self.query_button.pack(pady=10)def get_network_info(self):"""获取网络信息并更新界面"""try:# 清空旧数据self.ip_listbox.delete(0, tk.END)self.port_listbox.delete(0, tk.END)# 获取主机名hostname = socket.gethostname()self.hostname_label.config(text=f"主机名: {hostname}")# 获取IP地址ip_list = socket.gethostbyname_ex(hostname)[2]for ip in ip_list:self.ip_listbox.insert(tk.END, ip)# 获取常用端口状态common_ports = [21, 22, 80, 443, 3306, 3389]for port in common_ports:sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.settimeout(0.1)result = sock.connect_ex(('127.0.0.1', port))status = "开放" if result == 0 else "关闭"self.port_listbox.insert(tk.END, f"端口 {port}: {status}")sock.close()except Exception as e:tk.messagebox.showerror("错误", f"获取网络信息失败: {str(e)}")if __name__ == "__main__":root = tk.Tk()app = NetworkInfoApp(root)root.mainloop()
功能效果如下图所示:
实验1.2 UDP聊天程序(GUI版)
使用tkinter创建聊天界面包含以下组件:
1.本地IP和端口设置
2.目标IP和端口设
3.消息输入框和发送按钮
4.聊天记录显示区域
5.启动/停止监听按钮
主要步骤:
1.创建主窗口和界面布局、2.创建UDP socket、3.绑定指定IP和端口、4.启动接收线程、5.实现消息接收线程、6.实现消息发送功能、7.处理异常情况
代码参考:
#创建界面函数:def create_widgets(self):# 主框架main_frame = ttk.Frame(self.root, padding=10)main_frame.pack(fill=tk.BOTH, expand=True)# 地址设置addr_frame = ttk.LabelFrame(main_frame, text="网络设置", padding=10)addr_frame.grid(row=0, column=0, sticky=tk.W, pady=5)# 本地设置ttk.Label(addr_frame, text="本地IP:").grid(row=0, column=0, sticky=tk.W)self.local_ip = ttk.Entry(addr_frame, width=15)self.local_ip.insert(0, '0.0.0.0')self.local_ip.grid(row=0, column=1)ttk.Label(addr_frame, text="本地端口:").grid(row=0, column=2, padx=5)self.local_port = ttk.Entry(addr_frame, width=8)self.local_port.grid(row=0, column=3)# 目标设置ttk.Label(addr_frame, text="目标IP:").grid(row=1, column=0, sticky=tk.W, pady=5)self.target_ip = ttk.Entry(addr_frame, width=15)self.target_ip.grid(row=1, column=1)ttk.Label(addr_frame, text="目标端口:").grid(row=1, column=2)self.target_port = ttk.Entry(addr_frame, width=8)self.target_port.grid(row=1, column=3)# 消息区msg_frame = ttk.LabelFrame(main_frame, text="消息", padding=10)msg_frame.grid(row=1, column=0, sticky=tk.W, pady=5)self.msg_input = ttk.Entry(msg_frame, width=40)self.msg_input.grid(row=0, column=0)self.send_btn = ttk.Button(msg_frame, text="发送", command=self.send_message)self.send_btn.grid(row=0, column=1, padx=5)# 聊天记录self.chat_history = scrolledtext.ScrolledText(main_frame, width=50, height=15)self.chat_history.grid(row=2, column=0)# 控制按钮self.start_btn = ttk.Button(main_frame, text="启动监听", command=self.toggle_server)self.start_btn.grid(row=3, column=0, pady=10)启动\关闭服务器函数,绑定在启动监听按钮:def toggle_server(self):if not self.running:try:self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)self.socket.bind((self.local_ip.get(), int(self.local_port.get())))self.running = Trueself.start_btn.config(text="停止监听")threading.Thread(target=self.recv_thread, daemon=True).start()except Exception as e:messagebox.showerror("错误", f"启动失败: {str(e)}")else:self.running = Falseself.socket.close()self.start_btn.config(text="启动监听")#消息接收线程,在启动监听后开始。def recv_thread(self):"""接收消息线程"""while self.running:try:data, addr = self.socket.recvfrom(1024)msg = f"[来自 {addr[0]}:{addr[1]}]: {data.decode()}\n"self.chat_history.insert(tk.END, msg)except:break#发送消息函数,绑定在发送按钮:def send_message(self):try:msg = self.msg_input.get()if not msg:returntarget = (self.target_ip.get(), int(self.target_port.get()))self.socket.sendto(msg.encode(), target)self.chat_history.insert(tk.END, f"[我]: {msg}\n")self.msg_input.delete(0, tk.END)except Exception as e:messagebox.showerror("错误", f"发送失败: {str(e)}")
完整python脚本如下:
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import socket
import threadingclass UDPChatApp:def __init__(self, root):self.root = rootself.root.title("UDP网络聊天程序")self.running = Falseself.socket = Noneself.create_widgets()def create_widgets(self):# 主框架main_frame = ttk.Frame(self.root, padding=10)main_frame.pack(fill=tk.BOTH, expand=True)# 地址设置addr_frame = ttk.LabelFrame(main_frame, text="网络设置", padding=10)addr_frame.grid(row=0, column=0, sticky=tk.W, pady=5)# 本地设置ttk.Label(addr_frame, text="本地IP:").grid(row=0, column=0, sticky=tk.W)self.local_ip = ttk.Entry(addr_frame, width=15)self.local_ip.insert(0, '0.0.0.0')self.local_ip.grid(row=0, column=1)ttk.Label(addr_frame, text="本地端口:").grid(row=0, column=2, padx=5)self.local_port = ttk.Entry(addr_frame, width=8)self.local_port.grid(row=0, column=3)# 目标设置ttk.Label(addr_frame, text="目标IP:").grid(row=1, column=0, sticky=tk.W, pady=5)self.target_ip = ttk.Entry(addr_frame, width=15)self.target_ip.grid(row=1, column=1)ttk.Label(addr_frame, text="目标端口:").grid(row=1, column=2)self.target_port = ttk.Entry(addr_frame, width=8)self.target_port.grid(row=1, column=3)# 消息区msg_frame = ttk.LabelFrame(main_frame, text="消息", padding=10)msg_frame.grid(row=1, column=0, sticky=tk.W, pady=5)self.msg_input = ttk.Entry(msg_frame, width=40)self.msg_input.grid(row=0, column=0)self.send_btn = ttk.Button(msg_frame, text="发送", command=self.send_message)self.send_btn.grid(row=0, column=1, padx=5)# 聊天记录self.chat_history = scrolledtext.ScrolledText(main_frame, width=50, height=15)self.chat_history.grid(row=2, column=0)# 控制按钮self.start_btn = ttk.Button(main_frame, text="启动监听", command=self.toggle_server)self.start_btn.grid(row=3, column=0, pady=10)def toggle_server(self):if not self.running:try:self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)self.socket.bind((self.local_ip.get(), int(self.local_port.get())))self.running = Trueself.start_btn.config(text="停止监听")threading.Thread(target=self.recv_thread, daemon=True).start()except Exception as e:messagebox.showerror("错误", f"启动失败: {str(e)}")else:self.running = Falseif self.socket:self.socket.close()self.start_btn.config(text="启动监听")def recv_thread(self):"""接收消息线程"""while self.running:try:data, addr = self.socket.recvfrom(1024)msg = f"[来自 {addr[0]}:{addr[1]}]: {data.decode()}\n"self.chat_history.insert(tk.END, msg)except:breakdef send_message(self):try:msg = self.msg_input.get()if not msg:returntarget = (self.target_ip.get(), int(self.target_port.get()))self.socket.sendto(msg.encode(), target)self.chat_history.insert(tk.END, f"[我]: {msg}\n")self.msg_input.delete(0, tk.END)except Exception as e:messagebox.showerror("错误", f"发送失败: {str(e)}")if __name__ == "__main__":root = tk.Tk()app = UDPChatApp(root)root.mainloop()
功能效果如下图所示:
四、思考问题
1.Python中如何获取本机的主机名?
答:在Python中,可以使用socket
模块的gethostname()
函数来获取本机的主机名。
2.在实验1.1中,我们使用了哪个函数来获取主机名?哪个函数来获取IP地址列表?
答:获取主机名有socket.gethostname(),
获取IP地址列表有socket.gethostbyname_ex(hostname)
其中,hostname
是通过gethostname()
获取的主机名。gethostbyname_ex()
返回一个包含主机名、别名列表和IP地址列表的元组。
3.UDP和TCP有什么区别?
答:
特性 | UDP (用户数据报协议) | TCP (传输控制协议) |
---|---|---|
连接方式 | 无连接 | 面向连接 |
可靠性 | 不可靠,不保证数据顺序或送达 | 可靠,保证数据顺序和送达 |
速度 | 快,开销小 | 较慢,开销大 |
流量控制 | 无 | 有 |
拥塞控制 | 无 | 有 |
适用场景 | 实时应用(视频、语音、游戏) | 需要可靠传输的应用(文件传输、网页浏览) |
4.为什么实验1.2的程序要使用多线程?如果不使用多线程,直接在主线程中接收消息会有什么问题?
答:两种原因
使用多线程的原因:
聊天程序需要同时处理发送和接收消息的任务。如果只用单线程,程序会在等待接收消息时阻塞,无法同时发送消息,用户体验差。
不用多线程的问题:
主线程会因等待接收消息而被阻塞,导致程序无法及时响应用户输入或其他任务,失去交互性。
5. 在实验1.2中,我们使用了UDP协议,如果改用TCP实现聊天程序,代码会有哪些关键变化?
答:关键变化:
建立连接:TCP需要先建立连接(socket.connect()或socket.listen()和socket.accept())。
数据传输:TCP使用send()和recv(),而UDP使用sendto()和recvfrom()。
可靠性处理:TCP无需额外处理丢包或乱序,而UDP需要手动处理。
多线程/多进程:TCP可能需要更复杂的线程管理(如为每个连接创建线程)。
6. UDP是无连接的,如果网络丢包,程序会怎样?如何检测消息是否送达?
答:丢包的影响:
程序不会自动重传或报错,数据可能丢失或乱序,需要应用层处理。
检测消息送达的方法:
应用层确认机制:接收方收到消息后发送确认(ACK),发送方等待ACK超时则重传。
超时重传:发送方设置超时时间,未收到ACK则重发。
序列号:为消息添加序列号,接收方检查是否连续。