03_多进程

1.进程相关的概念

    1.同一个程序每次执行都有不同的进程,因为分配的计算机资源不同,进程由代码段,数据段,和PCB(进程控制块)组成

    2.进程的特征: 进程是操作系统资源分配的最小单位,每个进程单独占有4G的虚拟内存,进程之间相互独立,运行不受影响

    3.程序和进程的区别
        进程: 程序在计算机中一次执行的过程,占有CPU内存的计算机资源,有一定的生命周期
        程序: 是一个静态的描述,不占有计算机资源

    4.进程的状态
        1.三态: 就绪态,运行态,等待态
            就绪态: 进程具体运行条件,等待系统分配处理器运行
            运行态: 进程占有CPU处于运行的状态
            等待态: 又称阻塞态,睡眠态,进程暂时不具备运行条件,需要阻塞等待(sleep,accept),此时进程还在内存中等待获取CPU
        2.五态: 在三态上增加了新建态和终止态
            新建态: 创建一个进程,获取资源,直接表现为运行一个程序或者在程序中创建新的进程
            终止态: 进程执行结束,资源回收过程
        3.进程特殊的挂起状态: 是指各种原因进程放弃了CPU,导致进程无法继续执行,此时进程被踢出内存
        4.进程状态流程图: https://www.processon.com/view/link/5f1059f7f346fb2bfb29f2ba

    5.进程优先级
        优先级决定了一个进程的纸箱权限和占有资源的优先程度,用户程序默认优先级是0,优先级范围是 -20 ~ 19  # -20最高
        nice:
            以指定的优先级运行进程
            nice -9 ./while.py  # 以9的优先级运行此程序,以小于0的优先级运行必须加root权限
            sudo nice --9 ./while.py  # 用root权限的-9优先级运行此程序
        renice
            改变某个进程的优先级  # 改变已经在运行中的程序的优先级
            sudo renice 2 进程PID号  # 让进程以2的优先级继续运行

    6.进程的信息和保存
        PCB(进程控制块): 在Unix系统组进程创建后会在内存中开辟一块空间存放进程的相关信息,称为PCB
        PID: 在操作系统中进程的唯一标志,是大于0的整数,由系统自动分配
        ps -ajx  # 可以看到父进程的进程PID
        ps -aux  # 查看进程信息
            用户 PID 占有内存 优先级 进程状态等
            D: 等待态(不可中断等待)
            S: 等待态(可中断等待)
            T: 等待态(暂停)
            R: 运行态
            Z: 僵尸态
            +: 前台进程(不带+即为后台进程)
            <: 高优先级
            N: 低优先级
            l: 有进程链接,即有关联的进程
            s: 会话组,有一组进程实现一个大功能,会话组就相当于小组长
        top  # 动态查看当前运行的进程的状态,q退出,< >翻页
        htop  # 动态查看当前运行的进程的状态,top命令的升级版
        pstree  # 以树型结构查看进程

    7.进程的调度
        1.先来先服务
            先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度
            FCFS算法比较有利于长作业进程,而不利于短作业进程,由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业进程
        2.短作业优先
            短作业进程优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度
            但其对长作业不利,不能保证紧迫性作业进程被及时处理,作业的长短只是被估算出来的
        3.时间片轮转
            1.时间片轮转(Round Robin RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例,
                在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒,
                如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,
                等待下一次调度,同时,进程调度程序又去调度当前就绪队列中的第一个进程
            2.因此轮转法只能用来调度分配一些可以抢占的资源,这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程,
                CPU是可抢占资源的一种,但打印机等资源是不可抢占的,由于作业调度是对除了CPU之外的所有系统硬件资源的分配,
                其中包含有不可抢占资源,所以作业调度不使用轮转法
            3.在轮转法中,时间片长度的选取非常重要,首先时间片长度的选择会直接影响到系统的开销和响应时间,
                如果时间片长度过短,则调度程序抢占处理机的次数增多,这将使进程上下文切换次数也大大增加,从而加重系统开销,
                反过来,如果时间片长度选择过长,例如一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法,
                时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的
            4.在轮转法中,加入到就绪队列的进程有3种情况
                一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行
                另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞,当阻塞解除之后再回到就绪队列
                第三种情况就是新创建进程进入就绪队列
                如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率
                例如:
                    我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,
                    各队列之间的进程享有不同的优先级,但同一队列内优先级相同,
                    这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列
        4.多级反馈队列
            前3种用作进程调度的算法都有一定的局限性,如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,
            则短进程优先和基于进程长度的抢占式调度算法都将无法使用,而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,
            而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法,在采用多级反馈队列调度算法的系统中,
            调度算法的实施过程如下所述
            1.应设置多个就绪队列,并为各个队列赋予不同的优先级,第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低,
                该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小,
                例如,第二个队列的时间片要比第一个队列的时间片长一倍, ...,第i+1个队列的时间片要比第i个队列的时间片长一倍
            2.当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度,当轮到该进程执行时,如它能在该时间片内完成,
                便可准备撤离系统,如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行,
                如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列, ..., 如此下去
                当一个长作业进程从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行
            3.仅当第一队列空闲时,调度程序才调度第二队列中的进程运行,仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行,
                如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),
                则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程

