1.poll方法实现IO多路复用
p = poll()
p.register()
p.unregister()
p.poll()
2.epoll方法实现IO多路复用
* 效率更高
* 触发方式更多
* 可以监控的更多IO
3.struct模块
功能:python数据转换为bytes发送
Struct(fmt)生成数据格式对象
pack() 将数据打包转换为bytes
unpack() 将bytes数据解析
4.本地套接字
用于本地两个进程之间进行数据通信
5.多任务编程
6.进程(process)
*****************************************************************************************
一.基于fork的多进程编程
1.进程的运行特征
【1】进程可以使用计算机的多核资源
【2】进程是计算机分配资源的最小单位
【3】进程之间互不影响,各自独立
【4】每个进程拥有独立的空间,各自使用自己的空间
2.fork 使用
pid = os.fork()
功能:创建新的进程
没有参数
返回值:整数 ,如果创建进程失败,返回负数
如果成功,则在原有进程中返回新进程的PID,在新进程中返回0
注意:*子进程会复制父进程的全部内存空间。从fork下一句开始执行
* 父子进程各自独立运行,运行顺序不一定,进程执行不同的内容几乎是固定搭配
*父子进程有各自特征比如
*父进程fork之前开辟的空间,子进程同样拥有,父子进程对各自空间的操作,
不会相互影响
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid < 0: 7 print("Creat pricess failed") 8 elif pid == 0: 9 sleep(2) 10 print("The new process") 11 else: 12 sleep(3) 13 print("The old process") 14 15 print("fork test over")
eg2:
eg3":
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 print("******************") 5 a= 1 6 7 8 pid = os.fork() 9 10 if pid < 0: 11 print("Creat process failed") 12 elif pid == 0: 13 print("Child process") 14 print("a=%d"%a) 15 a = 10000 16 else: 17 # sleep(1) 18 print("Parent proess") 19 print("a:",a) 20 21 print("all a=%d"%a) 22
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 print("******************") 5 a= 1 6 7 8 pid = os.fork() 9 10 if pid < 0: 11 print("Creat process failed") 12 elif pid == 0: 13 print("Child process") 14 print("a=%d"%a) 15 a = 10000 16 else: 17 sleep(1) 18 print("Parent proess") 19 print("a:",a) 20 21 print("all a=%d"%a) 22
父进程sleep(1),子进程在先
二.进程相关函数
1.os.getpid()
功能:获取一个进程的PID
返回值:返回当前进程的PID
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid <0: 7 print("Error") 8 elif pid == 0: 9 print("Child PID:",os.getpid()) 10 else: 11 print("Parent PID:",os.getpid())
2.os.getppid()
功能:获取父进程的PID号
返回值:返回父进程PID
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid <0: 7 print("Error") 8 elif pid == 0: 9 print("Child PID:",os.getpid()) 10 print("GET parent pid:",os.getppid())#在子进程中获取父进程pid 11 12 else: 13 print("Parent PID:",os.getpid()) 14 print("Det child pid",pid)#在父进程中获取子进程pid
3.os._exit(status)
功能:结束一个进程
参数:标书进程的终止状态
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 3 os._exit(0)#进程退出 4 5 print("Process exit")
4.sys.exit([status]) 默认0
功能:进程退出
参数:整数 退出状态
字符串 表示退出时打印内容
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os,sys 2 3 4 sys.exit("退出进程") 5 6 print("Process exit")
三.孤儿和僵尸
1.孤儿进程:父进程先于子进程退出此时子进程成为孤儿进程
* 孤儿进程会被系统进程收养,此时系统进程就会成为该进程新的父进程,
孤儿进程退出该进程会自动处理
2.僵尸进程:子进程先于父进程退出,父进程没有处理子进程的退出状态,此时子进程就会成为僵尸进程
* 僵尸进程虽然结束但是会存留部分PCB在内存,大量僵尸进程会占用内存资源
3.如何避免僵尸进程产生
[1]:使用wait函数处理子进程退出
pid,status = os.wait()
功能:在父进程中阻塞等待处理子进程退出
返回值:pid 退出的子进程的PID
status 子进程退出状态
pid,status = os.Waitpid(pid,option)
功能:在父进程中处理进程的进出状态I
参数:pid -l 表示等待任意子进程退出
>0表示指定的子进程退出
option 0 表示阻塞等待
WHOHANG 表示非阻塞
返回值:pid 退出的子进程的PID
status 子进程退出状态
[2]创建二级子进程处理僵尸
1.父进程创建爱你子进程等待回收子进程
2.子进程创建二级子进程然后退出
3.二级子进程成为孤儿,和原来的父进程一同执行事件
[3]通过信号处理子进程退出
原理:子进程退出时会发送信号给父进程,如果父进程忽略了子进程的信号,
系统就会自动处理子进程退出
方法:
使用signal模块在父进程创建前写下如下语句
import signal
signal.signal(signal.SIGCHLD,signal.SIG_ING)
优点:*非阻塞不会影响父进程运行
*使用该方法父进程可以脱离所有子进程退出
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid ==0: 7 print("Child PID:",os.getpid()) 8 os._exit(0) 9 else: 10 print("Parent process,...") 11 while True: 12 pass
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid <0: 7 print("Error") 8 elif pid == 0: 9 sleep(3) 10 print("Child %d process exit"%os.getpid()) 11 os._exit(2) #2*256 = status: 512 12 13 else: 14 pid,status = os.wait() 15 print("pid:",pid) 16 #print("status:",status) 17 print("status:",os.WEXITSTATUS(status)) 18 while True: 19 pass
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 print("******************") 5 a= 1 6 7 8 pid = os.fork() 9 10 if pid < 0: 11 print("Creat process failed") 12 elif pid == 0: 13 print("Child process") 14 print("a=%d"%a) 15 a = 10000 16 else: 17 sleep(1) 18 print("Parent proess") 19 print("a:",a) 20 21 print("all a=%d"%a)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import os 2 from time import sleep 3 4 pid = os.fork() 5 6 if pid <0: 7 print("Error") 8 elif pid == 0: 9 sleep(3) 10 print("Child %d process exit"%os.getpid()) 11 os._exit(2) #2*256 = status: 512 12 else: 13 #非阻塞等待 14 while True: 15 p,status = os.waitpid(-1,os.WNOHANG) 16 if p !=0: 17 break 18 sleep(2) 19 sleep(1) 20 print("做了其他事情") 21 while True: 22 print("完成父进程的其他事情") 23 sleep(2)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #创建二级子进程处理僵尸 2 import os 3 from time import sleep 4 5 def f1(): 6 sleep(3) 7 print("元宵...") 8 9 def f2(): 10 sleep(4) 11 print("处理南北甜咸之争...") 12 13 pid = os.fork() 14 15 if pid <0: 16 print("Error") 17 elif pid ==0: 18 p = os.fork()#创建二级子进程 19 if p ==0: 20 f2()#二级子进程做另一件事 21 else: 22 os._exit(0) 23 else: 24 os.wait()#等待子进程退出 25 f1()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 import signal 2 import os 3 #处理子进程退出 4 signal.signal(signal.SIGCHLD,signal.SIG_IGN) 5 pid = os.fork() 6 if pid < 0: 7 print("Error") 8 elif pid ==0: 9 print("Child PID:",os.getpid()) 10 else: 11 pass 12 while True: 13 pass
群聊聊天室
功能: 类似qq微信群聊
1. 进群需要输入姓名,姓名不能重复
2. 进入聊天室会向其他人发送通知
xxx 进入了聊天室
3. 某人发消息群里其他人能够收到
xxx说:xxxxxxxxxxxx
4. 某人退出聊天室也会向其他人发通知
xxx 退出了聊天室
5. 管理员可以发送管理员消息,此时群里所有人都能收到
管理员说:xxxxxxxx
(服务器可以向所有的用户发送)
1. 确定技术模型
-使用字典保存用户信息{name:ip}
- 消息发送:客户端--》服务端--》其他客户端
- 套接字:udp套接字
* 存储用户:字典或者列表
- 消息收发随意:使用两个(多)进程分别处理消息收发
2. 注意事项
* 设计封装方法 将每个功能封装为函数
* 写一个模块测试一个函数 实现一个功能,测试一个功能
* 注释的编写添加
* 流程 搭建网络连接,逐个功能实现
具体实现功能
1. 搭建网络连接
服务端 : 创建UDP套接字,绑定地址,创建多进程
客户端 : 创建UDP套接字,创建多0进程
2. 用户登录
服务端 : * 接收姓名
* 判断姓名是否存在
* 根据判断结果回复客户端
* 不允许登录则功能结束
* 允许登录将用户则加入数据结构
* 将用户登录提示发送给其他人
客户端 : * 输入姓名
* 将姓名发送给服务器
* 接收服务器反馈 ,如果不允许登录则重新输入,允许则进入聊天
* 创建新的进程,用于消息收发
3. 聊天
服务端: * 接收消息,判断请求类型
* 将消息转发给其用户
客户端: * 循环发送消息
* 循环接收消息
4. 用户退出
服务端 : * 收到q表示客户端退出
* 将用户从user移除
* 告知其他人xxx退出,给该用户发送退 出指令
客户端 : * 输入q表示退出
* 将退出信息发送给服务器
* 发送进程退出
* 接收进程接收到服务器指令退出
5. 管理员消息
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #coding=utf-8 2 ''' 3 Chatroom 4 env:python3.5 5 exc:socket and fork 6 ''' 7 from socket import * 8 import os,sys 9 10 #用于存储用户{name:addr} 11 user ={} 12 13 #处理登录 14 def do_login(s,name,addr): 15 #判断姓名是否存在 16 if name in user: 17 s.sendto("该用户已存在".encode(),addr) 18 return 19 s.sendto(b'OK',addr) 20 21 #先通知其他人 22 msg = "欢迎%s 进入聊天室"%name 23 for i in user: 24 s.sendto(msg.encode(),user[i])#user[i]发送,通过键取值 25 #将用户插入user 26 user[name] = addr 27 28 29 def do_chat(s,name,text): 30 msg ="%s : %s"%(name,text)#格式 31 #循环发送给所有人,除了自己 32 for i in user: 33 if i != name: 34 s.sendto(msg.encode(),user[i])#接受UDP消息,user[i]发送,通过键取值 35 36 37 38 def do_requests(s): 39 while True: 40 data,addr = s.recvfrom(1024)#接收UDP消息 41 #解析请求 42 msgList = data.decode().split(' ')#*spilt('.')方法将指定的分隔符进行拆分,拆分将会组成一个字符串的数组并返回 43 #区分请求类型 44 if msgList[0]=='L': 45 do_login(s,msgList[1],addr) 46 elif msgList[0] =='C': 47 #重新组织消息内容 48 text = ' '.join(msgList[2:]) 49 do_chat(s,msgList[1],text) 50 elif msgList[0] == 'Q': 51 do_quit(s,user,msgList[1]) 52 53 def do_quit(s,user,name): 54 msg = "\n%s 退出了聊天室"%name 55 for i in user: 56 if i == name: 57 s.sendto(b'quit',user[i]) 58 else: 59 s.sendto(msg.encode(),user[i]) 60 #删除该用户 61 del user[name] 62 63 #创建网络连接 64 def main(): 65 ADDR = ('0.0.0.0',8888) 66 #创建套接字 67 s = socket(AF_INET,SOCK_DGRAM)#创建数据报套接字 68 s.bind(ADDR)#绑定地址 69 70 #处理各种客户端请求 71 do_requests(s) 72 73 if __name__=="__main__": 74 main()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 from socket import * 2 import os,sys 3 4 #服务区的地址 5 ADDR = ('172.40.71.149',8888) 6 7 #发送消息 8 def send_msg(s,name):#发消息 9 #输入q表示退出聊天室 10 while True: 11 text = input("发言:") 12 if text =="q": 13 msg = 'Q ' + name 14 s.sendto(msg.encode(),ADDR) 15 sys.exit("退出聊天室") 16 msg = "C %s %s"%(name,text)#C 17 s.sendto(msg.encode(),ADDR) 18 19 20 def recv_msg(s):#收消息 21 while True: 22 data,addr = s.recvfrom(2048) 23 #服务器发来quit表示要退出 24 if data.decode()=="quit": 25 sys.exit(0) 26 print(data.decode(),"\n发言:",end="") 27 28 #创建网络连接 29 def main(): 30 s = socket(AF_INET,SOCK_DGRAM) 31 while True: 32 name = input("请输入姓名:") 33 msg = "L "+name 34 s.sendto(msg.encode(),ADDR)#发送请求给服务端 35 #等待回应 36 data,addr = s.recvfrom(1024)#消息收发 37 if data.decode() =="OK": 38 print("您已经进入聊天室") 39 break 40 else: 41 print(data.decode())#打印不允许的原因 42 43 #创建新的进程,用以消息收发的随意性 44 pid = os.fork()#创建新的进程 45 if pid < 0: 46 sys.exit("error创建进程失败!!") 47 elif pid == 0: 48 send_msg(s,name)#发送消息 49 else: 50 recv_msg(s)#接受消息 51 52 53 if __name__=="__main__": 54 main()
作业: 1. 完成退出功能,解决格式问题
聊天过程中输入quit表示退出
2. 整理fork创建进程内容
3. 用fork创建父子进程,同时复制一个文件,各复制一半到一个新的文件中