【Python】学习笔记 -- CH14. 项目案例《多人聊天室》

文章目录

  • CH14. 项目案例《多人聊天室》
    • 使用wxpython绘制客户端布局
    • 会话通信
      • 客户端代码实现
      • 服务端代码实现

CH14. 项目案例《多人聊天室》

在这里插入图片描述

  • wxPython:是Python的第三方库,代码实现基于C++的wxWidgets库封装,呈现的界面风格和系统本地风格一致

使用wxpython绘制客户端布局

  • 客户端窗口布局

在这里插入图片描述

# client.py

# coding=utf-8
import wx


class Client(wx.Frame):
    def __init__(self, client_name):
        # 调用父类的初始化方式
        # None:没有父级窗口
        # id表示当前窗口的一个编号
        # pos:窗口的打开位置
        # size:窗体的大小,单位是像素,400宽,450高
        wx.Frame.__init__(self, None, id=1001, title=client_name+'的客户端界面',
                          pos=wx.DefaultPosition, size=(400,450))
        # 创建面板对象
        pl = wx.Panel(self)
        # 在面板中放上盒子
        box = wx.BoxSizer(wx.VERTICAL) # 垂直方向布局
        # 可伸缩的网格布局
        fgz1 = wx.FlexGridSizer(wx.HSCROLL) # 水平方向布局
        # 创建两个按钮
        conn_btn = wx.Button(pl, size=(200,40), label='连接')
        dis_conn_btn = wx.Button(pl, size=(200,40), label='断开')
        # 把两个按钮放到可伸缩的网格布局
        fgz1.Add(conn_btn, 1, wx.TOP | wx.LEFT)
        fgz1.Add(dis_conn_btn, 1, wx.TOP | wx.RIGHT)
        # (可伸缩的网格布局)添加到box中
        box.Add(fgz1, 1, wx.ALIGN_CENTER)

        # 只读文本框,显示聊天内容
        self.show_text = wx.TextCtrl(pl, size=(400,210),
                                     style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.show_text, 1, wx.ALIGN_CENTER)

        # 创建聊天文本框
        self.chat_text = wx.TextCtrl(pl, size=(400,120),
                                     style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.chat_text, 1, wx.ALIGN_CENTER)

        # 可伸缩的网格按钮
        fgz2 = wx.FlexGridSizer(wx.HSCROLL) # 水平方向布局
        # 创建两个按钮
        reset_btn = wx.Button(pl, size=(200,40), label='重新输入')
        send_btn = wx.Button(pl, size=(200,40), label='发送')
        # 把两个按钮放到可伸缩的网格布局
        fgz2.Add(reset_btn, 1, wx.TOP | wx.LEFT)
        fgz2.Add(send_btn, 1, wx.TOP | wx.RIGHT)
        # (可伸缩的网格布局)添加到box中
        box.Add(fgz2, 1, wx.ALIGN_CENTER)        

        # 将盒子放到面板中
        pl.SetSizer(box)

if __name__ == "__main__":
    app = wx.App()
    client = Client('客户端App')
    client.Show()

    app.MainLoop()

  • 服务端窗口布局

在这里插入图片描述

# coding=utf-8

import wx


class Server(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, id=1002, title='服务端App',
                        pos=wx.DefaultPosition, size=(400,500))
        # 创建面板
        pl = wx.Panel(self)

        # 创建盒子
        box = wx.BoxSizer(wx.VERTICAL) # 垂直方向上自动排版

        # 可伸缩的网格布局
        fgz1 = wx.FlexGridSizer(wx.HSCROLL)

        start_server_btn = wx.Button(pl,size=(133,40),label='启动服务')
        record_btn = wx.Button(pl,size=(144,40),label='保存聊天记录')
        stop_server_btn = wx.Button(pl,size=(133,40),label='停止服务')
        # 添加到可伸缩的网格布局中
        fgz1.Add(start_server_btn, 1, wx.TOP)
        fgz1.Add(record_btn, 1, wx.TOP)
        fgz1.Add(stop_server_btn, 1, wx.TOP)

        # 将可伸缩的布局放到Box中
        box.Add(fgz1, 1, wx.ALIGN_CENTRE)

        # 只读多行文本框
        self.show_text = wx.TextCtrl(pl, size=(400,410),
                                     style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.show_text, 1, wx.ALIGN_CENTER)

        pl.SetSizer(box)

if __name__ == '__main__':
    app = wx.App()
    server = Server()
    server.Show()

    app.MainLoop()

会话通信

  • 代码功能实现
    1. 启动服务器功能实现
    2. 客户端连接服务器
    3. 显示聊天信息
    4. 发送聊天信息到聊天室
    5. 客户端断开连接
    6. 客户端重置
    7. 保存聊天记录
    8. 停止服务

客户端代码实现

# client.py
# coding=utf-8
import threading
from socket import AF_INET, SOCK_STREAM, socket

import wx


