笙芝 发表于 2025-5-31 23:33:57

[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
      self.last_heartbeat = time.time()
      
      while True:
            try:
                message = client.recv(1024)
                if message == b'HEARTBEAT':
                  self.last_heartbeat = time.time()
                  continue
                self.broadcast(message)
            except:
                index = self.clients.index(client)
                self.clients.remove(client)
                client.close()
                nickname = self.nicknames
                self.broadcast(f'{nickname} 离开了聊天室!'.encode('utf-8'))
                self.nicknames.remove(nickname)
                if client in self.last_heartbeat:
                  del self.last_heartbeat
                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()
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: [Python]基于本地局域网的聊天程序