2.多进程相关的概念

    1.多个进程如何占有CPU
        一个内核同一时刻只能运行一个任务,多个进程对内核资源进行争夺,最终结果根据操作系统的时间片轮转和进程优先级决定
        操作系统决来定哪个进程占用计算机核心,占有计算机核心的进程称为该进程占有CPU的时间片

    2.父子进程: 在系统除了初始化进程其它进程都有一个父进程,可能有多个子进程

    3.os.fork创建进程的流程
        1.用户空间运行程序发起进程创建申请,调用操作系统内核接口创建进程
        2.操作系统分配计算机资源,确定进程状态,将新的进程提供给用户使用

    4.multiprocessing.Process创建进程的流程
        1.需要将进程事件封装成函数,然后使用multiprocessing模块提供的Process类创建进程
        2.新的进程和对应的函数相关联,进程启动会自动执行函数完成事件,最后回收进程

    5.多进程优缺点
        优点: 并行多个任务,提高运行效率,空间独立,数据安全,创建方便
        缺点: 进程创建销毁的过程中消耗较多的计算机资源

    6.计算机开启的进程数量建议: CPU核数 + 1

3.利用os模块的fork方法实现多进程

1.os模块中进程相关函数实现多进程

        1.fork方法概述
            父进程中fork之前的内容子进程同样会复制,但父子进程空间各自独立,fork之后的修改不会影响对方
            父子进程在执行上互不影响,谁先执行谁后执行完全不确定,子进程虽然复制父进程的空间,但是有自己的特性(如PID,PCB,栈空间等)
            fork()函数实现多进程流程图: https://www.processon.com/view/link/5f10dd93f346fb2bfb2a43a0

        2.进程的创建
            pid = os.fork()  # 创建一个新的进程,无参数
            创建失败: 返回一个负数(-1)
            创建成功: 在子进程中fork的返回值0,在父进程中的返回值是大于0的正整数(新的进程PID)

        2.获取PID号
            os.getpid()  # 获取当前进程的PID号并返回
            os.getppid()  # 获取当前进程父进程的PID并返回

        3.进程的退出
            os._exit(status)  # 结束一个进程,status表示进程的结束状态,是一个整数
            sys.exit([status])  # 结束一个进程并抛出异常,传入一个正整数表示结束状态,传入字符串表示打印结束语

        5.fork方法实现多进程

import os
import time
import sys


def func1(L):
    time.sleep(5)
    L.append(33)
    print("this is %s" % sys._getframe().f_code.co_name)  # sys._getframe().f_code.co_name 取函数名
    print("my pid is %s, my list is %s" % (os.getpid(), L))


def func2(L):
    time.sleep(3)
    L.append(44)
    print("this is %s" % sys._getframe().f_code.co_name)  # sys._getframe().f_code.co_name 取函数名
    print("my pid is %s, my list is %s" % (os.getpid(), L))


def main():
    L = [11, 22]
    print("准备创建进程")
    pid = os.fork()
    if pid < 0:
        print("创建进程失败")
    elif pid == 0:
        # os.getpid()取进程pid号  os.getppid()取父进程pid号
        print("子进程中执行的代码: pid is %s 父进程的pid是: %s 调用函数func2" % (os.getpid(), os.getppid()))
        func2(L)
    else:
        # 在父进程中pid = os.fork()执行成功后的返回值pid就是子进程的pid号
        print("父进程中执行的代码: pid is %s 子进程的pid是: %s 调用函数func1" % (os.getpid(), pid))
        func1(L)
    print("父子进程都会执行的代码: my pid is %s" % os.getpid())


if __name__ == "__main__":
    main()
"""执行结果
    准备创建进程
    父进程中执行的代码: pid is 17115 子进程的pid是: 17116 调用函数func1
    子进程中执行的代码: pid is 17116 父进程的pid是: 17115 调用函数func2
    this is func2
    my pid is 17116, my list is [11, 22, 44]
    父子进程都会执行的代码: my pid is 17116
    this is func1
    my pid is 17115, my list is [11, 22, 33]
    父子进程都会执行的代码: my pid is 17115
"""