class Client(wx.Frame):
    def __init__(self, client_name):
        # 调用父类的初始化方式
        # None:没有父级窗口
        # id表示当前窗口的一个编号
        # pos:窗口的打开位置
        # size:窗体的大小,单位是像素,400宽,450高
        wx.Frame.__init__(self, None, id=1001, title=client_name+'的客户端界面',
                          pos=wx.DefaultPosition, size=(400,450))
        # 创建面板对象
        pl = wx.Panel(self)
        # 在面板中放上盒子
        box = wx.BoxSizer(wx.VERTICAL) # 垂直方向布局
        # 可伸缩的网格布局
        fgz1 = wx.FlexGridSizer(wx.HSCROLL) # 水平方向布局
        # 创建两个按钮
        conn_btn = wx.Button(pl, size=(200,40), label='连接')
        dis_conn_btn = wx.Button(pl, size=(200,40), label='断开')
        # 把两个按钮放到可伸缩的网格布局
        fgz1.Add(conn_btn, 1, wx.TOP | wx.LEFT)
        fgz1.Add(dis_conn_btn, 1, wx.TOP | wx.RIGHT)
        # (可伸缩的网格布局)添加到box中
        box.Add(fgz1, 1, wx.ALIGN_CENTER)

        # 只读文本框,显示聊天内容
        self.show_text = wx.TextCtrl(pl, size=(400,210),
                                     style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.show_text, 1, wx.ALIGN_CENTER)

        # 创建聊天文本框
        self.chat_text = wx.TextCtrl(pl, size=(400,120),
                                     style=wx.TE_MULTILINE)
        box.Add(self.chat_text, 1, wx.ALIGN_CENTER)

        # 可伸缩的网格按钮
        fgz2 = wx.FlexGridSizer(wx.HSCROLL) # 水平方向布局
        # 创建两个按钮
        reset_btn = wx.Button(pl, size=(200,40), label='重新输入')
        send_btn = wx.Button(pl, size=(200,40), label='发送')
        # 把两个按钮放到可伸缩的网格布局
        fgz2.Add(reset_btn, 1, wx.TOP | wx.LEFT)
        fgz2.Add(send_btn, 1, wx.TOP | wx.RIGHT)
        # (可伸缩的网格布局)添加到box中
        box.Add(fgz2, 1, wx.ALIGN_CENTER)        

        # 将盒子放到面板中
        pl.SetSizer(box)
        # ---------------以上为客户端界面绘制----------------
        # ---------------连接按钮---------------------------
        self.Bind(wx.EVT_BUTTON, self.connect_to_server, conn_btn)

        # 实例属性设置
        self.client_name = client_name
        self.isConnected = False
        self.client_socket = None # 设置客户端的socket对象为空

        # ---------------发送按钮---------------------------
        self.Bind(wx.EVT_BUTTON, self.send_data_to_server, send_btn)
        self.Bind(wx.EVT_BUTTON, self.disconnect_with_server, dis_conn_btn)
        self.Bind(wx.EVT_BUTTON, self.reset_chat_text, reset_btn)

    def connect_to_server(self, event):
        if not self.isConnected:
            server_host_port = ('127.0.0.1', 8888)
            self.client_socket = socket(AF_INET, SOCK_STREAM)
            # 发送连接请求
            self.client_socket.connect(server_host_port)
            # 只要连接成功,发送client_name
            self.client_socket.send(self.client_name.encode('utf-8'))
            # 启动一个线程,客户端的线程与服务器的会话线程进行会话
            client_thread = threading.Thread(target=self.recv_data)
            # 设置为守护线程,父线程执行结束(窗体界面),子线程也自动关闭
            client_thread.daemon = True
            # 修改连接状态
            self.isConnected = True
            # 启动主线程
            client_thread.start()
            print(f'客户端{self.client_name}连接服务器成功')

    def recv_data(self):
        while self.isConnected:
            # 接收来自服务器的数据
            try:
                data = self.client_socket.recv(1024).decode('utf-8')
            except:
                self.isConnected = False
                continue
            # 显示到只读文本框
            self.show_text.AppendText('-'*40+'\n'+data+'\n')

    def send_data_to_server(self, event):
        # 判断连接状态
        if self.isConnected:
            input_data = self.chat_text.GetValue()
            if input_data !='':
                self.client_socket.send(input_data.encode('utf-8'))
                # 发完数据之后清空文本框
                self.chat_text.SetValue('')

    def disconnect_with_server(self, event):
        self.client_socket.send('Folin-disconnet'.encode('utf-8')) # 发送断开信息
        self.isConnected = False # 修改连接状态
    
    def reset_chat_text(self, event):
        self.chat_text.Clear()


if __name__ == "__main__":
    app = wx.App()
    client = Client('Folin')
    client.Show()

    app.MainLoop()

服务端代码实现

#server.py
# coding=utf-8

import threading
import time
from socket import AF_INET, SOCK_STREAM, socket

import wx


