Python 套接字远程聊天代码

Python 远程聊天代码

要实现一个远程聊天代码,我们需要使用 socket 进行远程连接,使用多线程 threading 同时接收消息和界面输入。

前置知识

Socket 套接字

Python 内置的 socket 模块正是为网络远程通信而设计,它提供了底层的网络访问接口,允许我们直接控制数据包的发送与接收过程,从而实现自定义的网络协议设计。

在深入了解socket模块之前,首先我们需要明确几个核心概念:

  • 套接字(Socket):用于描述IP地址和端口的组合,它是网络通信的基本单位。
  • 导入库:import socket
  • 创建套接字:使用 socket.socket() 函数创建一个新的套接字对象。
  • 绑定地址:通过调用 bind() 方法将套接字与特定的本地地址(即IP地址和端口号)关联起来。
  • 监听连接:服务器端需要调用 listen() 方法进入监听状态,等待客户端发起连接请求。
  • 接受连接:当有新的连接请求到达时,服务器可以通过 accept() 方法接受这个连接,并返回一个新套接字用于后续通信。
  • 发送/接收数据:使用 sendall()recv() 方法进行数据的发送与接收操作。

内容摘自腾讯开发者社区,有删改。

Threading 多线程

线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以和其他线程同时运行。

内容摘自博客园,有删改。

函数介绍详见此文

服务器端

import socket
import threading
import tkinter as tk
from tkinter import messagebox
from tkinter import font as tkFont

