用python实现多人聊天室小项目笔记

写在前面的絮絮叨叨

本项目用python实现一个简单的网络多人聊天室,我把代码都详细注释了一下,以便后期翻阅学习。代码涉及网络编程的些许知识,假如有人对网络编程有兴趣的话,我建议学学Java网络编程。

运行环境:需安装python3.x和对应python3.x的wxpython库

windows操作系统:pip install wx
Linux操作系统:也可通过pip下载wxpython,进入https://extras.wxpython.org/wxPython4/extras/linux/gtk3/选择相对应操作系统的wxpython.whl进行下载
踩过的坑:假如安装wxpython之后运行代码还是报错:No model named “wx”,就检查一下所安装的wxpython是否是支持python3.x的

项目代码

不带注释总代码行在200行左右,已上传至github:https://github.com/atuo-200/chat_room

server.py

import asynchat
import asyncore
import time

# 定义端口
PORT = 6666

# 定义结束异常类
class EndSession(Exception):
    pass


class ChatServer(asyncore.dispatcher):
    """
    创建一个支持多用户连接的聊天服务器
    """
    #重写构造方法
    def __init__(self, port):
	#显式调用父类构造方法
        asyncore.dispatcher.__init__(self)
        # 创建socket
        self.create_socket()
        # 设置 socket 为可重用
        self.set_reuse_addr()
        # 监听端口
        self.bind(('', port))
	#设置最大连接数为5,超出排队
        self.listen(5)
        self.users = {}
        self.main_room = ChatRoom(self)

    def handle_accept(self):
	#阻塞式监听,等待客户端的连接,生成连接对象(SSL通道,客户端地址)
        conn, addr = self.accept()
	#建立会话
        ChatSession(self, conn)

class ChatSession(asynchat.async_chat):
    """
    负责和客户端通信的会话类
    """

    def __init__(self, server, sock):
        asynchat.async_chat.__init__(self, sock)
        self.server = server
	#设置数据终止符
        self.set_terminator(b'\n')
	#设置数据列表
        self.data = []
        self.name = None
        self.enter(LoginRoom(server))

    def enter(self, room):
        # 从当前房间移除自身,然后添加到指定房间
        try:
            cur = self.room
        except AttributeError:
            pass
        else:
            cur.remove(self)
        self.room = room
        room.add(self)
    #重写处理客户端发来数据的方法
    def collect_incoming_data(self, data):
        # 接收客户端的数据并解码
        self.data.append(data.decode("utf-8"))
    #重写发现数据中终止符号时的处理方法
    def found_terminator(self):
        #将数据列表中的内容整合为一行
        line = ''.join(self.data)
	#清理数据列表
        self.data = []
        try:
            self.room.handle(self, line.encode("utf-8"))
        # 退出聊天室的处理
        except EndSession:
            self.handle_close()

    def handle_close(self):
        # 当 session 关闭时,将进入 LogoutRoom
        asynchat.async_chat.handle_close(self)
        self.enter(LogoutRoom(self.server))

class CommandHandler:
    """
    命令处理类
    """
    #定义未知命令的处理方法
    def unknown(self, session, cmd):
        # 通过 aynchat.async_chat.push 方法发送消息,向客户端发送错误提示
        session.push(('不知名命令 {} \n'.format(cmd)).encode("utf-8"))

    def handle(self, session, line):
	#解码
        line = line.decode()
        #判断去掉空格后是否还有数据
        if not line.strip():
            return
	#把数据以空格分隔符分割生成列表,最大分割数为1
        parts = line.split(' ', 1)
	#分割的第一部分为命令
        cmd = parts[0]
	#将分割后的第二部分去除空格保存到变量
        try:
            line = parts[1].strip()
        except IndexError:
            line = ''
        #获取指定名称的方法对象
        method = getattr(self, 'do_' + cmd, None)
	#调用获取到的方法对象
        try:
            method(session, line)
        except TypeError:
            self.unknown(session, cmd)
class Room(CommandHandler):
    """
    包含多个用户的环境,负责基本的命令处理和广播
    """

    def __init__(self, server):
        self.server = server
	#会话列表
        self.sessions = []

    def add(self, session):
        # 一个用户进入房间
        self.sessions.append(session)

    def remove(self, session):
        # 一个用户离开房间
        self.sessions.remove(session)
    #定义广播信息的处理方法
    def broadcast(self, line):
        #遍历所有用户会话,再使用 asynchat.asyn_chat.push 方法发送数据
        for session in self.sessions:
            session.push(line)

    def do_logout(self, session, line):
        # 退出房间
        raise EndSession