class SessionThread(threading.Thread):
    def __init__(self, client_socket, user_name, server):
        threading.Thread.__init__(self) # 调用父类初始化方法
        self.client_socket = client_socket
        self.user_name = user_name
        self.server = server
        self.isConnected = True # 会话线程启动

    def run(self) -> None:
        print(f'客户端:{self.user_name}已经和服务器连接成功,服务器启动一个会话线程')
        while self.isConnected:
            # 从客户端接收数据,存储到data中
            data = self.client_socket.recv(1024).decode('utf-8')
            # 如果客户端点击断开按钮,先给服务器发送一句话,消息自定义
            if data == 'Folin-disconnet':
                self.isConnected = False
                # 发送通知
                self.server.show_info_and_send_to_client('服务器通知:', f'{self.user_name}离开聊天室', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
            else:
                # 其他聊天信息显示给所有客户端,服务端也显示
                self.server.show_info_and_send_to_client(self.user_name, data, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
        # 关闭socket
        self.client_socket.close()
        print(f'{self.user_name} close...')

class Server(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, id=1002, title='服务端App',
                        pos=wx.DefaultPosition, size=(400,500))
        # 创建面板
        pl = wx.Panel(self)

        # 创建盒子
        box = wx.BoxSizer(wx.VERTICAL) # 垂直方向上自动排版

        # 可伸缩的网格布局
        fgz1 = wx.FlexGridSizer(wx.HSCROLL)

        start_server_btn = wx.Button(pl,size=(133,40),label='启动服务')
        record_btn = wx.Button(pl,size=(144,40),label='保存聊天记录')
        stop_server_btn = wx.Button(pl,size=(133,40),label='停止服务')
        # 添加到可伸缩的网格布局中
        fgz1.Add(start_server_btn, 1, wx.TOP)
        fgz1.Add(record_btn, 1, wx.TOP)
        fgz1.Add(stop_server_btn, 1, wx.TOP)

        # 将可伸缩的布局放到Box中
        box.Add(fgz1, 1, wx.ALIGN_CENTRE)

        # 只读多行文本框
        self.show_text = wx.TextCtrl(pl, size=(400,410),
                                     style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.show_text, 1, wx.ALIGN_CENTER)

        pl.SetSizer(box)

        # ------------------以上为界面绘制代码------------------------
        self.isOn = False # 存储服务器的启动状态,默认值为False,默认没有启动

        # 服务端绑定的ip地址和端口
        self.host_port = ('', 8888) # 空字符串表示本机的所有IP

        self.server_socket = None # 设置客户端的socket对象为空

        # 创建一个字典,存储与客户端对话的回话线程
        self.session_thread_dict = {} # key-value {客户端名称key: 会话线程value}

        # 绑定按钮‘启动服务’
        self.Bind(wx.EVT_BUTTON, self.start_server, start_server_btn)
        self.Bind(wx.EVT_BUTTON, self.save_record, record_btn)
        self.Bind(wx.EVT_BUTTON, self.stop_server, stop_server_btn)

    def save_record(self, event):
        # 获取只读文本框的内容
        record_data = self.show_text.GetValue()
        with open('record.log', 'w', encoding='utf-8') as file:
            file.write(record_data)

    def stop_server(self, event):
        print('服务器已停止服务')
        self.isOn = False
        self.server_socket.close()

    def start_server(self, event):
        # 判断服务器是否已启动,只有未启动时才启动
        if not self.isOn:
            # 启动服务器
            self.isOn = True
            # 创建socket对象
            self.server_socket = socket(AF_INET, SOCK_STREAM)
            # 绑定IP地址和端口
            self.server_socket.bind(self.host_port)
            # 监听
            self.server_socket.listen(5)
            # 创建主线程对象,函数式创建
            main_thread = threading.Thread(target=self.do_work)
            # 设置为守护线程,父线程执行结束(窗体界面),子线程也自动关闭
            main_thread.daemon = True
            # 启动主线程
            main_thread.start()
            print('start server')

    # 主线程函数
    def do_work(self):
        # 判断服务是否启动
        while self.isOn:
            # 接收客户端的连接请求
            try:
                client_socket, client_addr = self.server_socket.accept()
            except:
                print('has error')
                continue
            # 客户端发送连接请求之后,发送过来的第一条数据作为客户端的名称,客户端的名称去作为字典中的键
            user_name = client_socket.recv(1024).decode('utf-8')
            # 创建回话线程对象
            session_thread = SessionThread(client_socket, user_name, self)
            # 存储到字典中
            self.session_thread_dict[user_name] = session_thread

            # 启动会话线程
            session_thread.start()

            # 输出服务器的提示信息
            self.show_info_and_send_to_client('服务器通知:', f'欢迎{user_name}进入聊天室!', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
        # 当服务关闭(isOn)为False时,关闭socket
        self.server_socket.close()
        print('server_socket close...')

    def show_info_and_send_to_client(self, data_src, data, datetime):
        if self.isOn:
            send_data = f'{data_src}: {data}\n时间: {datetime}'
            # 只读文本框
            self.show_text.AppendText('-'*40+'\n'+send_data+'\n')
            # 给每个客户端都发送一次
            for client in self.session_thread_dict.values():
                if client.isConnected:
                    client.client_socket.send(send_data.encode('utf-8'))


if __name__ == '__main__':
    app = wx.App()
    server = Server()
    server.Show()
    app.MainLoop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值