[Python]基于本地局域网的聊天程序

摘要:基于本地局域网的聊天程序源码

源码

使用python编写,运用 socket和tkinter等模块实现多人在线聊天,拥有基础GUI界面

import socket
import threading
import sys
import time
import tkinter as tk
from tkinter import scrolledtext, messagebox
class ChatServer:
    def __init__(self, host='0.0.0.0', port=12345):
        self.host = host
        self.port = port
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind((host, port))
        self.server.listen(5)
        self.clients = []
        self.nicknames = []
        self.heartbeat_interval = 30  # 心跳检测间隔(秒)
        self.last_heartbeat = {}

    def broadcast(self, message):
        for client in self.clients:
            client.send(message)

    def handle(self, client):
        index = self.clients.index(client)
        nickname = self.nicknames[index]
        self.last_heartbeat[client] = time.time()
        
        while True:
            try:
                message = client.recv(1024)
                if message == b'HEARTBEAT':
                    self.last_heartbeat[client] = time.time()
                    continue
                self.broadcast(message)
            except:
                index = self.clients.index(client)
                self.clients.remove(client)
                client.close()
                nickname = self.nicknames[index]
                self.broadcast(f'{nickname} 离开了聊天室!'.encode('utf-8'))
                self.nicknames.remove(nickname)
                if client in self.last_heartbeat:
                    del self.last_heartbeat[client]
                break

    def receive(self):
        print('服务器已启动,等待连接...')
        while True:
            try:
                client, address = self.server.accept()
                print(f'已连接: {str(address)}')
            except socket.error as e:
                print(f'接受连接时出错: {e}')
                continue

            try:
                client.send('NICK'.encode('utf-8'))
                nickname = client.recv(1024).decode('utf-8')
                if not nickname:
                    raise ConnectionError('Empty nickname received')
                self.nicknames.append(nickname)
                self.clients.append(client)
            except (ConnectionError, socket.error) as e:
                print(f'客户端连接异常: {e}')
                client.close()
                continue

            print(f'昵称是: {nickname}')
            self.broadcast(f'{nickname} 加入了聊天室!'.encode('utf-8'))
            client.send('已连接到服务器!'.encode('utf-8'))

            thread = threading.Thread(target=self.handle, args=(client,))
            thread.start()

