import sys import json import socket import threading import logging from datetime import datetime from queue import Queue import tkinter as tk from tkinter import ttk, scrolledtext # 配置日志记录 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("client.log", encoding='utf-8'), logging.StreamHandler() ] ) class TcpClientGUI: def __init__(self, master): self.master = master self.client_socket = None self.receive_thread = None self.running = False self.receive_buffer = bytearray() # 接收缓冲区 self.is_receiving_json = False # JSON接收状态标志 # 创建界面组件 self.create_widgets() def create_widgets(self): # 连接参数区 param_frame = ttk.Frame(self.master, padding=10) param_frame.grid(row=0, column=0, sticky="ew") ttk.Label(param_frame, text="服务器IP:").grid(row=0, column=0, sticky="w") self.txt_ip = ttk.Entry(param_frame, width=20) self.txt_ip.grid(row=0, column=1, padx=5) self.txt_ip.insert(0, "127.0.0.1") ttk.Label(param_frame, text="端口:").grid(row=0, column=2, sticky="w") self.txt_port = ttk.Entry(param_frame, width=10) self.txt_port.grid(row=0, column=3, padx=5) self.txt_port.insert(0, "8080") # 按钮区 btn_frame = ttk.Frame(self.master, padding=10) btn_frame.grid(row=1, column=0, sticky="ew") self.btn_connect = ttk.Button(btn_frame, text="连接", command=self.connect) self.btn_connect.pack(side="left", padx=5) self.btn_disconnect = ttk.Button(btn_frame, text="断开", command=self.disconnect, state=tk.DISABLED) self.btn_disconnect.pack(side="left", padx=5) # 消息发送区 send_frame = ttk.Frame(self.master, padding=10) send_frame.grid(row=2, column=0, sticky="ew") ttk.Label(send_frame, text="发送消息:").pack(anchor="w") self.txt_send = ttk.Entry(send_frame, width=50) self.txt_send.pack(fill="x", pady=5) self.txt_send.bind("", self.send_message) # 接收显示区 receive_frame = ttk.Frame(self.master, padding=10) receive_frame.grid(row=3, column=0, sticky="nsew") self.txt_received = scrolledtext.ScrolledText(receive_frame, wrap=tk.WORD, width=60, height=20) self.txt_received.pack(fill="both", expand=True) # 状态栏 self.status_var = tk.StringVar(value="未连接") status_bar = ttk.Label(self.master, textvariable=self.status_var, relief=tk.SUNKEN) status_bar.grid(row=4, column=0, sticky="ew") # 配置网格布局权重 self.master.rowconfigure(3, weight=1) self.master.columnconfigure(0, weight=1) def connect(self): """建立TCP连接""" try: self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client_socket.connect( (self.txt_ip.get(), int(self.txt_port.get())) ) self.running = True self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True) self.receive_thread.start() self.update_status("已连接", "green") self.btn_connect.config(state=tk.DISABLED) self.btn_disconnect.config(state=tk.NORMAL) except Exception as e: self.log_error(f"连接失败: {str(e)}") def disconnect(self): """断开连接""" self.running = False if self.client_socket: self.client_socket.close() self.update_status("未连接", "red") self.btn_connect.config(state=tk.NORMAL) self.btn_disconnect.config(state=tk.DISABLED) def send_message(self, event=None): """发送消息""" try: message = self.txt_send.get() if message: # 添加换行符并编码为UTF-8 data = message.encode('utf-8') + b'\n' self.client_socket.sendall(data) self.append_text(f"[{datetime.now().strftime('%H:%M:%S')}] 发送: {message}\n") self.txt_send.delete(0, tk.END) except Exception as e: self.log_error(f"发送失败: {str(e)}") def receive_messages(self): """接收消息主循环""" try: while self.running: data = self.client_socket.recv(1024) if not data: break # 将接收到的数据添加到缓冲区 self.receive_buffer.extend(data) # 处理缓冲区数据 while len(self.receive_buffer) > 0: if self.is_receiving_json: self.process_json_data() else: self.process_normal_data() except ConnectionAbortedError: pass # 正常断开连接 except Exception as e: self.log_error(f"接收错误: {str(e)}") finally: self.disconnect() def process_normal_data(self): """处理普通数据""" # 查找OK标识(2字节) if len(self.receive_buffer) >= 2: if self.receive_buffer[:2] == b'OK': self.is_receiving_json = True del self.receive_buffer[:2] # 移除标识 return else: # 处理普通消息(查找换行符) newline_pos = self.receive_buffer.find(b'\n') if newline_pos != -1: message = self.receive_buffer[:newline_pos].decode('utf-8', errors='replace') self.append_text(f"[{datetime.now().strftime('%H:%M:%S')}] 接收: {message}\n") del self.receive_buffer[:newline_pos+1] def process_json_data(self): """处理JSON数据""" try: # 尝试解析JSON数组 data_str = self.receive_buffer.decode('utf-8', errors='replace') data = json.loads(data_str) if isinstance(data, list): self.save_json_file(data) self.append_text(f"[{datetime.now().strftime('%H:%M:%S')}] 收到JSON数组,已保存文件\n") self.receive_buffer.clear() self.is_receiving_json = False except json.JSONDecodeError: # 数据不完整,等待更多数据 pass def save_json_file(self, data): """保存JSON文件""" try: filename = f"log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) logging.info(f"文件已保存: {filename}") except Exception as e: self.log_error(f"保存文件失败: {str(e)}") def append_text(self, text): """线程安全更新接收文本框""" self.txt_received.insert(tk.END, text) self.txt_received.see(tk.END) def update_status(self, message, color="black"): """更新状态栏""" self.status_var.set(message) # 正确写法:直接调用主窗口的update方法 self.master.update() def log_error(self, message): """记录错误日志""" logging.error(message) self.txt_received.insert(tk.END, f"错误: {message}\n") if __name__ == "__main__": root = tk.Tk() root.title("TCP客户端") app = TcpClientGUI(root) root.mainloop()