2.孤儿进程

        1.孤儿进程概述
            父进程先于子进程退出,此子进程变成孤儿进程,孤儿进程会被系统指定的进程所收养,即该系统指定的进程成为孤儿进程新的父进程
            在孤儿进程退出时,系统指定的进程会进行处理不会使其变成僵尸进程

        2.验证孤儿进程的产生

import os
import time


def main():
    pid = os.fork()
    if pid < 0:
        print("创建新的进程失败")
    elif pid == 0:
        time.sleep(1)
        # 父进程已经退出,打印的将是系统分配的新的父进程pid
        print("父进程的pid是: %s" % os.getppid())
    else:
        print("父进程的pid是: %s" % os.getpid())


if __name__ == "__main__":
    main()
"""执行结果
    ~/Desktop/python3/demo $ python3 demo5.py
    父进程的pid是: 17082
    ~/Desktop/python3/demo $ 父进程的pid是: 1
"""

3.僵尸进程

        1.僵尸进程概述
            子进程先于父进程退出,但是父进程没有处理子进程的退出情况,此时子进程变成僵尸进程,僵尸进程会滞留PCB的部分信息在内存中
            僵尸进程的危害: 大量的僵尸进程会消耗系统资源应该尽量避免僵尸进程的产生

        2.验证僵尸进程的产生

import os


def main():
    pid = os.fork()
    if pid < 0:
        print("创建新的进程失败")
    elif pid == 0:
        print("子进程的pid是: %s" % os.getpid())
    else:
        # 子进程退出,父进程不退出
        print("父进程的pid是: %s" % os.getpid())
        while True:
            pass


if __name__ == "__main__":
    main()
"""执行结果父进程会死循环不结束
    ~/Desktop/python3/demo $ python3 demo5.py
    父进程的pid是: 17199
    子进程的pid是: 17200
"""
"""开启一个新的终端根据子进程的pid号验证僵尸进程 Z+
    ~/Desktop/python3/demo $ ps aux | grep "17200" | grep -v grep
    tangxuecheng     17200   0.0  0.0        0      0 s001  Z+    9:38上午   0:00.00 (Python)
 """

4.避免僵尸进程的产生-让父进程先退出: 这个方法不好控制,实际开发中一般都不会使用

5.避免僵尸进程的产生-让父进程处理子进程的退出(使用wait方法或者waitpid方法)

        1.利用wait方法处理子进程-原理是阻塞等待子进程执行结束,所以不推荐使用此方法避免僵尸进程

import os
import time
import sys


def main():
    pid = os.fork()
    if pid < 0:
        print("创建新的进程失败")
    elif pid == 0:
        print("子进程的pid是: %s" % os.getpid())
        time.sleep(1)
        sys.exit(1)
    else:
        # os.wait()阻塞等待子进程的退出处理子进程后再继续执行父进程,无参数
        # 返回值: 一个二元元组,第一个值为退出的子进程PID,第二个值为子进程退出状态
        p, status = os.wait()  # 此时等待子进程执行完毕后解除阻塞
        print(p, status, os.WEXITSTATUS(status))  # os.WEXITSTATUS(status)获取结束状态原数
        # 子进程退出,父进程不退出
        print("父进程的pid是: %s" % os.getpid())
        while True:
            pass


if __name__ == "__main__":
    main()
"""执行结果
    子进程的pid是: 17735
    17735 256 1
    父进程的pid是: 17734
"""

        2.利用waitpid方法处理子进程-原理是挂机父进程,当子进程执行结束再执行父进程,所以也不推荐使用

import os
import time
import sys


def main():
    pid = os.fork()
    if pid < 0:
        print("创建新的进程失败")
    elif pid == 0:
        print("子进程的pid是: %s" % os.getpid())
        time.sleep(1)
        sys.exit(1)
    else:
        # os.waitpid(pid, option)挂起父进程,等待子进程执行结束处理子进程后再执行父进程
        # 第一个参数pid: -1表示等待任意的子进程退出;>0的整数表示等待相应pid号的子进程退出
        # 第二个参数option: 0表示挂起父进程,子进程完成任务后父进程再继续执行父进程;os.WNOHANG表示不挂起父进程
        # 返回值: 一个二元元组,第一个值为退出的子进程PID,第二个值为子进程退出状态
        p, status = os.waitpid(-1, 0)  # 此时挂起父进程等待子进程执行结束后再继续执行父进程,所以不推荐使用此方法
        print(p, status, os.WEXITSTATUS(status))  # os.WEXITSTATUS(status)获取结束状态原数
        # 子进程退出,父进程不退出
        print("父进程的pid是: %s" % os.getpid())

        # # 设置为非阻塞状态,循环处理查看子进程状态
        # while True:
        #     try:
        #         p, status = os.waitpid(-1, os.WNOHANG)  # 此时不会挂起父进程而是直接向下继续执行
        #         print(p, status, os.WEXITSTATUS(status))  # os.WEXITSTATUS(status)获取结束状态原数
        #         time.sleep(0.5)
        #         # 子进程退出,父进程不退出
        #         print("父进程的pid是: %s" % os.getpid())
        #     except ChildProcessError as e:
        #         print(e)
        #         break

        while True:
            pass


