You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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卡顿,且无法处理后续其他消息。
  • 缺失图片转发逻辑:原服务器仅将图片保存到本地,未把图片数据转发给其他客户端。

解决方案

核心修改思路

  1. 定义消息类型前缀:用TEXT:标记文本消息,IMAGE:标记图片消息,附带文件名和大小信息,让服务器能区分处理。
  2. 服务器先读取消息头,根据类型处理文本或图片,完成后转发给其他客户端。
  3. 客户端发送图片时先传消息头,再发数据;接收时解析头信息,区分显示文本或图片。

修改后的服务器代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:15:06