Socket聊天GUI图片消息转发异常:服务器未转发且界面卡顿
问题描述
我用Socket开发了一款聊天GUI程序,要实现文本和图片消息发送功能。目前图片能发到服务器,但服务器没法转发给其他客户端;启用服务器端图片处理代码后,GUI界面会卡顿,还发不了文本消息,只能发一次图片。
原服务器代码
from socket import * from threading import * clients=[] names =[] def clientThread(client): initname = client.recv(1024).decode('utf-8') bayrak = True while True: try: if bayrak: names.append(initname) print(initname, 'connect') bayrak = False ''' if client.recv(1024).decode('utf8') == "came to photo": filepath = client.recv(1024).decode('utf-8') filepath = filepath.split("/")[-1] file = open(filepath, "wb") image_chunk = client.recv(1024) while image_chunk: file.write(image_chunk) image_chunk = client.recv(1024) file.close() ''' message = client.recv(1024).decode('utf-8') for c in clients: if c != client: index = clients.index(client) name = names[index] c.send((name + ':' + message).encode('utf-8')) except: index = clients.index(client) clients.remove(client) name = names[index] names.remove(name) print(name + 'out') break server = socket(AF_INET, SOCK_STREAM) ip= 'localhost' port = 8081 server.bind((ip,port)) server.listen() print('Server is listening') while True: client, address = server.accept() clients.append(client) print('Connect', address[0]+ ':' + str(address[1])) thread = Thread(target=clientThread, args=(client,)) thread.start()
原客户端代码
client = socket(AF_INET, SOCK_STREAM) ip= 'localhost' port = 8081 client.connect((ip, port)) pencere = Tk() pencere.title('Chat') messages =Text(pencere, width=50) messages.grid(row=0, column=0, padx=10, pady=10) yourMessage = Entry(pencere, width=50) yourMessage.insert(0, 'Name') yourMessage.grid(row=1,column=0,padx=10,pady=10) yourMessage.focus() yourMessage.select_range(0, END) def sendMessage(): clientMessage = yourMessage.get() messages.insert(END, '\n'+ 'You: ' + clientMessage) client.send(clientMessage.encode('utf-8')) yourMessage.delete(0, END) def openFile(): client.send("came to photo".encode("utf-8")) filepath = fd.askopenfilename() client.send(filepath.encode("utf-8")) file = open(filepath,"rb") data = file.read(1024) while data: client.send(data) data = file.read(1024) file.close() bmessageGonder = Button(pencere, text= 'Send',command=sendMessage) bmessageGonder.grid(row=2,column=0,padx=5,pady=5) dosyaSec = Button(pencere, text = 'Select Photo', command=openFile) dosyaSec.grid(row=3,column=0,padx=5,pady=5) def recvMessage(): while True: serverMessage = client.recv(1024).decode('utf-8') messages.insert(END, '\n' + serverMessage) recvThread = Thread(target=recvMessage) recvThread.daemon= True recvThread.start() pencere.mainloop()
问题原因分析
- 消息无明确标识:服务器和客户端没区分文本、图片消息的规则,启用图片代码后,服务器会优先等待图片标识,导致文本消息接收逻辑被阻塞。
- 无消息边界处理:图片接收用无限循环等待数据,会占用线程资源,引发GUI卡顿,且无法处理后续其他消息。
- 缺失图片转发逻辑:原服务器仅将图片保存到本地,未把图片数据转发给其他客户端。
解决方案
核心修改思路
- 定义消息类型前缀:用
TEXT:标记文本消息,IMAGE:标记图片消息,附带文件名和大小信息,让服务器能区分处理。 - 服务器先读取消息头,根据类型处理文本或图片,完成后转发给其他客户端。
- 客户端发送图片时先传消息头,再发数据;接收时解析头信息,区分显示文本或图片。
修改后的服务器代码
from socket import * from threading import * clients = [] names = [] def clientThread(client): # 接收客户端用户名 initname = client.recv(1024).decode('utf-8').strip() names.append(initname) print(f"{initname} 已连接") while True: try: # 先接收消息头,获取消息类型 header = client.recv(1024).decode('utf-8').strip() if not header: break if header.startswith("TEXT:"): # 处理文本消息 message = header[5:] # 转发给其他客户端 for c in clients: if c != client: c.send(f"{initname}: {message}".encode('utf-8')) elif header.startswith("IMAGE:"): # 解析图片消息头:IMAGE:文件名:文件大小 _, filename, file_size = header.split(":", 2) file_size = int(file_size) # 接收图片数据 received_size = 0 image_data = b"" while received_size < file_size: chunk = client.recv(min(1024, file_size - received_size)) if not chunk: break image_data += chunk received_size += len(chunk) # 转发图片给其他客户端:先发送头,再发数据 for c in clients: if c != client: c.send(header.encode('utf-8')) c.send(image_data) # 可选:保存图片到本地 with open(filename, "wb") as f: f.write(image_data) print(f"{initname} 发送的图片 {filename} 已保存并转发") except Exception as e: print(f"错误:{e}") break # 客户端断开后的清理 index = clients.index(client) clients.remove(client) names.remove(initname) print(f"{initname} 已断开连接") client.close() server = socket(AF_INET, SOCK_STREAM) ip = 'localhost' port = 8081 server.bind((ip, port)) server.listen() print('服务器正在监听...') while True: client, address = server.accept() clients.append(client) print(f"已连接客户端:{address[0]}:{address[1]}") thread = Thread(target=clientThread, args=(client,)) thread.start()
修改后的客户端代码
from socket import * from tkinter import Tk, Text, Entry, Button, filedialog as fd from threading import Thread client = socket(AF_INET, SOCK_STREAM) ip = 'localhost' port = 8081 client.connect((ip, port)) pencere = Tk() pencere.title('聊天程序') messages = Text(pencere, width=50) messages.grid(row=0, column=0, padx=10, pady=10) yourMessage = Entry(pencere, width=50) yourMessage.insert(0, '请输入用户名') yourMessage.grid(row=1, column=0, padx=10, pady=10) yourMessage.focus() yourMessage.select_range(0, END) # 先发送用户名到服务器 def send_username(event=None): username = yourMessage.get().strip() if username: client.send(username.encode('utf-8')) yourMessage.delete(0, END) yourMessage.insert(0, '输入消息...') yourMessage.unbind('<Return>') yourMessage.bind('<Return>', send_message) def send_message(event=None): clientMessage = yourMessage.get().strip() if clientMessage: # 发送文本消息,带TEXT:前缀 messages.insert('end', f'\n你: {clientMessage}') client.send(f"TEXT:{clientMessage}".encode('utf-8')) yourMessage.delete(0, END) def open_file(): filepath = fd.askopenfilename(filetypes=[("图片文件", "*.png;*.jpg;*.jpeg")]) if not filepath: return filename = filepath.split("/")[-1] # 获取文件大小 with open(filepath, "rb") as f: f.seek(0, 2) file_size = f.tell() f.seek(0) # 发送图片消息头:IMAGE:文件名:文件大小 client.send(f"IMAGE:{filename}:{file_size}".encode('utf-8')) # 发送图片数据 data = f.read(1024) while data: client.send(data) data = f.read(1024) messages.insert('end', f'\n你: 发送了图片 {filename}') def recv_message(): while True: try: # 接收消息头 header = client.recv(1024).decode('utf-8').strip() if not header: break if header.startswith("TEXT:"): # 处理文本消息 message = header[5:] messages.insert('end', f'\n{message}') elif header.startswith("IMAGE:"): # 解析图片头 _, filename, file_size = header.split(":", 2) file_size = int(file_size) # 接收图片数据 received_size = 0 image_data = b"" while received_size < file_size: chunk = client.recv(min(1024, file_size - received_size)) if not chunk: break image_data += chunk received_size += len(chunk) # 保存图片到本地并显示提示 save_path = f"received_{filename}" with open(save_path, "wb") as f: f.write(image_data) messages.insert('end', f'\n收到图片:{save_path}(已保存到本地)') except Exception as e: print(f"接收错误:{e}") break # 绑定回车键发送用户名 yourMessage.bind('<Return>', send_username) bmessageGonder = Button(pencere, text='发送', command=send_message) bmessageGonder.grid(row=2, column=0, padx=5, pady=5) dosyaSec = Button(pencere, text='选择图片', command=open_file) dosyaSec.grid(row=3, column=0, padx=5, pady=5) recvThread = Thread(target=recv_message) recvThread.daemon = True recvThread.start() pencere.mainloop()
关键修改说明
- 标准化消息协议:新增
TEXT:和IMAGE:前缀,让服务器准确区分消息类型,避免接收逻辑混乱。 - 处理消息边界:发送图片时附带文件大小,服务器按大小接收数据,不会无限等待,解决阻塞问题。
- 新增图片转发:服务器接收图片后直接转发给其他客户端,同时保留本地保存的可选功能。
- 优化客户端流程:先发送用户名,再处理消息发送,回车键绑定逻辑更合理,避免误操作。
内容的提问来源于stack exchange,提问作者azoss
相关产品推荐
相关产品推荐