if __name__ == "__main__":
    main()
"""执行结果
    子进程的pid是: 17899
    17899 256 1
    父进程的pid是: 17898
"""

6.避免僵尸进程的产生-让父进程处理子进程的退出(使用信号处理)

import os
import signal


def main():
    pid = os.fork()
    if pid < 0:
        print("创建新的进程失败")
    elif pid == 0:
        print("子进程的pid是: %s" % os.getpid())
    else:
        # 使用信号避免僵尸进程的原理: 在父进程中忽略子进程发送的信号
        signal.signal(signal.SIGCHLD, signal.SIG_IGN)
        # 子进程退出,父进程不退出
        print("父进程的pid是: %s" % os.getpid())
        while True:
            pass


if __name__ == "__main__":
    main()
"""执行结果父进程会死循环不结束
    ~/Desktop/python3/demo $ python3 demo5.py
    父进程的pid是: 3670
    子进程的pid是: 3671
"""
"""开启一个新的终端子根据子进程的pid号已近找不到对应的进程
    ~/Desktop/python3/demo $ ps aux | grep "3671" | grep -v grep
"""

7.避免僵尸进程的产生-创建二级子进程(让二级子进程成为孤儿进程)

        1.实现过程
            1.父进程创建一级子进程后等待一级子进程退出
            2.一级子进程创建二级子进程后马上退出,让二级子进程成为孤儿进程有系统管理
            3.让父进程和二级子进程处理具体的事件

        2.二级子进程避免僵尸进程示例验证

import os
import sys


def main():
    # 创建一级子进程
    pid1 = os.fork()
    if pid1 < 0:
        print("创建一级子进程失败")
    elif pid1 == 0:
        # 创建二级子进程
        pid2 = os.fork()
        if pid2 < 0:
            print("创建二级子进程失败")
        elif pid2 == 0:
            print("二级子进程的pid是: %s" % os.getpid())
            # 二级子进程继续处理自己的事件
            while True:
                pass
        else:
            # 一级子进程退出,使二级子进程成为孤儿进程
            sys.exit(0)
    else:
        # 等待一级子进程退出
        os.wait()
        print("父进程的pid是: %s" % os.getpid())
        # 父进程继续处理自己的事件
        while True:
            pass


if __name__ == "__main__":
    main()
"""执行结果
    二级子进程的pid是: 18629
    父进程的pid是: 18627
"""

8.UDP服务端-多任务聊天室

import socket
import sys
import traceback
import os


