套接字Socket编程样例

在运输层网络编程需要用到套接字socket编程,主要包括UDP和TCP编程。

1.1 UDP为无连接服务,其编程模型如图1.1所示。

图1.1 UDP编程模型

整个编程分为服务器端编程和客户单编程。

1.2 UDP服务器代码

python 复制代码
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import time

class UDPServerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("UDP服务器")
        self.root.geometry("600x800")
        self.root.resizable(True, True)
        
        # 服务器状态变量
        self.server_socket = None
        self.server_running = False
        self.server_thread = None
        
        # 创建界面组件
        self.create_widgets()
        
    def create_widgets(self):
        # 顶部控制面板
        control_frame = tk.Frame(self.root)
        control_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(control_frame, text="端口号:").pack(side=tk.LEFT, padx=(0, 5))
        self.port_entry = tk.Entry(control_frame, width=10)
        self.port_entry.insert(0, "9999")
        self.port_entry.pack(side=tk.LEFT, padx=(0, 10))
        
        self.start_button = tk.Button(control_frame, text="启动服务器", command=self.start_server)
        self.start_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.stop_button = tk.Button(control_frame, text="停止服务器", command=self.stop_server, state=tk.DISABLED)
        self.stop_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.clear_button = tk.Button(control_frame, text="清空日志", command=self.clear_log)
        self.clear_button.pack(side=tk.LEFT)
        
        # 状态显示
        status_frame = tk.Frame(self.root)
        status_frame.pack(pady=5, padx=10, fill=tk.X)
        
        self.status_label = tk.Label(status_frame, text="服务器状态: 未运行", fg="red")
        self.status_label.pack(side=tk.LEFT)
        
        # 日志显示区域
        log_frame = tk.LabelFrame(self.root, text="服务器日志")
        log_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
        self.log_text.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
        
        # 发送消息区域
        send_frame = tk.LabelFrame(self.root, text="发送消息")
        send_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(send_frame, text="消息内容:").pack(anchor=tk.W)
        self.message_entry = tk.Entry(send_frame)
        self.message_entry.pack(pady=5, padx=5, fill=tk.X)
        
        self.send_button = tk.Button(send_frame, text="发送消息", command=self.send_message, state=tk.DISABLED)
        self.send_button.pack(pady=5)
        
        # 客户端列表
        client_frame = tk.LabelFrame(self.root, text="已连接客户端")
        client_frame.pack(pady=10, padx=10, fill=tk.X)
        
        self.client_listbox = tk.Listbox(client_frame, height=4)
        self.client_listbox.pack(pady=5, padx=5, fill=tk.X)
        self.client_addresses = {}  # 存储客户端地址信息
        
    def log_message(self, message):
        """在日志区域添加消息"""
        timestamp = time.strftime("%H:%M:%S", time.localtime())
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        
    def start_server(self):
        """启动UDP服务器"""
        try:
            port = int(self.port_entry.get())
            if port < 1 or port > 65535:
                raise ValueError("端口号必须在1-65535之间")
                
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.server_socket.bind(('localhost', port))
            
            self.server_running = True
            self.server_thread = threading.Thread(target=self.server_loop, daemon=True)
            self.server_thread.start()
            
            self.start_button.config(state=tk.DISABLED)
            self.stop_button.config(state=tk.NORMAL)
            self.send_button.config(state=tk.NORMAL)
            self.port_entry.config(state=tk.DISABLED)
            self.status_label.config(text="服务器状态: 运行中", fg="green")
            
            self.log_message(f"UDP服务器已在端口 {port} 启动")
            
        except ValueError as e:
            messagebox.showerror("错误", f"端口号无效: {e}")
        except Exception as e:
            messagebox.showerror("错误", f"启动服务器失败: {e}")
            
    def stop_server(self):
        """停止UDP服务器"""
        self.server_running = False
        if self.server_socket:
            self.server_socket.close()
            
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)
        self.send_button.config(state=tk.DISABLED)
        self.port_entry.config(state=tk.NORMAL)
        self.status_label.config(text="服务器状态: 未运行", fg="red")
        
        self.log_message("UDP服务器已停止")
        
    def server_loop(self):
        """服务器主循环"""
        while self.server_running:
            try:
                # 设置接收超时,以便能及时响应停止命令
                self.server_socket.settimeout(1.0)
                data, client_address = self.server_socket.recvfrom(1024)
                
                message = data.decode('utf-8')
                self.log_message(f"收到来自 {client_address} 的消息: {message}")
                
                # 添加到客户端列表
                if client_address not in self.client_addresses:
                    self.client_addresses[client_address] = True
                    self.client_listbox.insert(tk.END, f"{client_address}:{client_address}")
                
                # 回复客户端
                response = f"服务器收到消息: {message}"
                self.server_socket.sendto(response.encode('utf-8'), client_address)
                
            except socket.timeout:
                # 超时继续循环,检查服务器是否仍在运行
                continue
            except Exception as e:
                if self.server_running:
                    self.log_message(f"服务器错误: {e}")
                break
                
    def send_message(self):
        """向所有客户端发送消息"""
        message = self.message_entry.get()
        if not message:
            messagebox.showwarning("警告", "请输入要发送的消息")
            return
            
        if not self.client_addresses:
            messagebox.showwarning("警告", "没有已连接的客户端")
            return
            
        try:
            for client_address in self.client_addresses:
                self.server_socket.sendto(message.encode('utf-8'), client_address)
                
            self.log_message(f"向所有客户端发送消息: {message}")
            self.message_entry.delete(0, tk.END)
        except Exception as e:
            messagebox.showerror("错误", f"发送消息失败: {e}")
            
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)