class ChatServer:
    def __init__(self, master):
        # 初始化聊天服务器
        self.master = master
        self.master.title("聊天服务器 Chat Server")
        self.setup_widgets()  # 设置界面组件
        self.sock = None  # 套接字初始化
        self.start = False  # 服务器启动状态
        self.clients = []  # 存储连接的客户端
        self.client_names = {}  # 存储客户端名称
        self.muted_clients = set()  # 存储被禁言的客户端

    def setup_widgets(self):
        # 设置界面组件
        font = tkFont.Font(family="华文楷体", size=12)

        self.messages = tk.Text(self.master, state='disabled', font=font)  # 消息显示框
        self.messages.grid(row=0, column=0, columnspan=2)

        self.client_listbox = tk.Listbox(self.master, font=font)  # 客户端列表框
        self.client_listbox.grid(row=0, column=2, rowspan=2)

        self.input_server = tk.Entry(self.master, font=font)  # 服务器输入框
        self.input_server.grid(row=2, column=0)

        self.send_button = tk.Button(self.master, text="发送 Send", command=self.send_server_message, font=font)  # 发送按钮
        self.send_button.grid(row=3, column=0)

        self.kick_button = tk.Button(self.master, text="踢出 Kick", command=self.kick_selected_user, font=font)  # 踢出按钮
        self.kick_button.grid(row=3, column=1)

        self.mute_button = tk.Button(self.master, text="禁言 Mute", command=self.mute_selected_user, font=font)  # 禁言按钮
        self.mute_button.grid(row=4, column=1)

        self.unmute_button = tk.Button(self.master, text="解除禁言 Unmute", command=self.unmute_selected_user, font=font)  # 解除禁言按钮
        self.unmute_button.grid(row=5, column=1)

        tk.Label(self.master, text="服务器IP地址 Server IP address: " + str(socket.gethostbyname(socket.gethostname())), font=font).grid(row=1, column=2)  # 显示服务器IP地址

        self.start_button = tk.Button(self.master, text="启动服务器 Start server", command=self.start_server, font=font)  # 启动服务器按钮
        self.start_button.grid(row=1, column=0)

        self.master.bind("<Return>", self.send_server_message_event)  # 绑定回车键发送消息

        self.master.protocol("WM_DELETE_WINDOW", self.on_close)  # 关闭窗口时的处理

    def start_server(self):
        # 启动聊天服务器
        if self.start == True:
            messagebox.showerror("服务器已启动 The server is up", "服务器已启动,请勿重复启动服务器!The server has been started, do not start the server repeatedly!")  # 已启动提示
            return
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建套接字
        self.sock.bind(('0.0.0.0', 8888))  # 绑定IP和端口
        self.sock.listen(1000)  # 监听连接
        threading.Thread(target=self.accept_connections, daemon=True).start()  # 启动接收连接的线程
        self.start = True  # 设置服务器为启动状态

    def accept_connections(self):
        # 接受客户端连接
        while True:
            client, addr = self.sock.accept()  # 接受连接
            self.clients.append(client)  # 添加到客户端列表
            threading.Thread(target=self.handle_client, args=(client,), daemon=True).start()  # 启动处理客户端的线程

    def handle_client(self, client):
        # 处理客户端消息
        while True:
            try:
                data = client.recv(1024)  # 接收数据
                message = data.decode('utf-8')  # 解码消息
                if message.startswith("/kick "):
                    self.kick_user(message.split()[1])  # 踢出用户
                elif message.startswith("/mute "):
                    self.mute_user(message.split()[1])  # 禁言用户
                elif message.startswith("/unmute "):
                    self.unmute_user(message.split()[1])  # 解除禁言用户
                elif message.startswith("/name "):
                    self.client_names[client] = message.split()[1]  # 更新客户端名称
                    self.update_client_list()  # 更新用户列表
                elif client not in self.muted_clients:
                    for other in self.clients:
                        if other != client:
                            other.sendall(data)  # 发送消息给其他客户端
                    self.messages.configure(state='normal')
                    self.messages.insert(tk.END, message + '\n')  # 显示消息
                    self.messages.configure(state='disabled')
                    self.messages.yview(tk.END)  # 滚动到消息框底部
            except socket.error as e:
                self.clients.remove(client)  # 移除断开的客户端
                if client in self.client_names:
                    del self.client_names[client]  # 删除客户端名称
                self.update_client_list()  # 更新用户列表
                break

    def send_server_message_event(self, event=None):
        # 发送服务器消息
        self.send_server_message()

    def send_server_message(self):
        # 发送消息到所有客户端
        message = self.input_server.get()  # 获取输入框的消息
        if message.startswith("/kick "):
            self.kick_user(message.split()[1])  # 踢出用户
        elif message.startswith("/mute "):
            self.mute_user(message.split()[1])  # 禁言用户
        elif message.startswith("/unmute "):
            self.unmute_user(message.split()[1])  # 解除禁言用户
        else:
            message = f"服务器 Chat server: {message}"  # 格式化消息
            for client in self.clients:
                client.sendall(message.encode('utf-8'))  # 发送消息到所有客户端
            self.messages.configure(state='normal')
            self.messages.insert(tk.END, message + '\n')  # 显示发送的消息
            self.messages.configure(state='disabled')
            self.messages.yview(tk.END)  # 滚动到消息框底部
        self.input_server.delete(0, tk.END)  # 清空输入框

    def kick_user(self, username):
        # 踢出指定用户
        for client, name in self.client_names.items():
            if name == username:
                client.sendall(f"{username} 已被踢出聊天室。{username} ,you have been kicked out of the chat room.".encode('utf-8'))  # 通知用户被踢出
                client.close()  # 关闭连接
                self.clients.remove(client)  # 从客户端列表中移除
                del self.client_names[client]  # 删除客户端名称
                self.update_client_list()  # 更新用户列表
                break

    def mute_user(self, username):
        # 禁言指定用户
        for client, name in self.client_names.items():
            if name == username:
                self.muted_clients.add(client)  # 添加到禁言列表
                client.sendall(f"用户 {username} 被禁言。{username} ,you have been banned.".encode('utf-8'))  # 通知用户被禁言
                break

    def unmute_user(self, username):
        # 解除禁言指定用户
        for client, name in self.client_names.items():
            if name == username:
                self.muted_clients.discard(client)  # 从禁言列表中移除
                client.sendall(f"用户 {username} 被解除禁言。{username} ,you have been unbanned.".encode('utf-8'))  # 通知用户解除禁言
                break

    def kick_selected_user(self):
        # 踢出选中的用户
        selected_user = self.client_listbox.get(tk.ACTIVE)  # 获取选中的用户
        if selected_user:
            self.kick_user(selected_user)  # 踢出用户

    def mute_selected_user(self):
        # 禁言选中的用户
        selected_user = self.client_listbox.get(tk.ACTIVE)  # 获取选中的用户
        if selected_user:
            self.mute_user(selected_user)  # 禁言用户

    def unmute_selected_user(self):
        # 解除禁言选中的用户
        selected_user = self.client_listbox.get(tk.ACTIVE)  # 获取选中的用户
        if selected_user:
            self.unmute_user(selected_user)  # 解除禁言用户

    def update_client_list(self):
        # 更新用户列表
        self.client_listbox.delete(0, tk.END)  # 清空列表框
        for name in self.client_names.values():
            self.client_listbox.insert(tk.END, name)  # 添加用户到列表框
        # 向所有客户端发送更新后的用户列表
        client_list_message = "/name " + " ".join(self.client_names.values())  # 格式化用户列表消息
        for client in self.clients:
            client.sendall(client_list_message.encode('utf-8'))  # 发送用户列表到所有客户端

    def on_close(self):
        # 关闭服务器
        if self.sock:
            for client in self.clients:
                client.sendall(b"/quit")  # 通知所有客户端服务器关闭
                client.close()  # 关闭连接
            self.sock.close()  # 关闭套接字
        self.master.destroy()  # 销毁窗口

