Smart_Report/tcp.py
2025-04-17 14:15:10 +08:00

208 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()