class UdpSocketServer:
    """UDP聊天室服务器对象"""

    def __init__(self):
        """完成UDP套接字创建和存储用户信息"""
        try:
            if len(sys.argv) < 3:
                print("python3 udp_server 127.0.0.1 7890")
                sys.exit(1)
            self.udp_socket_addr = (sys.argv[1], int(sys.argv[2]))
            # 1. 创建套接字
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # 2. 绑定本地信息
            self.udp_socket.bind(self.udp_socket_addr)
        except Exception:
            traceback.print_exc()
            sys.exit(1)

        # 用字典存储用户信息{name: (ip, port)}
        self.user = dict()
        # 客户端发送的消息列表
        self.msg_list = None
        # 客户端的地址
        self.recv_addr = None
        # 需要回送给客户端的消息
        self.msg = None

    def send_msg(self, recv_addr=None):
        """实现单发消息和群发消息"""
        # 发送数据
        if recv_addr:
            self.udp_socket.sendto(self.msg.encode("utf-8"), recv_addr)
        else:
            self.udp_socket.sendto(self.msg.encode("utf-8"), self.recv_addr)

    def recv_msg(self):
        """接收并清洗消息"""
        # 1. 接收数据
        recv_msg, recv_addr = self.udp_socket.recvfrom(1024)
        recv_msg = recv_msg.decode("utf-8")
        self.msg_list = recv_msg.split(" ")
        self.recv_addr = recv_addr

    def do_login(self):
        """登录"""
        if (self.msg_list[1] in self.user) or self.msg_list[1] == "管理员":
            self.msg = "该用户已存在,请重新输入"
            self.send_msg()
            return
        self.msg = "OK"
        self.send_msg()

        # 通知所有人
        self.msg = "\n欢迎%s进入聊天室" % self.msg_list[1]
        for i in self.user:
            self.send_msg(self.user[i])
        # 将用户插入字典
        self.user[self.msg_list[1]] = self.recv_addr

    def do_chat(self):
        """群聊"""
        self.msg = "\n%-4s: %s" % (self.msg_list[1], " ".join(self.msg_list[2:]))
        # 群发消息给除了客户端自己以外的所有人
        for i in self.user:
            if i != self.msg_list[1]:
                self.send_msg(self.user[i])

    def do_quit(self):
        """客户端退出"""
        del self.user[self.msg_list[1]]
        self.msg = "\n" + self.msg_list[1] + "离开了聊天室"
        for i in self.user:
            self.send_msg(self.user[i])

    def do_chlie(self):
        """处理客户端请求"""
        # 循环接收请求
        while True:
            self.recv_msg()
            if self.msg_list[0] == "登录":
                self.do_login()
            elif self.msg_list[0] == "聊天":
                self.do_chat()
            elif self.msg_list[0] == "退出":
                self.do_quit()
            else:
                self.msg = "错误的请求"
                self.send_msg()

    def do_parent(self):
        """实现管理员群发消息"""
        while True:
            msg = input("管理员发言: ")
            self.msg = "聊天 %s %s" % ("管理员", msg)
            self.send_msg(self.udp_socket_addr)

    def run(self):
        """run方法创建父子进程完成功能分类"""
        # 创建一级子进程
        pid1 = os.fork()
        if pid1 < 0:
            print("创建一级子进程失败")
        elif pid1 == 0:
            # 创建二级子进程
            pid2 = os.fork()
            if pid2 < 0:
                print("创建二级子进程失败")
            elif pid2 == 0:
                # 二级子进程处理客户端请求
                self.do_chlie()
            else:
                # 一级子进程退出,使二级子进程成为孤儿进程,避免僵尸进程的产生
                sys.exit(0)
        else:
            # 等待一级子进程退出
            os.wait()
            # 父进程发送管理员消息
            self.do_parent()

    def __del__(self):
        """关闭套接字"""
        print("%s对象已回收" % self)
        self.udp_socket.close()


def main():
    """程序入口完成主逻辑控制"""
    # 实例化一个udp聊天室对象
    udp_chat = UdpSocketServer()
    # 启动聊天室
    udp_chat.run()


if __name__ == "__main__":
    main()

9.UDP客户端-多任务聊天室

import socket
import sys
import traceback
import os
import signal


class UdpSocketClient:
    """UDP聊天室客户端对象"""

    def __init__(self):
        """完成UDP套接字创建和存储用户信息"""
        try:
            if len(sys.argv) < 3:
                print("python3 udp_client 127.0.0.1 7890")
                sys.exit(1)
            self.udp_socket_addr = (sys.argv[1], int(sys.argv[2]))
            # 1. 创建套接字
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        except Exception:
            traceback.print_exc()
            sys.exit(1)

        # 客户端姓名
        self.name = None
        # 客户端要发送的消息
        self.msg = None
        # 记录登录状态
        self.login_is_ok = False

    def send_msg(self):
        """发送消息"""
        self.udp_socket.sendto(self.msg.encode("utf-8"), self.udp_socket_addr)

    def recv_login_status(self):
        """接收登录请求结果"""
        recv_msg, _ = self.udp_socket.recvfrom(1024)  # 用不到返回的地址,因此用_保存
        # 判断是否登录成功
        if recv_msg.decode("utf-8") == "OK":
            print("已经成功进入聊天室")
            self.login_is_ok = True
        else:
            print(recv_msg.decode("utf-8"))

    def do_parent(self):
        """整理要发送的请求"""
        while True:
            self.msg = input("发言(quit退出): ")
            if self.msg == "quit":
                self.msg = "退出 " + self.name
                self.send_msg()
                os.kill(os.getppid(), signal.SIGKILL)  # 从子进程中杀死父进程
                sys.exit("退出聊天室")  # 子进程退出
            else:
                self.msg = "聊天 %s %s" % (self.name, self.msg)
                self.send_msg()

    def do_chlie(self):
        """接收消息"""
        while True:
            self.msg, _ = self.udp_socket.recvfrom(1024)  # 用不到返回的地址,因此用_保存
            print(self.msg.decode("utf-8") + "\n发言(quit退出): ", end="")

    def run(self):
        """run方法创建父子进程完成功能分类"""
        while True:
            self.name = input("请输入姓名: ").strip()
            self.msg = "登录 " + self.name
            self.send_msg()
            self.recv_login_status()
            if self.login_is_ok:
                break
        # 创建子进程
        pid = os.fork()
        if pid < 0:
            print("创建子进程失败")
        elif pid == 0:
            # 子进程发送消息
            self.do_parent()
        else:
            # 父进程接收消息
            self.do_chlie()

    def __del__(self):
        """关闭套接字"""
        print("%s对象已回收" % self)
        self.udp_socket.close()