if __name__ == '__main__':
    root = tk.Tk()  # 创建主窗口
    server = ChatServer(root)  # 创建聊天服务器实例
    root.mainloop()  # 运行主循环
客户端
import socket
import threading
import tkinter as tk
from tkinter import messagebox, simpledialog
from tkinter import font as tkFont

class ChatClient:
   def __init__(self, master):
       # 初始化聊天客户端
       self.master = master
       self.master.title("聊天客户端 Chat Client")
       self.setup_widgets()  # 设置界面组件
       self.sock = None  # 套接字初始化
       self.muted = False  # 用户是否被禁言的状态

   def setup_widgets(self):
       # 设置界面组件
       font = tkFont.Font(family="华文楷体", size=12)

       self.messages = tk.Text(self.master, state='disabled', font=font)  # 消息显示框
       self.messages.grid(row=0, column=0, columnspan=2)

       self.client_listbox = tk.Listbox(self.master, font=font)  # 客户端列表框
       self.client_listbox.grid(row=0, column=2, rowspan=2)

       self.input_user = tk.Entry(self.master, font=font)  # 用户输入框
       self.input_user.grid(row=1, column=0)
       self.send_button = tk.Button(self.master, text="发送 Sent", command=self.send_message, font=font)  # 发送按钮
       self.send_button.grid(row=1, column=1)
       
       self.master.bind("<Return>", self.send_message)  # 绑定回车键发送消息

       self.master.protocol("WM_DELETE_WINDOW", self.on_close)  # 关闭窗口时的处理

   def connect(self, host, port):
       # 连接到聊天服务器
       self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建套接字
       try:
           self.sock.connect((host, port))  # 连接到指定的主机和端口
           threading.Thread(target=self.receive_message, args=(user,), daemon=True).start()  # 启动接收消息的线程
       except socket.error as e:
           messagebox.showerror("连接错误 Connect Error", f"无法连接到服务器 Can't connect to server: {e}")  # 连接失败提示
           self.master.destroy()  # 销毁窗口
       else:
           messagebox.showinfo("连接成功 Connected accept.", f"已连接到服务器 {host}:{port} Connected to server.")  # 连接成功提示
           self.sock.sendall(f"/name {user}".encode('utf-8'))  # 发送用户名到服务器
           self.sock.sendall(f"用户 {user} 加入了聊天室。User {user} has joined the chat room.".encode('utf-8'))  # 通知其他用户

   def send_message(self, event=None):
       # 发送消息
       if self.muted:
           messagebox.showwarning("禁言 Banned", "你已被禁言,无法发送消息。You have been banned from sending messages.")  # 被禁言提示
           return
       message = f"{user}: " + self.input_user.get()  # 获取用户输入的消息
       if message:
           try:
               self.sock.sendall(message.encode('utf-8'))  # 发送消息
               self.input_user.delete(0, tk.END)  # 清空输入框
               self.messages.configure(state='normal')  # 使消息框可编辑
               self.messages.insert(tk.END, message + '\n')  # 显示发送的消息
               self.messages.configure(state='disabled')  # 使消息框不可编辑
               self.messages.yview(tk.END)  # 滚动到消息框底部
           except socket.error as e:
               messagebox.showerror("发送错误 Sent Error", f"无法发送消息 Can't to sent message: {e}")  # 发送失败提示

   def receive_message(self, username):
       # 接收消息
       while True:
           try:
               data = self.sock.recv(1024)  # 接收数据
               message = data.decode('utf-8')  # 解码消息
               if message.startswith("/name "):
                   self.update_client_list(message.split()[1:])  # 更新用户列表
                   self.messages.configure(state='normal')
                   self.messages.insert(tk.END, '正在刷新成员列表。Refreshing user list.\n')  # 刷新提示
                   self.messages.configure(state='disabled')
                   self.messages.yview(tk.END)
               elif message == f"{username} 已被踢出聊天室。{username} ,you have been kicked out of the chat room.":
                   messagebox.showinfo("提示 Prompt", "你已被踢出聊天室。You have been kicked out of the chat room.")  # 被踢出提示
                   self.on_close()  # 关闭连接
                   break
               elif message == f"用户 {username} 被禁言。{username} ,you have been banned.":
                   self.muted = True  # 设置为禁言状态
                   messagebox.showinfo("提示", "你已被禁言。You have been banned.")  # 禁言提示
               elif message == f"用户 {username} 被解除禁言。{username} ,you have been unbanned.":
                   self.muted = False  # 解除禁言状态
                   messagebox.showinfo("提示", "你已被解除禁言。You have been unbanned.")  # 解除禁言提示
               elif message == f"/quit":
                   messagebox.showinfo("提示", "服务器已关闭。The server has been closed.")  # 服务器关闭提示
                   self.on_close()  # 关闭连接
                   break
               else:
                   self.messages.configure(state='normal')
                   self.messages.insert(tk.END, message + '\n')  # 显示接收到的消息
                   self.messages.configure(state='disabled')
                   self.messages.yview(tk.END)
           except socket.error as e:
               print(f"接收错误 Receive error: {e}")  # 接收错误提示
               break

   def update_client_list(self, names):
       # 更新用户列表
       self.client_listbox.delete(0, tk.END)  # 清空列表框
       for name in names:
           self.client_listbox.insert(tk.END, name)  # 添加用户到列表框

   def on_close(self):
       # 关闭连接
       if self.sock:
           if user != '':
               self.sock.sendall(f"用户 {user} 离开了聊天室。User {user} has left the chat room.".encode('utf-8'))  # 通知用户离开
           else:
               self.sock.sendall("无名用户离开了聊天室。Unnamed user has left the chat room.".encode('utf-8'))  # 通知无名用户离开
           self.sock.close()  # 关闭套接字
       self.master.destroy()  # 销毁窗口

if __name__ == '__main__':
   root = tk.Tk()  # 创建主窗口
   client = ChatClient(root)  # 创建聊天客户端实例
   host = simpledialog.askstring("连接 Connect", "服务器地址 IP: ", initialvalue="127.0.0.1")  # 获取服务器地址
   port = simpledialog.askinteger("连接 Connect", "端口号 Port: ", initialvalue=8888)  # 获取端口号
   user = simpledialog.askstring("连接 Connect", "用户名 Username: ")  # 获取用户名
   client.connect(host, port)  # 连接到服务器
   root.mainloop()  # 运行主循环

源码地址Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值