class LoginRoom(Room):
    """
    处理登录用户
    """

    def add(self, session):
        # 用户连接成功的回应
        Room.add(self, session)
        # 使用 asynchat.asyn_chat.push 方法发送数据到客户端
        session.push('连接成功'.encode('utf-8'))

    def do_login(self, session, line):
        # 用户登录逻辑
        name = line.strip()
        # 获取用户名称
        if not name:
            session.push('用户名为空'.encode('utf-8'))
        # 检查是否有同名用户
        elif name in self.server.users:
            session.push('用户名已存在'.encode('utf-8'))
        # 用户名检查成功后,进入主聊天室
        else:
            session.name = name
            session.enter(self.server.main_room)


class LogoutRoom(Room):
    """
    处理退出用户
    """

    def add(self, session):
        # 从服务器中用户字典中移除相关记录
        try:
            del self.server.users[session.name]
        except KeyError:
            pass


class ChatRoom(Room):
    """
    聊天用的房间
    """

    def add(self, session):
        # 广播新用户进入
        session.push('登录成功'.encode('utf-8'))
        self.broadcast((session.name + ' 进入房间\n').encode("utf-8"))
	#向服务器的用户字典添加与会话的用户名相对应的会话
        self.server.users[session.name] = session
        Room.add(self, session)

    def remove(self, session):
        # 广播用户离开
        Room.remove(self, session)
        self.broadcast((session.name + ' 离开房间\n').encode("utf-8"))

    def do_say(self, session, line):
        # 客户端发送消息
        print(line)
        self.broadcast(('time:'+time.strftime('%H:%M:%S',time.localtime(time.time()))+ '\n'+session.name + ': ' + line + '\n').encode("utf-8"))

    def do_look(self, session, line):
        # 查看在线用户
        session.push('在线用户:\n'.encode('utf-8'))
        for other in self.sessions:
            session.push((other.name + '\n').encode("utf-8"))

if __name__ == '__main__':

    s = ChatServer(PORT)
    try:
        print("chat serve run at '127.0.0.1:{0}'".format(PORT))
	#开启循环监听网络事件
        asyncore.loop()
    except KeyboardInterrupt:
        print("chat server exit")

client.py

import wx
import telnetlib
from time import sleep
import _thread as thread

class LoginFrame(wx.Frame):
    """
    登录窗口类,继承wx.Frame类
    """
    #初始化,添加控件
    def __init__(self, parent, id, title, size):
        wx.Frame.__init__(self, parent, id, title)
	#设置窗体大小        
        self.SetSize(size)
	#放置正中央
        self.Center()
	#服务器地址框标签
        self.serverAddressLabel = wx.StaticText(self, label="服务器地址", pos=(10, 50), size=(120, 25))
	#用户名框标签        	
        self.userNameLabel = wx.StaticText(self, label="用户名", pos=(40, 100), size=(120, 25))
	#服务器地址框        
        self.serverAddress = wx.TextCtrl(self, pos=(120, 47), size=(150, 25))
	#用户名框        
        self.userName = wx.TextCtrl(self, pos=(120, 97), size=(150, 25))
	#登录按钮        
        self.loginButton = wx.Button(self, label='登录', pos=(80, 145), size=(130, 30))
        #登录按钮上绑定登录方法
        self.loginButton.Bind(wx.EVT_BUTTON, self.login)
	#显示组件        
        self.Show()

    def login(self, event):
        # 登录处理
        try:
            serverAddress = self.serverAddress.GetLineText(0).split(':')
            con.open(serverAddress[0], port=int(serverAddress[1]), timeout=10)
            response = con.read_some()
            if response != '连接成功'.encode('utf-8'):
                self.showDialog('Error', '连接失败!', (200, 100))
                return
            con.write(('login ' + str(self.userName.GetLineText(0)) + '\n').encode("utf-8"))
            response = con.read_some()
            if response == '用户名为空'.encode('utf-8'):
                self.showDialog('Error', '用户名为空!', (200, 100))
            elif response == '用户名已存在':
                self.showDialog('Error', '用户名已存在!', (200, 100))
            else:
                self.Close()
                ChatFrame(None, 2, title='阿坨聊天室', size=(500, 400))
        except Exception:
            self.showDialog('Error', '连接失败!', (195, 120))

    def showDialog(self, title, content, size):
        # 显示错误信息对话框
        dialog = wx.Dialog(self, title=title, size=size)
        dialog.Center()
        wx.StaticText(dialog, label=content)
	#显示对话窗口
        dialog.ShowModal()
