Python 笔记04(多线程-回调函数-聊天室)

一 回调函数

回调函数是一种在编程中常见的概念,它通常用于异步编程或事件驱动编程中。回调函数是一个函数,它作为参数传递给另一个函数,并在特定事件发生时被调用。

以下是有关Python中回调函数的一些基本概念和用法:

  1. 函数作为对象 在Python中,函数是一等公民,这意味着它们可以像变量一样被传递。你可以将一个函数作为参数传递给另一个函数,也可以将函数赋值给变量。

  2. 回调函数的定义: 回调函数通常是一个普通的Python函数,它可以在需要时被调用。这个函数接受一个或多个参数,通常是事件触发时的数据。

  3. 回调函数的注册: 在某些情况下,你需要将回调函数注册到某个事件处理器或异步任务中。这通常通过将回调函数作为参数传递给另一个函数或对象的方法来完成。

  4. 事件触发: 当特定事件发生时,注册的回调函数将被调用。这个事件可以是异步操作完成、用户交互、定时器触发等等。

以下是一个简单的示例,演示了如何在Python中使用回调函数:

# 定义一个回调函数
def callback_function(result):
    print(f"Callback received result: {result}")

# 模拟异步操作,当操作完成时调用回调函数
def async_operation(callback):
    # 模拟异步操作,假设操作需要一段时间
    import time
    time.sleep(2)
    result = 42  # 模拟操作的结果
    # 调用回调函数,将结果传递给回调函数
    callback(result)

# 注册回调函数并执行异步操作
async_operation(callback_function)

# 主程序可以继续执行其他操作
print("Main program continues...")

1.1 进程池回调函数

pool.apply_async(func=test1, callback=test3)     # 调用回调函数

import time, os
from multiprocessing import Pool


def test1():
    print(f'妈妈的进程ID:{os.getpid()}, 当前进程的父进程:{os.getppid()}')
    print(f'你女儿叫你起床,你慢吞吞的起床')
    time.sleep(3)
    print('你起来了')
    return 'good morning'


def test2():
    print(f'女儿进程,女儿开始早读,当前进程ID:{os.getpid()}')
    time.sleep(3)
    print('早读完成')


def test3(args):
    print('一起吃早餐')
    print(args)


if __name__ == '__main__':
    pool = Pool(4)
    pool.apply_async(func=test1, callback=test3)
    test2()

二 多人聊天室服务器代码

import wx
from socket import *
import threading
import time


