2018.8.10
目录
day05
孤儿、僵尸进程
- 孤儿进程:当父进程先于子进程退出,此时子进程就会成为孤儿进程。
孤儿进程会被系统指定进程收养,即系统进程会成为孤儿进程新的父进程,系统进程会自动处理孤儿进程退出状态import os from time import sleep pid = os.fork() if pid == 0: print("child PID:",os.getppid()) sleep(2) print("parent PID again:",os.getppid()) elif pid > 0: sleep(1) print("parent process:",os.getpid())
- 僵尸进程:子进程先于父进程退出,父进程没有处理子进程的退出状态,此时子进程就会成为僵尸进程。
僵尸进程会滞留部分PCB信息在内存中,大量的僵尸进程会消耗系统的内存资源,所以要尽量避免僵尸进程产生import os from time import sleep pid = os.fork() if pid == 0: print("child PID:",os.getpid()) print("parent PID again:",os.getppid()) elif pid > 0: print("parent process:",os.getpid()) while True: pass #ps -aux,查看子进程pid是否是z,(z代表子进程)
如何避免僵尸进程产生
- 父进程先退出
- 父进程处理子进程退出状态
- pid,status = os.wait()
- 功能:在父进程中阻塞等待处理子进程的退出
- 返回值:
pid 退出的那个子进程的PID号
status 子进程的退出状态 - 获取原来退出状态
os.WEXITSTATUS(status)import os from time import sleep pid = os.fork() if pid < 0: print("create process faild") elif pid == 0: print("Child process running") sleep(3) print("Child process over") os._exit(3) else: #等子进程执行完毕进行回收 pid,status = os.wait() print(pid,status) print(os.WEXITSTATUS(status)) #原来退出状态 while True: pass #Child process running #Child process over #3340 768 #3
- pid,status = os.waitpid(pid,option)
- 功能:在父进程中阻塞等待处理子进程的退出
- 参数:
pid -1 表示等待任意子进程退出
>0 表示等待对应PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞 - 返回值:
pid 退出那个子进程的PID号
status 子进程的退出状态 - waitpid(-1,0) ===>wait()
import os from time import sleep pid = os.fork() if pid < 0: print("create process faild") elif pid == 0: print("Child process running") sleep(3) print("Child process over") os._exit(3) else: while True: sleep(1) pid1,status = os.waitpid(-1,os.WNOHANG) print(pid1,status) if pid1 > 0: break while True: pass
- 创建二级子进程
- 父进程创建子进程等待子进程退出
- 子进程创建下一级子进程,然后立即退出
- 二级子进程成为孤儿,处理具体工作
#二级子进程处理僵尸问题 import os from time import sleep def fun1(): sleep(3) print("第一件事情") def fun2(): sleep(4) print("第二件事情") pid = os.fork() if pid < 0: print("create process failed") elif pid == 0: #创建二级子进程 pid1 = os.fork() if pid1 == 0: fun2() #执行fun2 elif pid1 > 0: os._exit(0) #子进程退出 else: os.wait() fun1()
写一个聊天室
功能:类似于qq群聊
- 进入聊天室需要输入姓名,姓名不能重复
- 有人进入聊天室此时会向其他人发起通知
xxx进入了聊天室 - 如果一个人发消息,其它人都可以收到
xxx说:xxxx - 如果某个人退出聊天室其它人也会收到通知
xxx 退出了聊天室 - 服务端可以喊话:此时群里所有人都能收到服务端消息
管理员 说:xxx
- 整体结构:分为几部分,如何封装,使用什么样的技术手段
- 服务端
- 客户端
- 在客户端和服务端每个功能封装为一个函数
- 技术方案:
转发:一个客户发送给服务器,服务器发送给其他人 - 套接字使用:udp完成操作
- 用户存储:字典 或者 列表(可变类型,能够遍历提取)
地址 用户名 - 发送和接收消息的控制:发送和接收使用多进程分离互不影响
注意事项
- 注重封装
- 分段测试
代码编写流程
- 搭建通信--》创建多进程--》每个进程功能确定--》实现每一个功能模块
功能细节梳理
- 进入聊天室
- 客户端:输入姓名
将信息发给我服务器 L name - 服务端:接受消息
判断请求类型
判断是否可以登录(姓名是否已经存在)
返回给客户端是否登录(如果可以服务端会将姓名插入到存储结构)
给所有人发送消息
- 客户端:输入姓名
- 聊天
- 客户端:发起聊天 c msg
接收服务器回复 - 服务端:接受消息
判断消息类型
组织消息结构转发给其他客户端
- 客户端:发起聊天 c msg
- 退出聊天室
- 客户端:发送消息退出 Q name
接受服务
退出程序 - 服务器:接收消息
判断请求类型
从用户结构删除对应用户
告知所有人 xxx退出
- 客户端:发送消息退出 Q name
服务器:
from socket import *
import os,sys
#发送管理员消息
def do_child(s,addr):
while True:
msg = input("管理员消息:")
msg = "C 管理员 " + msg
s.sendto(msg.encode(),addr)
#用户登录
def do_login(s,user,name,addr):
if (name in user) or name == "管理员":
s.sendto("该用户已存在".encode(),addr)
return
s.sendto(b'OK',addr)
#通知所有人
msg = "\n欢迎 %s 进入聊天室"%name
for i in user:
s.sendto(msg.encode(),user[i])
#插入user
user[name] = addr
def do_chat(s,user,name,data):
msg = "\n{} 说: {}".format(name,data)
for i in user:
if i != name:
s.sendto(msg.encode(),user[i])
def do_quit(s,user,name):
msg = "\n%s 离开了聊天室"%name
for i in user:
if i == name:
s.sendto(b'EXIT',user[i])
else:
s.sendto(msg.encode(),user[i])
del user[name] #删除离开的用户
#接收客户端请求并处理
def do_parent(s):
# 用于存储用户 {'Alex':('127.0.0.1',8888)}
user = {}
while True:
msg,addr = s.recvfrom(1024)
msgList = msg.decode().split(' ')
if msgList[0] == 'L':
do_login(s,user,msgList[1],addr)
elif msgList[0] == 'C':
# "C Levi [I miss you]"
data = ' '.join(msgList[2:])
do_chat(s,user,msgList[1],data)
elif msgList[0] == 'Q':
do_quit(s,user,msgList[1])
# 创建套接字,网络连接,创建父子进程
def main():
#server address
ADDR = ('0.0.0.0',8888)
#创建套接字
s = socket(AF_INET,SOCK_DGRAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(ADDR)
#创建父子进程
pid = os.fork()
if pid < 0:
sys.exit("创建进程失败")
elif pid == 0:
do_child(s,ADDR)
else:
do_parent(s)
if __name__ == "__main__":
main()
客户端:
from socket import *
import sys,os
def login(s,ADDR):
while True:
name = input("请输入用户名:")
msg = "L " + name
s.sendto(msg.encode(),ADDR)
#接收登录结果
data,addr = s.recvfrom(1024)
if data.decode() == 'OK':
print("@进入聊天室@")
return name
else:
print(data.decode())
#发送消息
def do_child(s,name,addr):
while True:
text = input("发言(quit退出):")
#退出
if text.strip() == "quit":
msg = "Q " + name
s.sendto(msg.encode(),addr)
sys.exit("退出聊天室")
msg = "C %s %s"%(name,text)
s.sendto(msg.encode(),addr)
#接收消息
def do_parent(s):
while True:
msg,addr = s.recvfrom(1024)
if msg.decode() == 'EXIT':
sys.exit(0)
print(msg.decode()+"\n发言(quit退出):",end="")
#main控制套接字的创建
def main():
if len(sys.argv) < 3:
print("argv is error")
return
HOST = sys.argv[1]
PORT = int(sys.argv[2])
ADDR = (HOST,PORT)
s = socket(AF_INET,SOCK_DGRAM)
name = login(s,ADDR)
if name:
pid = os.fork()
if pid < 0:
sys.exit("创建子进程失败")
elif pid == 0:
do_child(s,name,ADDR)
else:
do_parent(s)
else:
return
if __name__ == "__main__":
main()
multiprocessing 模块创建进程
- 需要将要做的事情进行封装成函数
- 使用multiprocessing 提供的类Process创建进程对象
- 通过进程对象和Process初始化进程进行进程的设置,绑定函数
- 启动进程,会自动执行绑定的函数
- 完成进程的回收
创建进程对象
Process()
- 功能:创建进程对象
- 参数:target:要绑定的函数
name:给进程起的,名称(默认process-1)
args:元组 用来给target函数位置传参
kwargs:字典 用来给target函数键值传参
p.start()
- 功能:启动进程 自动运行terget绑定函数,此时进程被创建
p.join([timeout])
- 功能:阻塞等待子进程退出
- 参数:超时时间
- 使用multiprocessing创建进程子进程同样复制父进程的全部内存空间,之后有自己独立空间,执行上互不干扰
- 子进程也是有自己特有的PID等资源
- 如果不使用join回收可能会产生僵尸进程
- 使用multiprocessing创建子进程,一般父进程功能就是创建子进程回收子进程,所有事件交给子进程完成
import multiprocessing as mp from time import sleep import os a = 1 def fun(): sleep(2) print("子进程事件",os.getpid()) global a a = 10000 print("a = ",a) #创建进程对象 p = mp.Process(target = fun) #启动进程 p.start() sleep(3) print("这是父进程") #回收进程 p.join() print("parent a:",a) # while True: # pass
作业:
- 梳理,聊天室代码
- 对进程概念和创建过程巩固
- 加深对http协议的理解
- 使用父子进程复制一个文件,分别复制文件的上半部分和下半部分到一个新的文件中,以字节区分
解析:import os from multiprocessing import Process from time import sleep #获取文件的大小 size = os.path.getsize("./timg.jpeg") # f = open("timg.jpeg",'rb') #复制前半部分 def copy1(img): f = open(img,'rb') n = size // 2 fw = open('1.jpeg','wb') while True: if n < 1024: data = f.read(n) fw.write(data) break data = f.read(1024) fw.write(data) n -= 1024 f.close() fw.close() #复制后半部分 def copy2(img): f = open(img,'rb') fw = open('2.jpeg','wb') f.seek(size // 2,0) while True: data = f.read(1024) if not data: break fw.write(data) fw.close() f.close() p1 = Process(target = copy1,args = ('timg.jpeg',)) p2 = Process(target = copy2,args = ('timg.jpeg',)) p1.start() p2.start() p1.join() p2.join()