class ChatFrame(wx.Frame):
    """
    聊天窗口类,继承wx.Frame类
    """

    def __init__(self, parent, id, title, size):
        # 初始化,添加控件
        wx.Frame.__init__(self, parent, id, title)
        self.SetSize(size)
        self.Center()
	#显示对话文本框,style设置其文本高亮显示和只读
        self.chatFrame = wx.TextCtrl(self, pos=(5, 5), size=(490, 310), style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.message = wx.TextCtrl(self, pos=(5, 320), size=(300, 25))
        self.sendButton = wx.Button(self, label="Send", pos=(310, 320), size=(58, 25))
        self.usersButton = wx.Button(self, label="Users", pos=(373, 320), size=(58, 25))
        self.closeButton = wx.Button(self, label="Close", pos=(436, 320), size=(58, 25))
        # 发送按钮绑定发送消息方法
        self.sendButton.Bind(wx.EVT_BUTTON, self.send)
        # Users按钮绑定获取在线用户数量方法
        self.usersButton.Bind(wx.EVT_BUTTON, self.lookUsers)
        # 关闭按钮绑定关闭方法
        self.closeButton.Bind(wx.EVT_BUTTON, self.close)
	#调用thread模块中的start_new_thread()来产生新线程负责接收服务器信息
	#第一个参数为线程要执行函数的函数名,第二个参数为需要传递给函数的实参,为tuple,若该函数不需要参数也要传入空tuple       
        thread.start_new_thread(self.receive, ())
        self.Show()

    def send(self, event):
        # 发送消息
        message = str(self.message.GetLineText(0)).strip()
        if message != '':
	    #这里的'say '不可随意变动,为呼应server.py中命令处理类定义的handle(),实现文字聊天协议而存在
            con.write(('say ' + message + '\n').encode("utf-8"))
            self.message.Clear()

    def lookUsers(self, event):
        #查看当前在线用户
        con.write(b'look\n')

    def close(self, event):
        # 关闭窗口
        con.write(b'logout\n')
        con.close()
        self.Close()

    def receive(self):
        # 接受服务器的消息
        while True:
            sleep(0.6)
	    #在I/O中读取数据,存在result变量中
            result = con.read_very_eager()
            if result != '':
                self.chatFrame.AppendText(result)

if __name__ == '__main__':
    #应用程序对象
    app = wx.App()
    #客户端使用telnetlib连接目标主机
    con = telnetlib.Telnet()
    #顶级窗口对象
    LoginFrame(None, -1, title="Login", size=(320, 250))
    #进入应用程序的主事件循环
    app.MainLoop()

项目来源

python实现文字聊天室 | 实验楼

所使用的第三方库

使用asyncore和asynchat实现异步通信编程
使用thread实现多线程编程
使用telnetlib实现客户端和服务器之间的通信
使用wxpython实现GUI界面编程

聊天服务器将同多个 socket 进行通信,所以我们可以基于 asyncore 模块实现聊天服务器。asyncore 模块是一个异步的 socket 处理器,通过使用该模块将大大简化异步编程的难>度。asynchat 模块在 asyncore 模块的基础上做了进一步封装,简化了基于文本协议的通信任务的开发难度

区分同步通信和异步通信

同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

参考资料

项目思路:python实现文字聊天室 | 实验楼
了解python网络编程:python3网络编程 | 菜鸟教程
关于socket的类型:简单理解socket(AF_INET&SOCK_STREAM,SOCK_DGRAM)
asyncore开发文档:asyncore | python中文开发手册

运行效果

在这里插入图片描述在这里插入图片描述

发布了28 篇原创文章 · 获赞 8 · 访问量 2865

猜你喜欢

转载自blog.csdn.net/atuo200/article/details/104075107
今日推荐