class ChatClientGUI(tk.Tk):
    def __init__(self, host='127.0.0.1', port=12345):
        super().__init__()
        self.title("聊天室客户端")
        self.geometry("600x800")
        self.configure(bg='#f0f0f0')
        
        # 昵称输入
        self.nickname_frame = tk.Frame(self, bg='#f0f0f0')
        self.nickname_frame.pack(pady=10)
        
        tk.Label(self.nickname_frame, text="输入昵称:", bg='#f0f0f0').pack(side=tk.LEFT)
        self.nickname_entry = tk.Entry(self.nickname_frame, width=30)
        self.nickname_entry.pack(side=tk.LEFT, padx=5)
        tk.Button(self.nickname_frame, text="连接", command=self.connect_server).pack(side=tk.LEFT)
        
        # 聊天显示区
        self.chat_frame = tk.Frame(self)
        self.chat_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
        
        self.chat_area = scrolledtext.ScrolledText(self.chat_frame, wrap=tk.WORD, state='disabled')
        self.chat_area.pack(fill=tk.BOTH, expand=True)
        
        # 消息输入区
        self.input_frame = tk.Frame(self, bg='#f0f0f0')
        self.input_frame.pack(pady=10, padx=10, fill=tk.X)
        
        self.message_entry = tk.Entry(self.input_frame, width=50)
        self.message_entry.config(font=('Microsoft YaHei', 10))  # 设置支持中文的字体
        self.message_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.message_entry.bind('<Return>', self.send_message)
        self.message_entry.bind('<Key>', lambda e: 'break' if e.keysym == 'Escape' else None)  # 防止ESC键关闭输入法
        
        tk.Button(self.input_frame, text="发送", command=self.send_message).pack(side=tk.LEFT, padx=5)
        
        # 网络连接
        self.client = None
        self.host = host
        self.port = port
        self.nickname = ""
        
    def connect_server(self):
        self.nickname = self.nickname_entry.get().strip()
        if not self.nickname:
            messagebox.showerror("错误", "请输入昵称")
            return
            
        try:
            self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client.connect((self.host, self.port))
            
            # 发送昵称
            self.client.send(self.nickname.encode('utf-8'))
            
            # 启动接收线程
            receive_thread = threading.Thread(target=self.receive, daemon=True)
            receive_thread.start()
            
            # 禁用昵称输入
            self.nickname_entry.config(state='disabled')
            self.nickname_frame.children['!button'].config(state='disabled')
            
            # 启用消息输入
            self.message_entry.config(state='normal')
            
        except ConnectionRefusedError:
            messagebox.showerror("错误", "服务器未运行")
        except socket.error as e:
            messagebox.showerror("错误", f"连接错误: {e}")
    
    def receive(self):
        while True:
            try:
                message = self.client.recv(1024).decode('utf-8')
                self.display_message(message)
            except ConnectionResetError:
                self.display_message("服务器连接已断开")
                self.client.close()
                break
            except socket.error as e:
                self.display_message(f"网络错误: {e}")
                self.client.close()
                break
    
    def send_message(self, event=None):
        message = self.message_entry.get()
        if message and self.client:
            try:
                full_message = f'{self.nickname}: {message}'
                self.client.send(full_message.encode('utf-8'))
                self.message_entry.delete(0, tk.END)
            except UnicodeEncodeError:
                messagebox.showerror("编码错误", "无法发送包含特殊字符的消息")
            except socket.error as e:
                messagebox.showerror("网络错误", f"发送失败: {e}")
    
    def display_message(self, message):
        self.chat_area.config(state='normal')
        self.chat_area.insert(tk.END, message + '\n')
        self.chat_area.config(state='disabled')
        self.chat_area.see(tk.END)

class ChatClient:
    def __init__(self, host='127.0.0.1', port=12345):
        self.nickname = input('输入你的昵称: ')
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            self.client.connect((host, port))
        except ConnectionRefusedError:
            print('服务器未运行,请先启动服务器')
            sys.exit(1)
        except socket.error as e:
            print(f'连接错误: {e}')
            sys.exit(1)

    def receive(self):
        while True:
            try:
                message = self.client.recv(1024).decode('utf-8')
                if message == 'NICK':
                    self.client.send(self.nickname.encode('utf-8'))
                else:
                    print(message)
            except ConnectionResetError:
                print('服务器连接已断开')
                self.client.close()
                break
            except socket.error as e:
                print(f'网络错误: {e}')
                self.client.close()
                break

    def write(self):
        while True:
            message = f'{self.nickname}: {input("")}'
            self.client.send(message.encode('utf-8'))

def check_server_running(host='127.0.0.1', port=12345):
    try:
        test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        test_socket.settimeout(1)
        test_socket.connect((host, port))
        test_socket.close()
        return True
    except:
        return False

def find_available_port(start_port=12345, max_tries=100):
    for port in range(start_port, start_port + max_tries):
        try:
            test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            test_socket.bind(('127.0.0.1', port))
            test_socket.close()
            return port
        except:
            continue
    return None

if __name__ == '__main__':
    if check_server_running():
        port = find_available_port()
        if port is None:
            print('无法找到可用端口,请稍后再试')
            sys.exit(1)
        # 使用GUI客户端
        app = ChatClientGUI(port=port)
        app.mainloop()
    else:
        choice = input('未检测到服务器,是否作为服务器启动?(y/n): ')
        if choice.lower() == 'y':
            print('[!]你当前作为服务器启动,可以再次启动程序并使用客户端启动')
            print('[!]请不要关闭此窗口')
            server = ChatServer()
            server.receive()
        else:
            port = find_available_port()
            if port is None:
                print('无法找到可用端口,请稍后再试')
                sys.exit(1)
            # 使用GUI客户端
            app = ChatClientGUI(port=port)
            app.mainloop()
posted @ 2025-05-09 21:30  XQ4564  阅读(43)  评论(0)    收藏  举报