def main():
    root = tk.Tk()
    app = UDPServerGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()

1.3 UDP客户端编程

python 复制代码
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import time

class UDPClientGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("UDP客户端")
        self.root.geometry("600x600")
        self.root.resizable(True, True)
        
        # 客户端状态变量
        self.client_socket = None
        self.server_address = None
        self.client_running = False
        self.receive_thread = None
        
        # 创建界面组件
        self.create_widgets()
        
    def create_widgets(self):
        # 顶部连接面板
        connection_frame = tk.Frame(self.root)
        connection_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(connection_frame, text="服务器地址:").pack(side=tk.LEFT, padx=(0, 5))
        self.host_entry = tk.Entry(connection_frame, width=15)
        self.host_entry.insert(0, "localhost")
        self.host_entry.pack(side=tk.LEFT, padx=(0, 5))
        
        tk.Label(connection_frame, text="端口:").pack(side=tk.LEFT, padx=(0, 5))
        self.port_entry = tk.Entry(connection_frame, width=8)
        self.port_entry.insert(0, "9999")
        self.port_entry.pack(side=tk.LEFT, padx=(0, 10))
        
        self.connect_button = tk.Button(connection_frame, text="连接服务器", command=self.connect_to_server)
        self.connect_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.disconnect_button = tk.Button(connection_frame, text="断开连接", command=self.disconnect_from_server, state=tk.DISABLED)
        self.disconnect_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.clear_button = tk.Button(connection_frame, text="清空日志", command=self.clear_log)
        self.clear_button.pack(side=tk.LEFT)
        
        # 状态显示
        status_frame = tk.Frame(self.root)
        status_frame.pack(pady=5, padx=10, fill=tk.X)
        
        self.status_label = tk.Label(status_frame, text="客户端状态: 未连接", fg="red")
        self.status_label.pack(side=tk.LEFT)
        
        # 日志显示区域
        log_frame = tk.LabelFrame(self.root, text="通信日志")
        log_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
        self.log_text.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
        
        # 消息发送区域
        send_frame = tk.LabelFrame(self.root, text="发送消息")
        send_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(send_frame, text="消息内容:").pack(anchor=tk.W)
        self.message_entry = tk.Entry(send_frame)
        self.message_entry.pack(pady=5, padx=5, fill=tk.X)
        
        self.send_button = tk.Button(send_frame, text="发送消息", command=self.send_message, state=tk.DISABLED)
        self.send_button.pack(pady=5)
        
    def log_message(self, message):
        """在日志区域添加消息"""
        timestamp = time.strftime("%H:%M:%S", time.localtime())
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        
    def connect_to_server(self):
        """连接到UDP服务器"""
        try:
            host = self.host_entry.get().strip()
            if not host:
                raise ValueError("服务器地址不能为空")
                
            port = int(self.port_entry.get())
            if port < 1 or port > 65535:
                raise ValueError("端口号必须在1-65535之间")
                
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.server_address = (host, port)
            self.client_running = True
           
            
            # 启动接收线程
            self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
            self.receive_thread.start()
            
            self.connect_button.config(state=tk.DISABLED)
            self.disconnect_button.config(state=tk.NORMAL)
            self.send_button.config(state=tk.NORMAL)
            self.host_entry.config(state=tk.DISABLED)
            self.port_entry.config(state=tk.DISABLED)
            self.status_label.config(text=f"客户端状态: 已连接到 {host}:{port}", fg="green")
            
            #获取本地地址,需要先连接服务器
            self.client_socket.connect(self.server_address)
            local=self.client_socket.getsockname()
            self.log_message(f"{local}已连接到服务器 {host}:{port}")
            
        except ValueError as e:
            messagebox.showerror("错误", f"参数无效: {e}")
        except Exception as e:
            messagebox.showerror("错误", f"连接服务器失败: {e}")
            
    def disconnect_from_server(self):
        """断开与UDP服务器的连接"""
        self.client_running = False
        if self.client_socket:
            self.client_socket.close()
            
        self.connect_button.config(state=tk.NORMAL)
        self.disconnect_button.config(state=tk.DISABLED)
        self.send_button.config(state=tk.DISABLED)
        self.host_entry.config(state=tk.NORMAL)
        self.port_entry.config(state=tk.NORMAL)
        self.status_label.config(text="客户端状态: 未连接", fg="red")
        
        self.log_message("已断开与服务器的连接")
        
    def send_message(self):
        """发送消息到服务器"""
        if not self.client_running:
            messagebox.showwarning("警告", "客户端未连接到服务器")
            return
            
        message = self.message_entry.get()
        if not message:
            messagebox.showwarning("警告", "请输入要发送的消息")
            return
            
        try:
            self.client_socket.sendto(message.encode('utf-8'), self.server_address)
            self.log_message(f"发送到服务器: {message}")
            self.message_entry.delete(0, tk.END)
        except Exception as e:
            messagebox.showerror("错误", f"发送消息失败: {e}")
            
    def receive_messages(self):
        """接收来自服务器的消息"""
        while self.client_running:
            try:
                # 设置接收超时,以便能及时响应停止命令
                self.client_socket.settimeout(1.0)
                data, server = self.client_socket.recvfrom(1024)
                
                response = data.decode('utf-8')
                self.log_message(f"服务器回复: {response}")
                
            except socket.timeout:
                # 超时继续循环,检查客户端是否仍在运行
                continue
            except Exception as e:
                if self.client_running:
                    self.log_message(f"接收消息错误: {e}")
                break
                
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)