def main():
    """程序入口完成主逻辑控制"""
    # 实例化一个udp聊天室对象
    udp_chat = UdpSocketClient()
    # 启动聊天室
    udp_chat.run()


if __name__ == "__main__":
    main()

10.简单的并发服务器-fork实现

from socket import *
import os
import sys
import signal


def client_handler(c):
    try:
        print("子进程接收%s客户端的请求" % str(c.getpeername()))
        while True:
            data = c.recv(1024)
            if not data:
                break
            print(data.decode("utf-8"))
            c.send(b"receive your message")
    except (KeyboardInterrupt, SystemError):
        raise
    except Exception as e:
        print(e)
    # 关闭客户端套接字
    c.close()
    # 结束子进程
    sys.exit(0)


def main():
    # 创建套接字
    s = socket()
    # 端口重用
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 绑定本地信息
    s.bind(("", 7890))
    # 监听
    s.listen(128)
    print("父进程%d等待客户端的链接" % os.getpid())
    while True:
        try:
            c, addr = s.accept()
        except KeyboardInterrupt:
            raise
        except Exception as e:
            print(e)
            continue
        # 为客户端创建新的进程
        # 处理僵尸进程
        signal.signal(signal.SIGCHLD, signal.SIG_IGN)
        pid = os.fork()
        if pid < 0:
            print("创建子进程失败")
            c.close()
        elif pid == 0:
            s.close()  # 子进程中关闭监听套接字
            # 处理客户端请求
            client_handler(c)
        else:
            c.close()  # 父进程中关闭客户端套接字
            continue


if __name__ == "__main__":
    main()

11.简单的并发服务器-测试客户端

import socket


def main():
    # 创建数据流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 连接服务器
    server_ip = input("请输入要连接的服务器的ip:")
    serve_port = int(input("请输入要连接的服务器的port:"))
    server_addr = (server_ip, serve_port)
    tcp_client_socket.connect(server_addr)
    # 发送数据
    send_data = input("请输入要发生的数据:")
    tcp_client_socket.send(send_data.encode("utf-8"))
    # 接收服务器发送过来的数据
    recv_data = tcp_client_socket.recv(1024)
    print("接收到的数据为:%s" % recv_data.decode("utf-8"))
    # 关闭套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

12.TFTP文件下载并发服务器-fork实现

from socket import *
import os
import sys
import signal
import time

# 文件库位置
FILE_PATH = "./05_mini-web框架/"


class TftpServer:
    """服务端处理类"""

    def __init__(self, connfd):
        self.connfd = connfd

    def do_list(self):
        filelist = os.listdir(FILE_PATH)
        if not filelist:
            self.connfd.send(b"N")
            return
        else:
            self.connfd.send(b"Y")
        time.sleep(0.1)
        files = str()
        for filename in filelist:
            if not filename.startswith(".") and os.path.isfile(FILE_PATH + filename):
                files = files + filename + "#"
        self.connfd.send(files.encode("utf-8"))

    def do_get(self, filename):
        try:
            fd = open(FILE_PATH + filename, "rb")
        except Exception:
            self.connfd.send(b"N")
            return
        else:
            self.connfd.send(b"Y")
            time.sleep(0.1)
            while True:
                data = fd.read(1024)
                if not data:
                    time.sleep(0.1)
                    self.connfd.send(b"##")
                    break
                self.connfd.send(data)
            fd.close()
            print("发送文件成功")

    def do_put(self, filename):
        try:
            fd = open(FILE_PATH + filename, "wb")
        except Exception:
            self.connfd.send(b"N")
            return
        self.connfd.send(b"Y")
        time.sleep(0.1)
        while True:
            data = self.connfd.recv(1024)
            if data == b"##":
                break
            fd.write(data)
        fd.close()
        print("接收文件完毕")


