208 lines
7.7 KiB
Python
Raw Permalink Normal View History

2025-04-17 14:15:10 +08:00
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("<Return>", 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()