def main():
    root = tk.Tk()
    app = UDPClientGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()

1.4 调试结果

2.1 TCP编程模型如图2.1所示

图2.2 TCP编程模型

2.2 TCP服务器端代码

python 复制代码
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import datetime

class TCPServerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("TCP服务器")
        self.root.geometry("600x600")
        
        # 服务器状态变量
        self.server_socket = None
        self.client_sockets = {}  # 存储客户端连接
        self.is_running = False
        
        # 创建界面组件
        self.create_widgets()
    def getTime(self):
        """获取当前时间"""
        now = datetime.datetime.now()
        time=now.strftime("%H:%M:%S")
        return time    
    def create_widgets(self):
        # 服务器配置区域
        config_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
        config_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(config_frame, text="监听IP:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.ip_entry = tk.Entry(config_frame, width=20)
        self.ip_entry.grid(row=0, column=1, padx=5, pady=5)
        self.ip_entry.insert(0, "127.0.0.1")
        
        tk.Label(config_frame, text="监听端口:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.port_entry = tk.Entry(config_frame, width=10)
        self.port_entry.grid(row=0, column=3, padx=5, pady=5)
        self.port_entry.insert(0, "9999")
        
        self.start_btn = tk.Button(config_frame, text="启动服务器", command=self.start_server, width=12)
        self.start_btn.grid(row=0, column=4, padx=5, pady=5)
        
        self.stop_btn = tk.Button(config_frame, text="停止服务器", command=self.stop_server, width=12, state=tk.DISABLED)
        self.stop_btn.grid(row=0, column=5, padx=5, pady=5)
        
        # 客户端列表区域
        client_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
        client_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(client_frame, text="已连接客户端:").pack(anchor=tk.W, padx=5, pady=5)
        self.client_listbox = tk.Listbox(client_frame, height=4)
        self.client_listbox.pack(padx=5, pady=5, fill=tk.X)
        
        # 消息区域
        message_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
        message_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        tk.Label(message_frame, text="通信消息:").pack(anchor=tk.W, padx=5, pady=5)
        self.message_text = scrolledtext.ScrolledText(message_frame, height=12)
        self.message_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
        self.message_text.config(state=tk.DISABLED)
        
        # 发送消息区域
        send_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
        send_frame.pack(pady=10, padx=10, fill=tk.X)
        
        tk.Label(send_frame, text="广播消息:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.send_entry = tk.Entry(send_frame, width=40)
        self.send_entry.grid(row=0, column=1, padx=5, pady=5)
        
        self.send_btn = tk.Button(send_frame, text="发送", command=self.broadcast_message, width=10)
        self.send_btn.grid(row=0, column=2, padx=5, pady=5)
        
    def start_server(self):
        ip = self.ip_entry.get().strip()
        port = self.port_entry.get().strip()
        
        if not ip or not port.isdigit():
            messagebox.showerror("错误", "请输入正确的IP地址和端口号")
            return
            
        port = int(port)
        
        try:
            # 创建服务器套接字
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.server_socket.bind((ip, port))
            self.server_socket.listen(5)
            
            self.is_running = True
            self.start_btn.config(state=tk.DISABLED)
            self.stop_btn.config(state=tk.NORMAL)
            
            # 更新界面状态
            self.append_message(f"{self.getTime()}:服务器已在 {ip}:{port} 上启动,等待客户端连接...\n")
            
            # 启动接受连接的线程
            accept_thread = threading.Thread(target=self.accept_connections, daemon=True)
            accept_thread.start()
            
        except Exception as e:
            messagebox.showerror("错误", f"启动服务器失败:\n{str(e)}")
            self.start_btn.config(state=tk.NORMAL)
            self.stop_btn.config(state=tk.DISABLED)
            
    def stop_server(self):
        self.is_running = False
        
        # 关闭所有客户端连接
        for addr, conn in list(self.client_sockets.items()):
            try:
                conn.close()
            except:
                pass
        self.client_sockets.clear()
        
        # 关闭服务器套接字
        if self.server_socket:
            try:
                self.server_socket.close()
            except:
                pass
            self.server_socket = None
            
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.client_listbox.delete(0, tk.END)
        self.append_message(f"{self.getTime()}:服务器已停止\n")
        
    def accept_connections(self):
        while self.is_running and self.server_socket:
            try:
                # 设置超时以便能及时退出循环
                self.server_socket.settimeout(1.0)
                conn, addr = self.server_socket.accept()
                conn.settimeout(1.0)  # 设置客户端连接超时
                
                # 将新客户端加入字典
                self.client_sockets[addr] = conn
                self.append_message(f"{self.getTime()}:客户端:{addr} 已连接\n")
                
                # 更新客户端列表
                self.root.after(0, self.update_client_list)
                
                # 启动接收消息线程
                recv_thread = threading.Thread(
                    target=self.handle_client, 
                    args=(conn, addr), 
                    daemon=True
                )
                recv_thread.start()
                
            except socket.timeout:
                continue  # 正常超时,继续循环
            except Exception as e:
                if self.is_running:
                    self.append_message(f"接受连接时出错: {str(e)}\n")
                break
                
    def handle_client(self, conn, addr):
        while self.is_running:
            try:
                data = conn.recv(1024)
                if not data:
                    break

                message = data.decode('utf-8')
                display_msg = f"{self.getTime()}:[{addr}]: {message}\n"
                self.append_message(display_msg)
                
            except socket.timeout:
                continue
            except Exception as e:
                break
                
        # 客户端断开连接后的清理工作
        if addr in self.client_sockets:
            del self.client_sockets[addr]
          
            self.append_message(f"{self.getTime()}:客户端 :{addr} 已断开连接\n")
            self.root.after(0, self.update_client_list)
            
        try:
            conn.close()
        except:
            pass
            
    def broadcast_message(self):
        if not self.client_sockets:
            messagebox.showwarning("警告", "没有已连接的客户端")
            return
            
        message = self.send_entry.get().strip()
        if not message:
            return
            
        disconnected_clients = []
        for addr, conn in self.client_sockets.items():
            try:
                conn.send(message.encode('utf-8'))
            except:
                disconnected_clients.append(addr)
                
        # 移除已断开的客户端
        for addr in disconnected_clients:
            if addr in self.client_sockets:
                del self.client_sockets[addr]
                
        if disconnected_clients:
            self.update_client_list()
            
        self.append_message(f"{self.getTime()}:[服务器广播]: {message}\n")
        self.send_entry.delete(0, tk.END)
        
    def append_message(self, message):
        # 在主线程中更新UI
        self.root.after(0, lambda: self._update_message_display(message))
        
    def _update_message_display(self, message):
        self.message_text.config(state=tk.NORMAL)
        self.message_text.insert(tk.END, message)
        self.message_text.config(state=tk.DISABLED)
        self.message_text.see(tk.END)
        
    def update_client_list(self):
        self.client_listbox.delete(0, tk.END)
        for addr in self.client_sockets.keys():
            self.client_listbox.insert(tk.END, f"{addr}:{addr}")

def main():
    root = tk.Tk()
    app = TCPServerApp(root)
    root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
    root.mainloop()
    
def on_closing(root, app):
    if app.is_running:
        app.stop_server()
    root.destroy()

if __name__ == "__main__":
    main()

2.3 TCP客户端代码

python 复制代码
import tkinter as tk
from tkinter import scrolledtext, messagebox, ttk
import socket
import threading
import datetime

class TCPClientApp:
    def __init__(self, root):
        self.root = root
        self.root.title("TCP客户端")
        self.root.geometry("700x600")
        self.root.resizable(True, True)
        
        # 客户端状态变量
        self.client_socket = None
        self.is_connected = False
        self.receive_thread = None
        self.is_receiving = False
        
        # 创建界面组件
        self.create_widgets()

    def getTime(self):
        """获取当前时间"""
        now = datetime.datetime.now()
        time=now.strftime("%H:%M:%S")
        return time   
    def create_widgets(self):
        # 连接配置区域
        connection_frame = tk.LabelFrame(self.root, text="连接配置", padx=10, pady=10)
        connection_frame.pack(pady=10, padx=10, fill=tk.X)
        
        # IP地址输入
        tk.Label(connection_frame, text="服务器IP:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.ip_entry = tk.Entry(connection_frame, width=20)
        self.ip_entry.grid(row=0, column=1, padx=5, pady=5)
        self.ip_entry.insert(0, "127.0.0.1")
        
        # 端口输入
        tk.Label(connection_frame, text="端口:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        self.port_entry = tk.Entry(connection_frame, width=10)
        self.port_entry.grid(row=0, column=3, padx=5, pady=5)
        self.port_entry.insert(0, "9999")
        
        # 连接按钮
        self.connect_btn = tk.Button(connection_frame, text="连接服务器", command=self.connect_to_server, width=12)
        self.connect_btn.grid(row=0, column=4, padx=5, pady=5)
        
        # 断开按钮
        self.disconnect_btn = tk.Button(connection_frame, text="断开连接", command=self.disconnect_from_server, width=12, state=tk.DISABLED)
        self.disconnect_btn.grid(row=0, column=5, padx=5, pady=5)
        
        # 连接状态指示
        self.status_label = tk.Label(connection_frame, text="未连接", fg="red")
        self.status_label.grid(row=0, column=6, padx=10, pady=5)
        
        # 消息显示区域
        message_frame = tk.LabelFrame(self.root, text="通信消息", padx=10, pady=10)
        message_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        # 消息文本框
        self.message_text = scrolledtext.ScrolledText(message_frame, height=15, wrap=tk.WORD)
        self.message_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
        self.message_text.config(state=tk.DISABLED)
        
        # 发送消息区域
        send_frame = tk.LabelFrame(self.root, text="发送消息", padx=10, pady=10)
        send_frame.pack(pady=10, padx=10, fill=tk.X)
        
        # 消息输入框
        tk.Label(send_frame, text="消息内容:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.send_entry = tk.Entry(send_frame, width=50)
        self.send_entry.grid(row=0, column=1, padx=5, pady=5)
        self.send_entry.bind("<Return>", lambda event: self.send_message())
        
        # 发送按钮
        self.send_btn = tk.Button(send_frame, text="发送", command=self.send_message, width=10, state=tk.DISABLED)
        self.send_btn.grid(row=0, column=2, padx=5, pady=5)
        
        # 清空按钮
        clear_btn = tk.Button(send_frame, text="清空消息", command=self.clear_messages, width=10)
        clear_btn.grid(row=0, column=3, padx=5, pady=5)
        
        # 快捷消息区域
        quick_frame = tk.LabelFrame(self.root, text="快捷消息", padx=10, pady=10)
        quick_frame.pack(pady=10, padx=10, fill=tk.X)
        
        # 快捷按钮
        buttons = [
            ("Hello Server", "Hello Server"),
            ("时间查询", "TIME"),
            ("状态查询", "STATUS"),
            ("测试消息", "This is a test message")
        ]
        
        for i, (btn_text, msg) in enumerate(buttons):
            btn = tk.Button(quick_frame, text=btn_text, 
                           command=lambda m=msg: self.quick_send(m),
                           width=12)
            btn.grid(row=0, column=i, padx=5, pady=5)
            btn.config(state=tk.DISABLED)
            setattr(self, f"quick_btn_{i}", btn)
        
    def connect_to_server(self):
        """连接到服务器"""
        if self.is_connected:
            return
            
        ip = self.ip_entry.get().strip()
        port = self.port_entry.get().strip()
        
        if not ip or not port.isdigit():
            messagebox.showerror("错误", "请输入正确的IP地址和端口号")
            return
            
        port = int(port)
        
        try:
            # 创建客户端套接字
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client_socket.settimeout(5)  # 设置连接超时
            
            # 连接到服务器
            self.client_socket.connect((ip, port))
            
            # 更新连接状态
            self.is_connected = True
            self.connect_btn.config(state=tk.DISABLED)
            self.disconnect_btn.config(state=tk.NORMAL)
            self.send_btn.config(state=tk.NORMAL)
            self.status_label.config(text="已连接", fg="green")
            
            # 启用快捷按钮
            for i in range(4):
                getattr(self, f"quick_btn_{i}").config(state=tk.NORMAL)
            
            # 开始接收消息
            self.is_receiving = True
            self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
            self.receive_thread.start()
            
            local_address = self.client_socket.getsockname()
            self.append_message(f"{self.getTime()}:{local_address}已连接到服务器 {ip}:{port}\n")
            
        except socket.timeout:
            messagebox.showerror("错误", "连接超时,请检查服务器地址和端口")
            self.cleanup_connection()
        except ConnectionRefusedError:
            messagebox.showerror("错误", "连接被拒绝,请检查服务器是否正在运行")
            self.cleanup_connection()
        except Exception as e:
            messagebox.showerror("错误", f"连接失败:\n{str(e)}")
            self.cleanup_connection()
            
    def disconnect_from_server(self):
        """断开与服务器的连接"""
        self.is_receiving = False
        
        if self.client_socket:
            try:
                self.client_socket.close()
            except:
                pass
            self.client_socket = None

        self.cleanup_connection()
        self.append_message("{self.getTime()}:已断开与服务器的连接\n")
        
    def cleanup_connection(self):
        """清理连接状态"""
        self.is_connected = False
        self.connect_btn.config(state=tk.NORMAL)
        self.disconnect_btn.config(state=tk.DISABLED)
        self.send_btn.config(state=tk.DISABLED)
        self.status_label.config(text="未连接", fg="red")
        
        # 禁用快捷按钮
        for i in range(4):
            getattr(self, f"quick_btn_{i}").config(state=tk.DISABLED)
            
    def receive_messages(self):
        """接收服务器消息的线程函数"""
        while self.is_receiving and self.client_socket:
            try:
                # 设置接收超时
                self.client_socket.settimeout(1.0)
                data = self.client_socket.recv(1024)
                
                if not data:
                    # 服务器关闭连接
                    break

                 
                message = data.decode('utf-8')
                self.append_message(f"{self.getTime()}:[服务器]: {message}\n")
                
            except socket.timeout:
                # 正常超时,继续循环
                continue
            except Exception as e:
                if self.is_receiving:
                    self.append_message(f"[错误] 接收消息时出错: {str(e)}\n")
                break
                
        # 如果仍在连接状态,说明是异常断开
        if self.is_connected:
            self.root.after(0, self.handle_disconnect)
            
    def handle_disconnect(self):
        """处理意外断开连接"""
       
        self.cleanup_connection()
        self.append_message(f"{self.getTime()}:与服务器的连接已断开\n")
        messagebox.showinfo("提示", "与服务器的连接已断开")
        
    def send_message(self):
        """发送消息到服务器"""
        if not self.is_connected or not self.client_socket:
            messagebox.showwarning("警告", "未连接到服务器")
            return
            
        message = self.send_entry.get().strip()
        if not message:
            return
            
        try:
            
            self.client_socket.send(message.encode('utf-8'))
            self.append_message(f"{self.getTime()}:[我]: {message}\n")
            self.send_entry.delete(0, tk.END)
        except Exception as e:
            messagebox.showerror("错误", f"发送消息失败:\n{str(e)}")
            self.handle_disconnect()
            
    def quick_send(self, message):
        """快速发送预设消息"""
        self.send_entry.delete(0, tk.END)
        self.send_entry.insert(0, message)
        self.send_message()
        
    def append_message(self, message):
        """在消息区域追加消息"""
        # 使用after方法确保在主线程中更新UI
        self.root.after(0, lambda: self._update_message_display(message))
        
    def _update_message_display(self, message):
        """实际更新消息显示"""
        self.message_text.config(state=tk.NORMAL)
        self.message_text.insert(tk.END, message)
        self.message_text.config(state=tk.DISABLED)
        self.message_text.see(tk.END)
        
    def clear_messages(self):
        """清空消息显示区域"""
        self.message_text.config(state=tk.NORMAL)
        self.message_text.delete(1.0, tk.END)
        self.message_text.config(state=tk.DISABLED)

def main():
    root = tk.Tk()
    app = TCPClientApp(root)
    root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
    root.mainloop()
    
def on_closing(root, app):
    """窗口关闭事件处理"""
    if app.is_connected:
        app.disconnect_from_server()
    root.destroy()

if __name__ == "__main__":
    main()

2.4 调试结果

相关推荐
zl_dfq1 天前
计算机网络 之 【UDP协议】(UDP报文格式及特点、UDP内核实现简介、UDP VS TCP)
网络协议·计算机网络·udp
冉佳驹2 天前
Qt【第六篇】 ——— 事件处理、多线程、网络与文件等操作详解
qt·http·udp·tcp·事件·多线程与互斥锁
liulilittle2 天前
静态隧道 UDP 限制与绕过:以 DMIT 机房为例
网络·网络协议·udp
胖咕噜的稞达鸭3 天前
总结面试经验TCP和UDP的区别,TCP慢启动机制,拥塞控制,Linux指令,DNS的理解,TLS握手流程
tcp/ip·面试·udp
kim_puppy3 天前
TCP的三次握手,四次挥手
java·网络·tcp
Dxy12393102163 天前
JavaScript 如何捕获异常:从基础到进阶的完整指南
开发语言·javascript·udp
末日汐4 天前
传输层协议UDP
linux·网络·udp
zl_dfq4 天前
计算机网络 之 【TCP协议】(确认应答、超时重传、流量控制、三次握手、四次挥手、滑动窗口、快重传、延迟应答、Nagle算法、捎带应答、拥塞控制)
网络·计算机网络·tcp
zl_dfq4 天前
计算机网络 之 【TCP协议】(面向字节流、TCP异常情况、保活机制、文件与Socket的关系、网络协议栈的本质)
网络·计算机网络·tcp