def main():
    """完成主流程控制"""
    if len(sys.argv) < 3:
        print("argv is error")
        sys.exit(1)
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])
    ADDR = (HOST, PORT)
    BUFFERSIZE = 1024
    # 创建套接字
    sockfd = socket()
    # 端口重用
    sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    # 绑定
    sockfd.bind(ADDR)
    # 监听
    sockfd.listen(128)
    # 忽略子进程改变状态的信号避免僵尸进程
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)

    while True:
        try:
            connfd, addr = sockfd.accept()
        except KeyboardInterrupt:
            sockfd.close()
            sys.exit(0)
        except Exception:
            continue
        print("客户%s登录" % str(addr))
        pid = os.fork()
        if pid < 0:
            print("创建子进程失败")
            connfd.close()
        elif pid == 0:
            sockfd.close()
            tftp = TftpServer(connfd)
            while True:
                data = connfd.recv(BUFFERSIZE).decode("utf-8")
                if not data:
                    print("客户端强制退出了...")
                    sys.exit(0)
                if data[0] == "L":
                    tftp.do_list()
                elif data[0] == "G":
                    filename = data.split(" ")[-1]
                    tftp.do_get(filename)
                elif data[0] == "P":
                    filename = data.split(" ")[-1]
                    tftp.do_put(filename)
                elif data[0] == "Q":
                    print("客户端已正常退出")
                    sys.exit(0)
        else:
            connfd.close()
            continue


if __name__ == "__main__":
    main()

13.TFTP文件下载客户端

from socket import *
import sys
import time


class TftpClient:
    """客户端请求类"""

    def __init__(self, sockfd):
        self.sockfd = sockfd

    def do_list(self):
        # 发送请求类别
        self.sockfd.send(b"L")
        # 服务器回复 Y/N
        data = self.sockfd.recv(1024).decode("utf-8")
        if data == "Y":
            data = self.sockfd.recv(1024).decode("utf-8")
            files = data.split("#")
            for file in files:
                print(file)
            print("文件列表展示完成")
        else:
            print("请求文件列表失败...")

    def do_get(self, filename):
        # 发送请求类别
        self.sockfd.send(("G " + filename).encode("utf-8"))
        # 服务器回复 Y/N
        data = self.sockfd.recv(1024).decode("utf-8")
        if data == "Y":
            fd = open(filename, "wb")
            while True:
                data = self.sockfd.recv(1024)
                if data == b"##":
                    break
                fd.write(data)
            print("文件下载完成")
        else:
            print("文件下载失败...")

    def do_put(self, filename):
        try:
            fd = open(filename, "rb")
        except Exception:
            print("上传的文件不存在...")
            return
        # 发送请求类别
        self.sockfd.send(("P " + filename).encode("utf-8"))
        # 服务器回复 Y/N
        data = self.sockfd.recv(1024).decode("utf-8")
        if data == "Y":
            while True:
                data = fd.read(1024)
                if not data:
                    time.sleep(0.1)
                    self.sockfd.send(b"##")
                    break
                self.sockfd.send(data)
            fd.close()
            print("文件%s上传完成" % filename)
        else:
            print("上传下载失败...")

    def do_quit(self):
        self.sockfd.send(b"Q")
        self.sockfd.close()
        sys.exit(0)


def main():
    """完成主流程控制"""
    if len(sys.argv) < 3:
        print("argv is error")
        sys.exit(1)
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])
    ADDR = (HOST, PORT)
    BUFFERSIZE = 1024
    # 创建套接字
    sockfd = socket()
    # 连接服务器
    sockfd.connect(ADDR)
    # 创建客户端请求对象
    tftp = TftpClient(sockfd)
    while True:
        print("======命令选项=======")
        print("********list********")
        print("******get file******")
        print("******put file******")
        print("********quit********")
        print("====================")
        data = input("请输入命令: ")
        if data.strip() == "list":
            tftp.do_list()
        elif data[:3] == "get":
            filename = data.split(" ")[-1]
            tftp.do_get(filename)
        elif data[:3] == "put":
            filename = data.split(" ")[-1]
            tftp.do_put(filename)
        elif data.strip() == "quit":
            tftp.do_quit()
        else:
            print("请输入正确的命令...")


if __name__ == "__main__":
    main()

4.标准库模块创建子进程(multiprocessing)

1.创建子进程类

import multiprocessing

p = multiprocessing.Process()  # 创建子进程
    参数:
        target: 如果传递了函数的引用,可以任务这个子进程就执行这里的代码
        args: 给target指定的函数传递的参数,以元组的方式传递
        kwargs: 给target指定的函数传递命名参数
        name: 给进程设定一个名字,可以不设定默认为process-N,N为从1开始递增的整数
        group: 指定进程组,大多数情况下用不到
p = multiprocessing.current_process()  # 获取当前进程对象

2.进程对象的属性和方法

p.start()  # 启动子进程,此时进程真正创建
p.join([timeout])  # 阻塞等待回收相应的子进程,timeout为超时等待时间默认为阻塞
p.terminate()  # 不管任务是否完成,立即终止子进程
p.is_alive()  # 进程状态,判断进程子进程是否还在活着