class ChatServer(wx.Frame):
    def __init__(self):
        """创建窗口"""
        wx.Frame.__init__(self, None, id=102, title='服务器界面', pos=wx.DefaultPosition, size=(400, 470))
        pl = wx.Panel(self)  # 在窗口中初始化一个面板
        # 在面板里面会放一些按钮,文本框,文本输入框等,把这些对象统一放入一个盒子里面
        box = wx.BoxSizer(wx.VERTICAL)  # 在盒子里面垂直方向自动排版

        g1 = wx.FlexGridSizer(wx.HORIZONTAL)  # 可升缩的网格布局,水平方向
        # 创建三个按钮
        start_server_button = wx.Button(pl, size=(133, 40), label="启动")
        record_save_button = wx.Button(pl, size=(133, 40), label="聊天记录保存")
        stop_server_button = wx.Button(pl, size=(133, 40), label="停止")
        g1.Add(start_server_button, 1, wx.TOP)
        g1.Add(record_save_button, 1, wx.TOP)
        g1.Add(stop_server_button, 1, wx.TOP)
        box.Add(g1, 1, wx.ALIGN_CENTER)  # ALIGN_CENTER 联合居中

        # 创建只读的文本框,显示聊天记录
        self.text = wx.TextCtrl(pl, size=(400, 400), style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.text, 1, wx.ALIGN_CENTER)
        pl.SetSizer(box)
        '''以上代码窗口结束 '''

        '''服务准备执行的一些属性'''
        self.isOn = False  # 服务器没有启动
        self.host_port = ('', 8888)
        self.server_socket = socket(AF_INET, SOCK_STREAM)  # TCP协议的服务器端套接字
        self.server_socket.bind(self.host_port)
        self.server_socket.listen(5)
        self.session_thread_map = {}  # 存放所有的服务器会话线程,字典:客户端名字为Key,会话线程为Value

        '''给所有的按钮绑定相应的动作'''  # 给启动按钮,绑定一个按钮事件,事件触发的时候会自动调用一个函数
        self.Bind(wx.EVT_BUTTON, self.start_server, start_server_button)
        self.Bind(wx.EVT_BUTTON, self.save_record, record_save_button)

    # 服务器开始启动函数
    def start_server(self, event):
        print('服务器开始启动')
        if not self.isOn:
            # 启动服务器
            self.isOn = True
            # 创建线程对象
            main_thread = threading.Thread(target=self.do_work)
            main_thread.setDaemon(True)  # 设置守护线程
            main_thread.start()

    # 服务运行之后的函数
    def do_work(self):
        print("服务器开始工作")
        while self.isOn:  # 在start_server中被赋值为True
            session_socket, client_addr = self.server_socket.accept()
            # 服务首先接受客户端发过来的第一条消息,我们规定第一条消息为客户端的名字
            username = session_socket.recv(1024).decode('UTF-8')  # 接受客户端名字

            # 创建一个会话线程 【采用的是继承式实现多线程】
            session_thread = SessionThread(session_socket, username, self)
            # 字典元素的添加 {客户名称key:客户端的线程对象为值}
            self.session_thread_map[username] = session_thread
            session_thread.start()  # 启动客户端的线程
            # 表示有客户端进入到聊天室
            self.show_info_and_send_client("服务器通知", f"欢迎{username}进入聊天室!",
                                           time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
        self.server_socket.close()

    # 在文本中显示接受和发送的信息
    def show_info_and_send_client(self, source, data, data_time):
        send_data = f'{source}:{data}\n 时间:{data_time}\n'

        self.text.AppendText(f'--------------------\n{send_data}')  # 在服务器聊天框显示
        for client in self.session_thread_map.values():
            if client.isOn:
                client.user_socket.send(send_data.encode('utf-8'))   # 数据转发到所有没有关闭的客户端

    # 服务保存聊天记录
    def save_record(self, event):
        record = self.text.GetValue()
        with open("record.log", "w+") as f:
            f.write(record)


# 服务器端会话线程的类
class SessionThread(threading.Thread):
    def __init__(self, socket, un, server):
        threading.Thread.__init__(self)
        self.user_socket = socket
        self.username = un
        self.server = server
        self.isOn = True   # 会话是否已经启动

    def run(self):
        print(f'客户端{self.username}, 已经和服务器连接成功,服务器启动一个会话线程')
        while self.isOn:
            data = self.user_socket.recv(1024).decode('utf8')  # 接收聊天信息
            if data == 'A^disconnect^B':   # 客户端点击断开按钮,约定内容
                self.isOn = False
                self.server.show_info_and_send_client(f'服务器通知,{self.username}离开了聊天室!'
                                                      f'{time.strftime("%Y-%m-%d %H:%M:%S")}')
            else:
                # 其他聊天信息
                self.server.show_info_and_send_client(self.username, data, time.strftime("%Y-%m-%d %H:%M:%S"))
        self.user_socket.close()


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

三 多人聊天室客户端代码

import wx
from socket import *
import threading


class ChatClient(wx.Frame):
    def __init__(self, c_name):  # c_name:客户端名字
        # 调用父类的初始化函数
        wx.Frame.__init__(self, None, id=101, title=f'{c_name}的客户端界面', pos=wx.DefaultPosition, size=(400, 470))
        pl = wx.Panel(self)  # 在窗口中初始化一个面板
        # 在面板里面会放一些按钮,文本框,文本输入框等,把这些对象统一放入一个盒子里面
        box = wx.BoxSizer(wx.VERTICAL)  # 在盒子里面垂直方向自动排版

        g1 = wx.FlexGridSizer(wx.HORIZONTAL)  # 可升缩的网格布局,水平方向
        # 创建两个按钮
        conn_button = wx.Button(pl, size=(200, 40), label="连接")
        dis_conn_button = wx.Button(pl, size=(200, 40), label="离开")
        g1.Add(conn_button, 1, wx.TOP | wx.LEFT)  # 连接按钮布局在左边
        g1.Add(dis_conn_button, 1, wx.TOP | wx.RIGHT)  # 断开按钮布局在右边
        box.Add(g1, 1, wx.ALIGN_CENTER)  # ALIGN_CENTER 联合居中

        # 创建聊天内容的文本框,不能写消息 :TE_MULTILINE -->多行  TE_READONLY-->只读
        self.text = wx.TextCtrl(pl, size=(400, 250), style=wx.TE_MULTILINE | wx.TE_READONLY)
        box.Add(self.text, 1, wx.ALIGN_CENTER)

        # 创建聊天的输入文本框,可以写
        self.input_text = wx.TextCtrl(pl, size=(400, 100), style=wx.TE_MULTILINE)
        box.Add(self.input_text, 1, wx.ALIGN_CENTER)

        # 最后创建两个按钮,分别是发送和重置
        g2 = wx.FlexGridSizer(wx.HORIZONTAL)
        clear_button = wx.Button(pl, size=(200, 40), label="重置")
        send_button = wx.Button(pl, size=(200, 40), label="发送")
        g2.Add(clear_button, 1, wx.TOP | wx.LEFT)
        g2.Add(send_button, 1, wx.TOP | wx.RIGHT)
        box.Add(g2, 1, wx.ALIGN_CENTER)

        pl.SetSizer(box)  # 把盒子放入面板中

        ''' 以上代码完成了客户端界面(窗口) '''

        '''给所有按钮绑定点击事件'''
        self.Bind(wx.EVT_BUTTON, self.connect_to_server, conn_button)
        self.Bind(wx.EVT_BUTTON, self.send_to, send_button)
        self.Bind(wx.EVT_BUTTON, self.go_out, dis_conn_button)
        self.Bind(wx.EVT_BUTTON, self.reset, clear_button)

        '''客户端的属性'''
        self.name = c_name
        self.isConnected = False  # 客户端是否已经连上服务器
        self.client_socket = None

    # 连接服务器
    def connect_to_server(self, event):
        print(f"客户端{self.name},开始连接服务器")
        if not self.isConnected:
            server_host_port = ('localhost', 8888)
            self.client_socket = socket(AF_INET, SOCK_STREAM)
            self.client_socket.connect(server_host_port)
            # 之前规定,客户端只要连接成功,马上把自己的名字发给服务器
            self.client_socket.send(self.name.encode('UTF-8'))
            t = threading.Thread(target=self.receive_data)  # 客户端,采用函数式实现多线程
            t.setDaemon(True)  # 客户端UI界面如果关闭,当前守护线程也自动关闭
            self.isConnected = True
            t.start()

    # 接受聊天数据
    def receive_data(self):
        print("客户端准备接收服务器的数据")
        while self.isConnected:
            data = self.client_socket.recv(1024).decode('UTF-8')
            # 从服务器接收到的数据,需要显示
            self.text.AppendText(f'{data}\n')

    # 客户端发送信息到聊天室
    def send_to(self, event):
        if self.isConnected:
            info = self.input_text.GetValue()
            if info != '':
                self.client_socket.send(info.encode('UTF-8'))
                # 输入框中的数据如果已经发送了,输入框重新为空
                self.input_text.SetValue('')

    # 客户端离开聊天
    def go_out(self, event):
        self.client_socket.send('A^disconnect^B'.encode('UTF-8'))
        # 客户端主线程也关闭
        self.isConnected = False

    # 客户端的输入信息重置
    def reset(self, event):
        self.input_text.Clear()


if __name__ == '__main__':
    app = wx.App()
    name = input("请输入客户端名字:")
    ChatClient(name).Show()
    app.MainLoop()  # 循环刷新显示

猜你喜欢

转载自blog.csdn.net/March_A/article/details/133325480