aW1wb3J0IHRraW50ZXIgYXMgdGsKZnJvbSB0a2ludGVyIGltcG9ydCBzY3JvbGxlZHRleHQsIG1lc3NhZ2Vib3gKaW1wb3J0IHNvY2tldAppbXBvcnQgdGhyZWFkaW5nCmltcG9ydCBzeXMKCmNsYXNzIENoYXRDbGllbnQ6CiAgICBkZWYgX19pbml0X18oc2VsZiwgcm9vdCk6CiAgICAgICAgc2VsZi5yb290ID0gcm9vdAogICAgICAgIHNlbGYucm9vdC50aXRsZSgmcXVvdDvlsYDln5/nvZHogYrlpKnlrqQmcXVvdDspCiAgICAgICAgc2VsZi5yb290Lmdlb21ldHJ5KCZxdW90OzYwMHg0NTAmcXVvdDspCiAgICAgICAgc2VsZi5yb290LnJlc2l6YWJsZShGYWxzZSwgRmFsc2UpCgogICAgICAgICMg6L+e5o6l5L+h5oGvCiAgICAgICAgc2VsZi5zb2NrZXQgPSBOb25lCiAgICAgICAgc2VsZi5uaWNrbmFtZSA9ICZxdW90OyZxdW90OwoKICAgICAgICAjIOeVjOmdoue7hOS7tgogICAgICAgIHNlbGYuY3JlYXRlX3dpZGdldHMoKQoKICAgICAgICAjIOWFiOW8ueWHuui/nuaOpeWvueivneahhgogICAgICAgIHNlbGYuY29ubmVjdF90b19zZXJ2ZXIoKQoKICAgIGRlZiBjcmVhdGVfd2lkZ2V0cyhzZWxmKToKICAgICAgICAjIOa2iOaBr+aYvuekuuWMuuWfnwogICAgICAgIHNlbGYudGV4dF9hcmVhID0gc2Nyb2xsZWR0ZXh0LlNjcm9sbGVkVGV4dChzZWxmLnJvb3QsIHN0YXRlPSdkaXNhYmxlZCcsIHdyYXA9dGsuV09SRCkKICAgICAgICBzZWxmLnRleHRfYXJlYS5wYWNrKHBhZHg9MTAsIHBhZHk9MTAsIGZpbGw9dGsuQk9USCwgZXhwYW5kPVRydWUpCgogICAgICAgICMg5bqV6YOo5qGG5p62CiAgICAgICAgYm90dG9tX2ZyYW1lID0gdGsuRnJhbWUoc2VsZi5yb290KQogICAgICAgIGJvdHRvbV9mcmFtZS5wYWNrKHBhZHg9MTAsIHBhZHk9MTAsIGZpbGw9dGsuWCkKCiAgICAgICAgc2VsZi5lbnRyeSA9IHRrLkVudHJ5KGJvdHRvbV9mcmFtZSkKICAgICAgICBzZWxmLmVudHJ5LnBhY2soc2lkZT10ay5MRUZULCBmaWxsPXRrLlgsIGV4cGFuZD1UcnVlKQogICAgICAgIHNlbGYuZW50cnkuYmluZCgmcXVvdDsmbHQ7UmV0dXJuJmd0OyZxdW90OywgbGFtYmRhIGU6IHNlbGYuc2VuZF9tZXNzYWdlKCkpCgogICAgICAgIHNlbmRfYnRuID0gdGsuQnV0dG9uKGJvdHRvbV9mcmFtZSwgdGV4dD0mcXVvdDvlj5HpgIEmcXVvdDssIGNvbW1hbmQ9c2VsZi5zZW5kX21lc3NhZ2UpCiAgICAgICAgc2VuZF9idG4ucGFjayhzaWRlPXRrLlJJR0hULCBwYWR4PTUpCgogICAgZGVmIGNvbm5lY3RfdG9fc2VydmVyKHNlbGYpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+W8ueWHuuWvueivneahhu+8jOi+k+WFpeacjeWKoeWZqElQ5ZKM5pi156ewJnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgZGlhbG9nID0gdGsuVG9wbGV2ZWwoc2VsZi5yb290KQogICAgICAgIGRpYWxvZy50aXRsZSgmcXVvdDvov57mjqXorr7nva4mcXVvdDspCiAgICAgICAgZGlhbG9nLmdlb21ldHJ5KCZxdW90OzMwMHgyMDAmcXVvdDspCiAgICAgICAgZGlhbG9nLnJlc2l6YWJsZShGYWxzZSwgRmFsc2UpCiAgICAgICAgZGlhbG9nLmdyYWJfc2V0KCkgICMg5qih5oCBCgogICAgICAgIHRrLkxhYmVsKGRpYWxvZywgdGV4dD0mcXVvdDvmnI3liqHlmahJUDomcXVvdDspLnBhY2socGFkeT01KQogICAgICAgIGlwX2VudHJ5ID0gdGsuRW50cnkoZGlhbG9nLCB3aWR0aD0yMCkKICAgICAgICBpcF9lbnRyeS5wYWNrKCkKICAgICAgICBpcF9lbnRyeS5pbnNlcnQoMCwgJnF1b3Q7MTI3LjAuMC4xJnF1b3Q7KSAgIyDpu5jorqTmnKzmnLoKCiAgICAgICAgdGsuTGFiZWwoZGlhbG9nLCB0ZXh0PSZxdW90O+aYteensDomcXVvdDspLnBhY2socGFkeT01KQogICAgICAgIG5hbWVfZW50cnkgPSB0ay5FbnRyeShkaWFsb2csIHdpZHRoPTIwKQogICAgICAgIG5hbWVfZW50cnkucGFjaygpCgogICAgICAgIGRlZiBkb19jb25uZWN0KCk6CiAgICAgICAgICAgIGlwID0gaXBfZW50cnkuZ2V0KCkuc3RyaXAoKQogICAgICAgICAgICBuaWNrID0gbmFtZV9lbnRyeS5nZXQoKS5zdHJpcCgpCiAgICAgICAgICAgIGlmIG5vdCBpcCBvciBub3QgbmljazoKICAgICAgICAgICAgICAgIG1lc3NhZ2Vib3guc2hvd2Vycm9yKCZxdW90O+mUmeivryZxdW90OywgJnF1b3Q76K+35aGr5YaZ5a6M5pW05L+h5oGvJnF1b3Q7KQogICAgICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICAgIHNlbGYubmlja25hbWUgPSBuaWNrCiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHNlbGYuc29ja2V0ID0gc29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCwgc29ja2V0LlNPQ0tfU1RSRUFNKQogICAgICAgICAgICAgICAgc2VsZi5zb2NrZXQuY29ubmVjdCgoaXAsIDEyMzQ1KSkKICAgICAgICAgICAgICAgICMg5YWI5Y+R6YCB5pi156ewCiAgICAgICAgICAgICAgICBzZWxmLnNvY2tldC5zZW5kKG5pY2suZW5jb2RlKCd1dGYtOCcpKQogICAgICAgICAgICAgICAgIyDlkK/liqjmjqXmlLbnur/nqIsKICAgICAgICAgICAgICAgIHJlY3ZfdGhyZWFkID0gdGhyZWFkaW5nLlRocmVhZCh0YXJnZXQ9c2VsZi5yZWNlaXZlX21lc3NhZ2VzLCBkYWVtb249VHJ1ZSkKICAgICAgICAgICAgICAgIHJlY3ZfdGhyZWFkLnN0YXJ0KCkKICAgICAgICAgICAgICAgIGRpYWxvZy5kZXN0cm95KCkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICAgICAgbWVzc2FnZWJveC5zaG93ZXJyb3IoJnF1b3Q76L+e5o6l5aSx6LSlJnF1b3Q7LCBmJnF1b3Q75peg5rOV6L+e5o6l5pyN5Yqh5Zmo77yae2V9JnF1b3Q7KQoKICAgICAgICB0ay5CdXR0b24oZGlhbG9nLCB0ZXh0PSZxdW90O+i/nuaOpSZxdW90OywgY29tbWFuZD1kb19jb25uZWN0KS5wYWNrKHBhZHk9MTUpCgogICAgZGVmIHJlY2VpdmVfbWVzc2FnZXMoc2VsZik6CiAgICAgICAgJnF1b3Q7JnF1b3Q7JnF1b3Q75oyB57ut5o6l5pS25pyN5Yqh5Zmo5raI5oGv5bm25pi+56S6JnF1b3Q7JnF1b3Q7JnF1b3Q7CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgbXNnID0gc2VsZi5zb2NrZXQucmVjdigxMDI0KS5kZWNvZGUoJ3V0Zi04JykKICAgICAgICAgICAgICAgIGlmIG5vdCBtc2c6CiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIHNlbGYuZGlzcGxheV9tZXNzYWdlKG1zZykKICAgICAgICAgICAgZXhjZXB0OgogICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAjIOaWreW8gOi/nuaOpeWQjuWFs+mXreeql+WPowogICAgICAgIHNlbGYucm9vdC5hZnRlcigwLCBsYW1iZGE6IG1lc3NhZ2Vib3guc2hvd2Vycm9yKCZxdW90O+i/nuaOpeaWreW8gCZxdW90OywgJnF1b3Q75LiO5pyN5Yqh5Zmo5aSx5Y676L+e5o6lJnF1b3Q7KSkKICAgICAgICBzZWxmLnJvb3QuYWZ0ZXIoMCwgc2VsZi5yb290LmRlc3Ryb3kpCgogICAgZGVmIGRpc3BsYXlfbWVzc2FnZShzZWxmLCBtc2cpOgogICAgICAgICZxdW90OyZxdW90OyZxdW90O+WcqOaWh+acrOWMuuWfn+a3u+WKoOa2iOaBr++8iOe6v+eoi+WuieWFqO+8iSZxdW90OyZxdW90OyZxdW90OwogICAgICAgIHNlbGYudGV4dF9hcmVhLmNvbmZpZyhzdGF0ZT0nbm9ybWFsJykKICAgICAgICBzZWxmLnRleHRfYXJlYS5pbnNlcnQodGsuRU5ELCBtc2cgKyAmcXVvdDtcbiZxdW90OykKICAgICAgICBzZWxmLnRleHRfYXJlYS5zZWUodGsuRU5EKQogICAgICAgIHNlbGYudGV4dF9hcmVhLmNvbmZpZyhzdGF0ZT0nZGlzYWJsZWQnKQoKICAgIGRlZiBzZW5kX21lc3NhZ2Uoc2VsZik6CiAgICAgICAgdGV4dCA9IHNlbGYuZW50cnkuZ2V0KCkuc3RyaXAoKQogICAgICAgIGlmIG5vdCB0ZXh0OgogICAgICAgICAgICByZXR1cm4KICAgICAgICAjIOajgOafpeaYr+WQpuaYr+engeiBiu+8iOagvOW8j++8mkDmmLXnp7Ag5raI5oGv5YaF5a6577yJCiAgICAgICAgaWYgdGV4dC5zdGFydHN3aXRoKCdAJyk6CiAgICAgICAgICAgIHBhcnRzID0gdGV4dC5zcGxpdCgnICcsIDEpCiAgICAgICAgICAgIGlmIGxlbihwYXJ0cykgPT0gMjoKICAgICAgICAgICAgICAgIHRhcmdldCA9IHBhcnRzWzBdWzE6XQogICAgICAgICAgICAgICAgY29udGVudCA9IHBhcnRzWzFdCiAgICAgICAgICAgICAgICBmdWxsX21zZyA9IGYmcXVvdDtb56eB6IGKe3RhcmdldH1de2NvbnRlbnR9JnF1b3Q7CiAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgc2VsZi5zb2NrZXQuc2VuZChmdWxsX21zZy5lbmNvZGUoJ3V0Zi04JykpCiAgICAgICAgICAgICAgICAgICAgc2VsZi5kaXNwbGF5X21lc3NhZ2UoZiZxdW90O1vkvaDnp4HogYp7dGFyZ2V0fV06IHtjb250ZW50fSZxdW90OykKICAgICAgICAgICAgICAgIGV4Y2VwdDoKICAgICAgICAgICAgICAgICAgICBzZWxmLmRpc3BsYXlfbWVzc2FnZSgmcXVvdDvlj5HpgIHlpLHotKUmcXVvdDspCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBzZWxmLmRpc3BsYXlfbWVzc2FnZSgmcXVvdDvnp4HogYrmoLzlvI/plJnor6/vvIzor7fnlKggQOaYteensCDmtojmga/lhoXlrrkmcXVvdDspCiAgICAgICAgZWxzZToKICAgICAgICAgICAgIyDmma7pgJrnvqTogYoKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgc2VsZi5zb2NrZXQuc2VuZCh0ZXh0LmVuY29kZSgndXRmLTgnKSkKICAgICAgICAgICAgICAgIHNlbGYuZGlzcGxheV9tZXNzYWdlKGYmcXVvdDvmiJE6IHt0ZXh0fSZxdW90OykKICAgICAgICAgICAgZXhjZXB0OgogICAgICAgICAgICAgICAgc2VsZi5kaXNwbGF5X21lc3NhZ2UoJnF1b3Q75Y+R6YCB5aSx6LSlJnF1b3Q7KQogICAgICAgIHNlbGYuZW50cnkuZGVsZXRlKDAsIHRrLkVORCkKCmlmIF9fbmFtZV9fID09ICZxdW90O19fbWFpbl9fJnF1b3Q7OgogICAgcm9vdCA9IHRrLlRrKCkKICAgIGFwcCA9IENoYXRDbGllbnQocm9vdCkKICAgIHJvb3QubWFpbmxvb3AoKQ==
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import sys
class ChatClient:
def __init__(self, root):
self.root = root
self.root.title("局域网聊天室")
self.root.geometry("600x450")
self.root.resizable(False, False)
# 连接信息
self.socket = None
self.nickname = ""
# 界面组件
self.create_widgets()
# 先弹出连接对话框
self.connect_to_server()
def create_widgets(self):
# 消息显示区域
self.text_area = scrolledtext.ScrolledText(self.root, state='disabled', wrap=tk.WORD)
self.text_area.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
# 底部框架
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(padx=10, pady=10, fill=tk.X)
self.entry = tk.Entry(bottom_frame)
self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.entry.bind("<Return>", lambda e: self.send_message())
send_btn = tk.Button(bottom_frame, text="发送", command=self.send_message)
send_btn.pack(side=tk.RIGHT, padx=5)
def connect_to_server(self):
"""弹出对话框,输入服务器IP和昵称"""
dialog = tk.Toplevel(self.root)
dialog.title("连接设置")
dialog.geometry("300x200")
dialog.resizable(False, False)
dialog.grab_set() # 模态
tk.Label(dialog, text="服务器IP:").pack(pady=5)
ip_entry = tk.Entry(dialog, width=20)
ip_entry.pack()
ip_entry.insert(0, "127.0.0.1") # 默认本机
tk.Label(dialog, text="昵称:").pack(pady=5)
name_entry = tk.Entry(dialog, width=20)
name_entry.pack()
def do_connect():
ip = ip_entry.get().strip()
nick = name_entry.get().strip()
if not ip or not nick:
messagebox.showerror("错误", "请填写完整信息")
return
self.nickname = nick
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((ip, 12345))
# 先发送昵称
self.socket.send(nick.encode('utf-8'))
# 启动接收线程
recv_thread = threading.Thread(target=self.receive_messages, daemon=True)
recv_thread.start()
dialog.destroy()
except Exception as e:
messagebox.showerror("连接失败", f"无法连接服务器:{e}")
tk.Button(dialog, text="连接", command=do_connect).pack(pady=15)
def receive_messages(self):
"""持续接收服务器消息并显示"""
while True:
try:
msg = self.socket.recv(1024).decode('utf-8')
if not msg:
break
self.display_message(msg)
except:
break
# 断开连接后关闭窗口
self.root.after(0, lambda: messagebox.showerror("连接断开", "与服务器失去连接"))
self.root.after(0, self.root.destroy)
def display_message(self, msg):
"""在文本区域添加消息(线程安全)"""
self.text_area.config(state='normal')
self.text_area.insert(tk.END, msg + "\n")
self.text_area.see(tk.END)
self.text_area.config(state='disabled')
def send_message(self):
text = self.entry.get().strip()
if not text:
return
# 检查是否是私聊(格式:@昵称 消息内容)
if text.startswith('@'):
parts = text.split(' ', 1)
if len(parts) == 2:
target = parts[0][1:]
content = parts[1]
full_msg = f"[私聊{target}]{content}"
try:
self.socket.send(full_msg.encode('utf-8'))
self.display_message(f"[你私聊{target}]: {content}")
except:
self.display_message("发送失败")
else:
self.display_message("私聊格式错误,请用 @昵称 消息内容")
else:
# 普通群聊
try:
self.socket.send(text.encode('utf-8'))
self.display_message(f"我: {text}")
except:
self.display_message("发送失败")
self.entry.delete(0, tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = ChatClient(root)
root.mainloop()