# 该属性不是Linux/Unix系统中所说的守护进程设置,设置必须在p.start()前,一般使用daemon = True时,不用再调 p.join()
p.daemon  # 默认值为False表示主进程结束后不影响子进程执行,设置为 p.daemon = True 表示主进程执行完毕所有的子进程一同退出
p.deamon = True  # 将p进程设置为守护进程,父进程代码结束守护进程就结束,且守护进程p不允许开启子进程
p.name  # 当前进程的别名,默认为Process-N,N为从1开始递增的整数
p.pid  # 当前进程的PID号

守护进程: 生命周期长,随系统创建随系统销毁,不受前端控制,后台运行,守护进程一般为操作系统进程或者自动化运行进程居多

3.验证守护进程

from multiprocessing import Process
import time


def func():
    time.sleep(2)
    print(123)


def func1():
    time.sleep(1)
    print('abc')


# 守护进程是根据主进程的代码执行完毕,守护进程就结束
if __name__ == '__main__':
    t = Process(target=func)
    t.daemon = True
    t.start()
    t1 = Process(target=func1)
    t1.start()
    print(456)

4.多进程-查看进程的pid

from multiprocessing import Process
import os


def run_proc():
    """子进程要执行的代码"""
    print('子进程运行中,pid=%d...' % os.getpid())  # os.getpid获取当前进程的进程号
    print('子进程将要结束...')


if __name__ == '__main__':
    print('父进程pid: %d' % os.getpid())  # os.getpid获取当前进程的进程号
    print('子进程%s的pid是: ' % (p.name, p.pid))
    p = Process(target=run_proc)
    p.start()
    p.join()

5.多进程-给子进程指定的函数传递参数

from multiprocessing import Process
import os
from time import sleep


def run_proc(name, age, **kwargs):
    for i in range(10):
        print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
        print(kwargs)
        sleep(0.2)


if __name__ == '__main__':
    p = Process(target=run_proc, args=('test', 18), kwargs={"m": 20})
    p.start()
    sleep(1)  # 1秒中之后,立即结束子进程
    p.terminate()

6.多进程-进程间不共享全局变量

from multiprocessing import Process
import os
import time

nums = [11, 22]


def work1():
    """子进程要执行的代码"""
    print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))
    for i in range(3):
        nums.append(i)
        time.sleep(1)
        print("in process1 pid=%d ,nums=%s" % (os.getpid(), nums))


def work2():
    """子进程要执行的代码"""
    print("in process2 pid=%d ,nums=%s" % (os.getpid(), nums))


if __name__ == "__main__":
    p1 = Process(target=work1)
    p1.start()
    p1.join()  # 阻塞等待回收进程

    p2 = Process(target=work2)
    p2.start()
    p2.join()  # 阻塞等待回收进程

7.多进程-文件切割拷贝

import os
import multiprocessing


def copy_file(num, size):
    f = open("./demo1.py", "rb")
    n = size // 2
    if num == 2:  # num为1时拷贝文件前半部分,为2是拷贝文件后半部分
        f.seek(n, 0)
    fw = open("./copy" + str(num) + ".py", "wb")
    while True:
        if n < 64:
            date = f.read(n)
            fw.write(date)
            break
        date = f.read(64)
        fw.write(date)
        n -= 64
    fw.close()
    f.close()


def main():
    size = os.path.getsize("./demo1.py")  # 获取一个文件的大小多少字节

    # 在创建进程前获取文件对象, 父子进程实际上是操作的一个文件流对象,会造成操作混乱
    # f = open("./demo1.py", "rb")

    p1 = multiprocessing.Process(target=copy_file, args=(1, size))
    p2 = multiprocessing.Process(target=copy_file, args=(2, size))

    p1.start()
    p2.start()
    p1.join()
    p2.join()


if __name__ == "__main__":
    main()

5.自定义进程类

import time
from multiprocessing import Process


class Clock_Process(Process):
    """自定义进程类继承自Process类"""
    def __init__(self, value):
        super().__init__()
        self.value = value

    # 在自定义的进程类中需要重写父类的run方法
    def run(self):
        n = 5
        while n > 0:
            print("The time is {}".format(time.ctime()))
            time.sleep(self.value)
            n -= 1


def main():
    # 用自己的进程类创建进程
    p = Clock_Process(2)
    p.start()  # 告诉操作系统帮我去开启一个子进程-->就绪状态
    # p.run()  # 告诉操作系统现在马上帮我执行这个子进程-->执行
    p.join()


if __name__ == "__main__":
    main()
posted @ 2020-08-21 22:57  唐雪成  阅读(186)  评论(0编辑  收藏  举报