【细节呈现】基于UDP协议与多进程使用Python开发的即时聊天室

项目简介

该项目是一个基于UDP协议实现的即时聊天室,使用Python语言开发。用户可以通过客户端加入聊天室,发送消息、退出聊天室。管理员可以发送命令,移除用户或查询用户信息。

GitHub : https://github.com/ITchujian/chat_room

开发环境

  • Pycharm
  • Linux Mint

项目实现

自定义协议

  • “join#username”: 客户端向服务端发送此协议以加入聊天室,其中"username"为客户端指定的用户名。
  • “duplicated”: 服务端向客户端发送此协议,表示该客户端指定的用户名已经被其他客户端占用,加入聊天室失败。
  • “to_join”:服务端向客户端发送此协议,表示客户端成功加入聊天室。
  • “to_join_admin”:服务端向管理员客户端发送此协议,表示管理员身份验证成功,成功加入聊天室。
  • “quit#username”:客户端向服务端发送此协议以退出聊天室,其中"username"为客户端的用户名。
  • “quit#admin”:管理员客户端向服务端发送此协议以退出聊天室。
  • “{datetime.now().strftime(‘%Y-%m-%d %H:%M:%S’)} > {data.decode(‘utf-8’).split(‘#’)[0]} {data.decode(‘utf-8’).split(': ')[1]}”:服务端向所有客户端广播消息时,如果是管理员发送的指令,会采用此格式输出。
  • “remove#name”: 管理员客户端向服务端发送此协议以移除指定的成员,其中"name"为要移除的成员的用户名。
  • “remove@you”: 服务端向被移除的客户端发送此协议,表示该客户端已经被管理员移除聊天室。
  • “name#name”:管理员客户端向服务端发送此协议以查询指定成员的IP地址和端口号,其中"name"为要查询的成员的用户名。服务端将查询结果返回给管理员客户端。

服务端

  1. ChatServer

该类是服务器端的主要实现,包含以下方法:

  • __init__(self, host, port) :初始化ChatServer类实例,其中host参数为服务器IP地址,port参数为服务器端口号。
  • start(self) :服务端开启,监听客户端请求。
  • send_to_all(self, msg, sender_address) :将消息广播给所有客户端。
  • is_duplicate_username(self, name) :检查新加入的客户端是否与已有客户端用户名重复。
  • execute_cmd(self, cmd, address, admin_addr) :通过分析命令,解析管理员操作。
  • remove_user(self, name) :移除指定用户名的用户。
  • find_addr_by_name(self, name) :查找指定用户名的用户地址。
  1. 运行服务器
if __name__ == '__main__':
    server = ChatServer('127.0.0.1', 1234)
    server.start()
  1. 完整代码
"""
    即时聊天室服务端
    该服务端通过UDP协议实现即时聊天室功能。
"""
import socket
import time
from datetime import datetime


class ChatServer:
    def __init__(self, host: str, port: int):
        """
        初始化ChatServer类实例。
        参数:
            host: string类型,服务器IP地址
            port: int类型,服务器端口号
        返回:
            None
        """
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.clients = {}
        self.administer = {
            "name": "管理员大人",
            "password": "admin",
            "addr": None
        }

    def start(self):
        """
        服务端开启,监听客户端请求。
        参数:
            None
        返回:
            None
        """
        print('Starting server...')
        self.sock.bind((self.host, self.port))
        while True:
            data, address = self.sock.recvfrom(1024)
            if data.decode('utf-8').split("#")[0] == 'join':
                if self.is_duplicate_username(data.decode('utf-8').split("#")[1]):
                    self.sock.sendto('duplicated'.encode('utf-8'), address)
                else:
                    if len(data.decode().split("#")) == 3 and data.decode('utf-8').split('#')[1] == self.administer[
                        "name"] and \
                            data.decode('utf-8').split('#')[2] == self.administer["password"]:
                        self.sock.sendto('to_join_admin'.encode('utf-8'), address)
                        self.clients[address] = "admin"
                        self.administer["addr"] = address
                        time.sleep(0.02)
                        self.send_to_all(f'to_join_admin', address)
                    else:
                        self.sock.sendto('to_join'.encode('utf-8'), address)
                        self.clients[address] = data.decode('utf-8').split("#")[1]
                        time.sleep(0.02)
                        self.send_to_all(f'join#{self.clients[address]}', address)
            elif data.decode('utf-8').split("#")[0] == 'quit':
                self.send_to_all(f'quit#{self.clients[address]}', address)
                del self.clients[address]
            else:
                if address == self.administer["addr"]:
                    print(data.decode())
                    if " " in data.decode() and len(data.decode().split(" ")) == 3:
                        self.execute_cmd(data.decode("utf-8"), self.find_addr_by_name(data.decode().split(" ")[2]),
                                         address)
                    else:
                        self.send_to_all(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} > {data.decode('utf-8').split('#')[0]} {data.decode('utf-8').split(': ')[1]}", address)
                else:
                    self.send_to_all(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} > {data.decode('utf-8')}", address)

    def send_to_all(self, msg, sender_address):
        """
        将消息广播给所有客户端。
        参数:
            msg: string类型,要广播的消息内容
            sender_address: tuple类型,发送消息的客户端地址
        返回:
            None
        """
        for client_address in self.clients:
            if client_address != sender_address:
                self.sock.sendto(msg.encode('utf-8'), client_address)

    def is_duplicate_username(self, name):
        """
        检查新加入的客户端是否与已有客户端用户名重复。
        参数:
            name: string类型,新加入客户端的用户名
        返回:
            bool类型,如果重复返回True,否则返回False
        """
        if name in self.clients.values():
            return True
        else:
            return False

    def execute_cmd(self, cmd, address, admin_addr):
        """
        通过分析命令,解析管理员操作
        """

        operate = cmd.split(" ")[1]
        name = cmd.split(" ")[2]
        if operate == "remove":
            if self.remove_user(name):
                self.sock.sendto("您因违规已被移除聊天室!".encode(), address)
                time.sleep(0.01)
                self.send_to_all(f"成员 {name} 已经被移除聊天室!", address)
                self.sock.sendto("remove@you".encode(), address)
        elif operate == "name":
            self.sock.sendto(
                f"姓名:{name}\nIP地址:{self.find_addr_by_name(name)[0]}\n端口号:{self.find_addr_by_name(name)[1]}\n".encode(),
                admin_addr)

    def remove_user(self, name):
        for k, v in self.clients.items():
            if v == name:
                del [k]
                return True
        return False

    def find_addr_by_name(self, name):
        for k, v in self.clients.items():
            if v == name:
                return k
        return None, None


if __name__ == '__main__':
    server = ChatServer('127.0.0.1', 1234)
    server.start()

客户端

  1. 实现思路

我们需要实现的聊天室客户端具有以下功能:

  • 初始化连接主机地址、端口号和客户端的用户名;
  • 向服务端发送 ‘join’ 消息,以加入聊天室;
  • 开始接收并打印聊天室中的消息;
  • 发送消息至聊天室。
  • 为了实现以上功能,我们需要使用到Python内置的socket模块和multiprocessing模块。其中,socket模块用于与服务端进行数据通信,而multiprocessing模块则用于实现客户端与服务端同时进行数据通信的功能。
  1. 代码实现
"""
    即时聊天室客户端
    该服务端通过UDP协议实现即时聊天室功能。
"""
import socket
import multiprocessing


class ChatClient:
    def __init__(self, host: str, port: int, name: str):
        """
        初始化客户端,包括指定连接的主机地址、端口号和客户端的用户名。
        参数:
            host: string类型,服务器IP地址
            port: int类型,服务器端口号
            name: string类型,用户姓名
        返回:
            None
        """
        self.host = host
        self.port = port
        self.name = name
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.receive_process = None
        self.is_remove = False
        self.queue = multiprocessing.Queue()

    def start(self):
        """
        启动聊天室客户端,包括发送 'join' 消息,开始接收并打印聊天室中的消息,以及发送消息。
        参数:
            None
        返回:
            None
        """
        print('正在加入中...')
        self.login()
        self.message_center()

    def login(self):
        """
        客户端登录入口
        参数:
            None
        返回:
            None
        """
        while True:
            self.sock.sendto(f'join#{self.name}'.encode('utf-8'), (self.host, self.port))
            data, address = self.sock.recvfrom(1024)
            if data.decode('utf-8') == 'duplicated':
                print('用户名已被使用,请重新输入!')
                self.name = input('输入你的名字: ')
            elif data.decode('utf-8') == 'to_join_admin':
                print('您加入了聊天室!')
                print("管理员指令表\n1.移除某一用户:remove \n2.查询某一用户:name \n")
                self.receive_process = multiprocessing.Process(target=self.receive)
                self.receive_process.start()
                break
            else:
                print('你加入了聊天室!')
                self.receive_process = multiprocessing.Process(target=self.receive)
                self.receive_process.start()
                break

    def message_center(self):
        """
        客户端消息收发中心,包括发送信息、创建消息监听进程等
        参数:
            None
        返回:
            None
        """
        while True:
            if self.queue.qsize() > 0:
                self.is_remove = self.queue.get()
            if self.is_remove:
                self.receive_process.terminate()
                break
            msg = input('')
            if msg == 'quit':
                self.sock.sendto(f'quit#{self.name}'.encode('utf-8'), (self.host, self.port))
                self.receive_process.terminate()
                break
            else:
                self.sock.sendto(f'{self.name}: {msg}'.encode('utf-8'), (self.host, self.port))

    def receive(self):
        """
        接收并打印聊天室中的消息。
        参数:
            None
        返回:
            None
        """
        while True:
            data, address = self.sock.recvfrom(1024)
            if data.decode('utf-8').split("#")[0] == 'join':
                print(f"{data.decode('utf-8').split('#')[1]}加入了聊天室")
            elif data.decode('utf-8').split('#')[0] == 'quit':
                print(f"{data.decode('utf-8').split('#')[1]}离开了聊天室")
            elif data.decode('utf-8') == 'to_join_admin':
                print("尊贵的管理员已上线,请文明发言!")
            elif data.decode("utf-8") == "remove@you":
                self.queue.put(True)
            else:
                print(data.decode('utf-8'))


if __name__ == '__main__':
    name = input('输入你的名字: ')
    client = ChatClient('127.0.0.1', 1234, name)
    client.start()

你可能感兴趣的:(项目集,python,udp,开发语言)