zoukankan      html  css  js  c++  java
  • Python学习笔记之 并发编程

    多任务编程

    概念

    同一时间执行多个任务

    执行方式

    • 并发:在一段时间内交替执行任务单核CPU
    • 并行:给CPU每个核分配任务多核CPU,真正的多任务

    进程与线程

    • 一个正在运行的程序或软件就是一个进程
    • 注意:一个程序至少有一个进程,至少有一个线程
    • 关系:进程就是一个公司,负责提供办公室、电脑...(向CPU索要资源),公司里的员工就是线程(真正负责干活的是线程

    多进程

    1. 进程理论基础

    1. 定义:程序在计算机中的一次执行
    • 程序是一个可执行的文件,是静态的占有磁盘
    • 进程是一个动态的过程描述,占有计算机运行资源,有一定生命周期
    1. 系统中如何产生一个进程
      【1】 用户空间通过调用程序接口或者命令发起请求
      【2】 操作系统接收用户请求,开始创建进程
      【3】 操作系统调配计算机资源,确定进程状态等
      【4】 操作系统将创建的进程提供给用户使用

    2. 进程基本概念

    • CPU时间片:如果一个进程占有CPU内核,则称这个进程在CPU时间片上
    • PCB(进程控制模块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程
    • 进程ID(PID):系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复

      Linux查看进程ID:ps -aux

    • 父子进程:系统中每一个进程(除了系统初始化进程),都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理

      查看进程树:pstree

    • 进程状态
      • 三态
        就绪态:进程具备执行条件,等待分配CPU资源
        运行态:进程占有CPU时间片正在运行
        阻塞态:进程不具备执行条件,等待条件满足

    2. 创建进程

    【1】 进程类
    import multiprocessing
    
    【2】 参数
    multiprocessing.Process(
        group=None, # 指定进程组,一般不用
        target=None, # 执行的目标任务名(函数)
        name=None, # 进程名
        args=(), # 以元组传参
        kwargs={}, # 以字典传参
        *,
        daemon=None, # 守护进程
    )
    
    【3】 类方法(常用)
    p = Process()
    p.start() # 启动子进程
    p.join() # 阻塞主进程直到子进程执行结束
    p.terminate() # 中止子进程
    p.daemon = True # 设为守护进程
    
    【4】 创建进程的两种方法
    • 一、实例化Process类
      p = Process()
      
    • 二、继承并重写__init__和run方法
      class MyProcess(Process):
          def __init__(self):
              ...
          
          def run(self):
              ...
      

    实例1:

    from multiprocessing import Process
    from time import sleep
    
    def do():
        for i in range(3):
            print('.........
    ',end='')
            sleep(0.1)
    
    def do2():
        for i in range(3):
            print('aaaaaaaaaaa
    ',end='')
            sleep(0.1)
    
    if __name__ == '__main__':
        p1 = Process(target=do)
        p2 = Process(target=do2)
        p1.start()
        p2.start()
    

    运行结果:

    .........
    aaaaaaaaaaa
    aaaaaaaaaaa
    .........
    aaaaaaaaaaa
    .........
    

    注意!进程执行是无序的

    【4】进程相关操作
    os.getpid() # 获取当前进程编号
    os.getppid() # 获取父进程编号
    os.kill(pid,9) # 杀死进程(Linux)
    os.popen(f'taskkill /pid {pid}') # 杀死进程(Windows)
    multiprocessing.current_process() # 获取进程对象
    multiprocessing.cpu_count() # 获取CPU内核数
    
    【5】 注意事项
    • 进程会把if name == 'main'上面的所有代码复制一遍,因此必须加上if name == 'main'
    • 子进程是父进程的拷贝,因此进程间不共享全局变量
    • 守护进程会随主进程的结束而销毁

    3. 进程池

    1. 必要性
      【1】 进程的创建和销毁过程消耗的资源较多
      【2】 当任务众多时,每个任务在很短的时间内完成时,需要频繁地创建和销毁进程。此时对计算机压力较大
      【3】 进程池技术很好的解决了以上问题

    2. 原理

      创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全部处理完毕统一销毁。增加进程的重复利用,降低资源消耗

    3. 进程池实现

      【1】 创建进程池对象,放入适当的进程

      from multiprocessing import Pool
      
      Pool(processes)
      功能:创建进程池对象
      参数:指定进程数量,默认根据系统自动判定
      

      【2】 将事件加入进程池队列执行

      pool.apply_async(func,args,kwds)
      
      功能:使用进程池执行func事件
      参数:func 事件函数
          args 元组 给func按位置传参
          kwds 字典 给func按照键值传参
      

      【3】 关闭进程池

      pool.close()
      
      功能:关闭进程池,无法再向进程池中加入进程
      

      【4】 回收进程池中进程

      pool.join()
      
      功能:回收进程池中进程
      
    4. 实例

      from multiprocessing import Pool
      import os
      from time import sleep,ctime
      
      def do():
          for i in range(3):
              print(f'{os.getpid()}:hhhhhhhhh  {ctime()}
      ',end='')
              sleep(0.01)
      
      if __name__ == '__main__':
          pool = Pool(10) # 创建进程池
      
          for i in range(10): # 向进程池中加入事件
              pool.apply_async(func=do)
      
          pool.close() # 关闭进程池
      
          pool.join() # 回收进程,阻塞主进程
      
          print('主进程结束')
      

      运行结果:

      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      7544:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      7544:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11988:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      1824:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11456:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11988:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      7544:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4596:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      8532:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11988:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11456:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      1824:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      1824:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      11456:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      15552:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      8532:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4596:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4596:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      4400:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      8532:hhhhhhhhh  Thu Aug 20 19:46:36 2020
      主进程结束
      

    4.进程间通信(IPC)

    1. 必要性:进程空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信
    2. 常用进程通信方法

      管道、消息队列、共享内存、信号、信号量、套接字

    消息队列

    1. 通信原理

      在内存中建立队列模型,进程通过队列将消息存入,或者从队列中取出完成进程间通信

      注意!消息队列应在主进程中创建

    2. 实现方法

      from multiprocessing import Queue
      
      q = Queue(maxsize=0)
      功能:创建队列对象
      参数:最多存放消息个数
      返回值:队列对象
      
      q.put(data,[block,timeout])
      功能:向队列存入消息
      参数:data 要存入的内容
           block 设置是否阻塞 False为非阻塞,
                 如果block为True,当消息队列满了时就会阻塞,
                 如果block为False,则会直接报错queue.Full
           timeout 超时检测(最多阻塞的时间)
      
      q.get([block,timeout])
      功能:从队列取出消息
      注意!队列中的消息是先进先出
      参数:block 设置是否阻塞 False为非阻塞
           timeout 超时检测
      返回值:获取到的内容
      
      q.full() # 判断队列是否为满
      q.empty() # 判断队列是否为空
      q.qsize() # 获取队列中消息个数
      q.close() # 关闭队列
      
    3. 实例

      from multiprocessing import Queue,Process
      from time import sleep
      
      ''' 
      消息队列演示
      注意!多个进程需要使用同一个消息队列
      '''
      
      def request(q1,q2):
          print('使用wx登录?')
          q1.put('请求用户名密码') # 写消息队列
          data = q2.get()
          print(f'获取到信息:{data}')
      
      def handler(q1,q2):
          data = q1.get() # 读消息队列
          print('收到请求:',data)
          sleep(0.1)
          q2.put({'name':'Zhang','passwd':'123'})
      
      if __name__ == '__main__':
          # 消息队列
          q1 = Queue(3)
          q2 = Queue(3)
      
          p1 = Process(target=request,args=(q1,q2))
          p2 = Process(target=handler,args=(q1,q2))
          p1.start()
          p2.start()
          p1.join()
          p2.join()
      

      运行结果:

      使用wx登录?
      收到请求: 请求用户名密码
      获取到信息:{'name': 'Zhang', 'passwd': '123'}
      

      在实际应用中,一般会创建多个消息队列分别用于读、写,或是一个进程只读,另一个进程只写

    僵尸进程(Linux

    僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程(有害)。如果父进程先退出 ,子进程(孤儿进程,无害)被init接管,子进程退出后init会回收其占用的相关资源

    注意!僵尸进程无法通过 kill 命令来清除

    示例:

    from multiprocessing import Process, current_process
    import logging
    import os
    import time
    
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)-15s - %(levelname)s - %(message)s'
    )
    
    
    def run():
        logging.info('exit child process %s', current_process().pid)
        os._exit(3)
    
    if __name__ == '__main__':
        p = Process(target=run)
        p.start()
        time.sleep(100)
    

    运行结果:

    2020-08-21 18:36:17,783 - INFO - exit child process 13972
    

    清理僵尸进程方法:

    • 第一种方法就是结束父进程。当父进程退出的时候僵尸进程随后也会被清除。
    • 第二种方法就是通过 wait 调用来读取子进程退出状态。我们可以通过处理 SIGCHLD 信号,在处理程序中调用 wait 系统调用来清除僵尸进程
    • 第三种办法就说把进程变成孤儿进程,这样进程就会自动交由 init 进程(pid 为 1 的进程)来处理,一般 init 进程都包含对僵尸进程进行处理的逻辑。(这里有个坑,那就是 docker 容器中一般 pid 为 1 进程就是主程序的进程,而不是我们预期的 init 进程。如果要使用这种方法的话,需要注意一下类似的场景)

    多线程

    线程基本概念

    1. 什么是线程
      【1】 线程被称为轻量级的进程
      【2】 线程也可以使用计算机多核资源,是多任务编程方式
      【3】 线程是系统分配内核的最小单元
      【4】 线程可以理解为进程的分支任务
    2. 线程特征
      【1】 一个进程中可以包括多个线程
      【2】 线程也是一个运行行为,消耗计算机资源
      【3】 一个进程中的所有线程共享这个进程的资源
      【4】 多个线程之间的运行互不影响各自运行
      【5】 线程的创建和销毁消耗资源远小于进程

    threading模块创建线程

    【1】 创建线程

    from threading import Thread
    
    t = Thread()
    功能:创建线程对象
    参数:target 绑定线程函数
         args 元组 给线程函数位置传参
         kwargs 字典 给线程函数键值传参
    

    【2】 启动线程

    t.start()
    

    【3】 回收线程

    t.join([timeout])
    功能:阻塞主线程直到子线程执行结束
    

    线程对象属性

    t.name 线程名称
    t.setName() 设置线程名称
    t.getName() 获取线程名称
    t.daemon = True 设置为守护线程,即主线程执行完后自动销毁守护线程

    实例1:

    from threading import Thread
    from time import sleep,ctime
    
    def do(id):
        for i in range(3):
            sleep(0.1)
            print(f'线程{id}:{ctime()}
    ',end='')
    
    if __name__ == '__main__':
        t1 = Thread(target=do,args=(1,))
        t2 = Thread(target=do, args=(2,))
        t1.start()
        t2.start()
    

    实例2:

    from threading import Thread
    from time import sleep,ctime
    
    def do(id):
        for i in range(3):
            sleep(0.1)
            print(f'线程{id}:{ctime()}
    ',end='')
    
    class MyThread(Thread):
        def __init__(self,id):
            self.id = id
            Thread.__init__(self)
    
        def run(self):
            do(self.id)
    
    if __name__ == '__main__':
        t1 = MyThread(1)
        t2 = MyThread(2)
        t1.start()
        t2.start()
    

    运行结果:

    线程2:Thu Aug 20 21:22:35 2020
    线程1:Thu Aug 20 21:22:35 2020
    线程1:Thu Aug 20 21:22:36 2020
    线程2:Thu Aug 20 21:22:36 2020
    线程2:Thu Aug 20 21:22:36 2020
    线程1:Thu Aug 20 21:22:36 2020
    

    显然,线程的执行也是无序的

    实例3:

    from threading import Thread
    
    data = ''
    
    def write():
        global data
        data = 'write'
    
    def read():
        global data
        print(f'data = {data}')
    
    if __name__ == '__main__':
        t1 = Thread(target=write)
        t2 = Thread(target=read)
        t1.start()
        t2.start()
    

    运行结果:

    data = write
    

    这个例子说明:线程之间共享全局变量

    同步互斥

    线程之间的通信方法

    1. 通信方法

      线程之间使用全局变量进行通信

    2. 共享资源争夺
      • 共享资源:多个进程或线程都可以操作的资源被称为共享资源。对共享资源的操作代码段称为临界区
      • 影响:对共享资源的无序操作可能会带来数据的混乱(脏数据)或者操作错误。此时往往需要同步互斥机制协调操作顺序
    3. 同步互斥机制

    同步:同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作

    互斥是一种制约关系,当一个进程或线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作

    线程Event

    from threading import Event
    
    e = Event() # 创建线程Event对象
    
    e.wait([timeout]) # 阻塞等待e被set,timeout为超时时间
    
    e.set() # 设置e,使wait结束阻塞
    
    e.clear() # 使e回到未被设置状态
    
    e.is_set() # 查看当前e是否被设置
    

    实例:

    from threading import Event,Thread
    
    ''' 
    Event互斥方法
    '''
    
    s = None # 用于通信
    e = Event()
    
    def yzr():
        print('杨子荣前来拜山头
    ',end='')
        # 临界区
        global s
        s = '天王盖地虎'
        e.set() # 解除wait阻塞
    
    if __name__ == '__main__':
        t = Thread(target=yzr)
        t.start()
        print('说对口令就是自己人')
        e.wait(3) # 阻塞等待说口令
        if s == '天王盖地虎':
            print('宝塔镇河妖')
            print('确认过眼神,你是自己人')
        else:
            print('兄弟们,打死他')
    

    运行结果:

    杨子荣前来拜山头
    说对口令就是自己人
    宝塔镇河妖
    确认过眼神,你是自己人
    

    线程锁Lock

    • 多个线程会去抢锁,抢到的先执行
    from threading import Lock
    
    lock = Lock() # 创建锁
    
    lock.acquire() # 上锁
    
    lock.release() # 解锁
    
    with lock: # 上锁
        ...
        # with代码块结束自动解锁
    

    实例:

    from threading import Thread,Lock
    from random import random
    
    value = 0
    lock = Lock() # 创建锁
    
    def js():
        for i in range(10000):
            x = random()
            y = random()
            if (x**2 + y**2)**0.5 < 1:
                lock.acquire()
                global value
                value += 1
                lock.release()
                ''' 
                with lock:
                    value += 1
                '''
            else:
                continue
    
    if __name__ == '__main__':
        thread_list = []
        for i in range(1000):
            t = Thread(target=js)
            t.start()
            thread_list.append(t)
        for i in thread_list:
            i.join()
        print(f'π ≈ {value/10000000 * 4}')
    

    运行结果:

    π ≈ 3.1417504
    

    死锁及其处理

    1. 定义

      死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁

    1. 死锁产生条件

      死锁发生的必要条件

      • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待

      • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放

      • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放,通常CPU内存资源是可以被系统强行调配剥夺的

      • 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源

      死锁的产生原因

      简单来说造成死锁的原因可以概括成三句话:

      • 当前线程拥有其他线程需要的资源
      • 当前线程等待其他线程已拥有的资源
      • 都不放弃自己拥有的资源。
    2. 如何避免死锁

      死锁是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个,来预防发生死锁。预防死锁是一种较易实现的方法,但由于所施加的限制条件往往太严格,可能会导致系统资源利用率低。

    Python线程GIL

    1. Python线程GIL问题(全局解释器锁)

      什么是GIL:由于Python解释器设计中加入了解释器锁,导致Python解释器同一时刻只能解释执行一个线程。大大降低了线程的执行效率。

      导致后果:因为遇到阻塞时,线程会自动让出解释器去解释其它线程,所以Python多线程在执行多阻塞高延迟IO时可以提升程序效率。其他情况并不能对效率有所提升。

      单核CPU:

      多核CPU:

      GIL问题建议

      • 尽量使用进程完成无阻塞的并发行为
      • 不使用C作为解释器(Java C#)
    2. 结论:在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。

    常见网络通信模型

    1. 循环服务器模型:循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。

      优点:实现简单,占用资源少

      缺点:无法同时处理多个客户端请求

      适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序。UDP比TCP更适合循环

    2. 多进程/线程网络并发模型:每当一个客户端连接服务器就创建一个新的进程/线程为该客户端服务。客户端退出时再销毁该进程/线程。

      优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。

      缺点:资源消耗较大

      使用情况:客户端同时连接量较少,需要处理行为较复杂情况。

    3. IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求

      优点:资源消耗较少,能同时高效处理多个IO行为。

      缺点:只能处理并发产生的IO事件,无法处理CPU计算。

    IO并发

    IO分类

    阻塞IO,非阻塞IO,IO多路复用,异步IO等

    阻塞IO

    1. 定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞是IO的默认状态

    2. 效率:阻塞IO是效率很低的一种IO,但是由于逻辑简单,所以是默认IO行为。

    3. 阻塞情况:

    • 因为某种执行条件没有满足造成的函数阻塞

    • 处理IO的时间较长产生的阻塞状态,如:网络传输、大文件读写

    非阻塞IO

    1. 定义:通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
    • 设置套接字为非阻塞IO

      sockfd.setblocking(bool)

      功能:设置套接字为非阻塞IO

      参数:默认为True,表示套接字为阻塞IO;设置为Falsw则套接字IO变为非阻塞

    • 超时检测:设置一个最长阻塞时间,超过该时间后则不再阻塞等待。

      sockfd.settimeout(timeout)

    实例:

    # test.py
    import socket
    from time import sleep
    import logging
    
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s',
        datefmt='%Y/%m/%d %H:%M:%S',
        filename='output.log',
        filemode='w'
    )
    
    if __name__ == '__main__':
        logger = logging.getLogger(__name__)
        sockfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        sockfd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
        sockfd.setblocking(False)
        sockfd.bind(("",9090))
        sockfd.listen(1)
        while True:
            print('Waiting for connect...')
            try:
                connfd,addr = sockfd.accept()
            except BlockingIOError as e:
                sleep(1)
                logger.error(f'报错:{e}')
    

    运行结果:

    Waiting for connect...
    Waiting for connect...
    Waiting for connect...
    Waiting for connect...
    

    output.log:

    2020/08/21 20:59:58 - __main__ - ERROR - 26 - test - 报错:[WinError 10035] 无法立即完成一个非阻止性套接字操作。
    2020/08/21 20:59:59 - __main__ - ERROR - 26 - test - 报错:[WinError 10035] 无法立即完成一个非阻止性套接字操作。
    2020/08/21 21:00:00 - __main__ - ERROR - 26 - test - 报错:[WinError 10035] 无法立即完成一个非阻止性套接字操作。
    

    IO多路复用

    1. 定义

      同时监控多个IO事件,当哪个IO事件准备就绪,就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行。提高了IO执行效率。

    2. 具体方案

      select方法:Windows、Linux、Unix

      poll方法:Linux、Unix

      epoll方法:Linux

    select方法

    import select
    
    rs, ws, xs = select.select(rlist, wlist, xlist[, timeout])
    功能:监控IO事件,阻塞等待IO发生
    参数:rlist 列表 存放关注的等待发生的IO事件
         wlist 列表 存放关注的要主动处理的IO事件
         xlist 列表 存放关注的出现异常要处理的IO
         timeout int 超时时间
    
    返回值:rs 列表 rlist中准备就绪的IO
           ws 列表 wlist中准备就绪的IO
           xs 列表 xlist中准备就绪的IO
    

    注意

    wlist中如果存在IO事件,则select立即返回给ws

    处理IO过程中不要出现死循环占有服务端的情况

    IO多路复用消耗资源较少,效率较高

    实例:

    # 多路复用.py
    '''
    1. 所有的IO使用select监控
    2. 每个IO发生时进行处理,没有发生时即进入监控状态
    3. 每个IO不会长期阻塞服务器端执行
    '''
    
    from socket import *
    from select import select
    import logging
    
    logger = logging.basicConfig(
        filename='output.log',
        filemode='w',
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        level=logging.NOTSET,
        datefmt='%Y/%m/%d %H:%M:%S'
    )
    
    if __name__ == '__main__':
        logger = logging.getLogger(__name__)
        # 创建套接字,作为关注的IO
        sockfd = socket(AF_INET,SOCK_STREAM)
        sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,True)
        sockfd.bind(("",9090))
        sockfd.listen(3)
        logger.info('开始监听')
    
        # 将关注IO放入列表
        rlist = [sockfd]
        wlist = []
        xlist = []
    
        # 监控IO发生
        while True:
            rs, ws, xs = select(rlist, wlist, xlist)
            logger.info(f'rlist : {rlist}')
            for r in rs:
                # 将取出的IO根据不同情况进行处理
                if r is sockfd:
                    c, addr = r.accept()
                    logger.info(f'Connect from {addr}')
                    rlist.append(c) # 将c和addr加入监控列表
                else:
                    # 有客户端发消息
                    data = r.recv(1024).decode('gbk')
                    if not data:
                        rlist.remove(r)
                        logger.info(f'{r}断开连接')
                    else:
                        logger.info(f'来自{r}的消息:{data}')
    
            for w in ws:
                pass
    

    output.log:

    2020/08/21 21:41:39 - __main__ - INFO - 开始监听
    2020/08/21 21:41:41 - __main__ - INFO - rlist : [<socket.socket fd=544, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 9090)>]
    2020/08/21 21:41:41 - __main__ - INFO - Connect from ('127.0.0.1', 58832)
    2020/08/21 21:41:41 - __main__ - INFO - rlist : [<socket.socket fd=544, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 9090)>, <socket.socket fd=568, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9090), raddr=('127.0.0.1', 58832)>]
    2020/08/21 21:41:41 - __main__ - INFO - 来自<socket.socket fd=568, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9090), raddr=('127.0.0.1', 58832)>的消息:傻子
    2020/08/21 21:41:41 - __main__ - INFO - rlist : [<socket.socket fd=544, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 9090)>, <socket.socket fd=568, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9090), raddr=('127.0.0.1', 58832)>]
    2020/08/21 21:41:41 - __main__ - INFO - <socket.socket fd=568, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9090), raddr=('127.0.0.1', 58832)>断开连接
    
  • 相关阅读:
    邮箱启用授权码发送邮件
    try里Response.end()问题
    NPOI导入excel文件为DataTable,使用SqlBulkCopy添加到数据库表
    Python入门(二)
    Python入门(一):PTVS写Python程序,调试模式下input()提示文字乱码问题
    【译】第4节---简单的Code First示例
    【译】第3节--- 配置开发环境
    【译】第2节--- 什么是Code First?
    【译】第1节--- EF Code First 介绍
    多条件查询----补发周一内容(六级让我忽略了JAVA)
  • 原文地址:https://www.cnblogs.com/zhujiangyu/p/13543540.html
Copyright © 2011-2022 走看看