基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
# 服务端.py
import socketserver # 导入模块
# 先定义一个类,这个类专门解决通信循环的,必须继承一个类BaseRequestHandler
class MyRequestHandler(socketserver.BaseRequestHandler):
def handle(self): # 必须要写一个函数,叫handle的方法,里面放通信循环
while True:
try:
data = self.request.recv(1024) # 最大接收的字节数
if len(data) == 0:
break
print(data)
self.request.send(data.upper())
except Exception:
break
self.request.close()
# 链接循环,套接字属于IO密集型,对于IO密集型应该使用多线程
# 多线程ThreadingTCPServer里面放:监听的服务端ip和端口、定义的类、bind_and_activate=True
# bind_and_activate=True等同于bind()并且listen()默认属性为True无需添加。
server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyRequestHandler,bind_and_activate=True)
# 一直对外提供服务
server.serve_forever()
# serve_forever()每建成一个链接,都调用MyRequestHandler这个类,创建一个对象,
# 把它建成的链接对象赋值给self下面的request进行通信
# 整体逻辑:相当于客户端每发来一个请求,服务端就启一个线程,每启一个线程就去运行对象下面的
# handle方法,把跟这个客户端所有相关的套接字信息全都放到self对象里面去并触发这个对象下面
# 的handle方法用这个方法跟客户端进行通信
# 客户端.py (可实现多个客户端同时通信)
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议
# 2、打电话
phone.connect(('127.0.0.1',8080))
# 3、发\收数据
while True:
msg = input('>>>: ').strip()
if len(msg) == 0:
continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 4、关闭
phone.close()
# 服务端.py
import socketserver
class MyRequesthanlder(socketserver.BaseRequestHandler):
# 必须要写一个函数,叫handle的方法,里面放通信循环
def handle(self):
# 收到消息,进行解压。第一个值是客户端发来的数据。第二个值是套接字对象,用它来回消息
data,server = self.request
# 将收到的消息转大写回复,所有套接字信息都封装进self里了
server.sendto(data.upper(),self.client_address)
server = socketserver.ThreadingUDPServer(('127.0.0.1',9999),MyRequesthanlder)
server.serve_forever()
# 整体逻辑同上面TCP协议一样
# 客户端.py
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
while True:
msg = input(">>>>:").strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',9999))
res,server_addr = client.recvfrom(1024)
print(res.decode('utf-8'))
from multiprocessing import Process
import time
import os
def task(name,age):
# 如果在任务中取出进程id号,需要使用os模块
print('当前进程(子进程)id号是:',os.getpid()) #当前进程id号
print('当前进程父进程的id号是:',os.getppid()) # 当前进程父进程的id号
time.sleep(10)
print(name)
print(age)
if __name__ == '__main__':
p=Process(target=task,kwargs={'age':19,'name':'lqz'})
p.start()
print('p这个进程的id号是:',p.pid) # 进程id号
print('当前进程id(主进程)号是:', os.getpid()) # 当前进程id号
print('当前进程父进程(pycharm)的id号是:', os.getppid()) # 当前进程父进程的id号
time.sleep(10)
'''
如果有p对象,就是用p.pid获取进程id号
如果没有p对象,就是用os模块的
os.getpid() #当前进程id号
os.getppid() #父进程id号
僵尸进程的危害:占用PCB资源(主要是PID资源),系统将会因为产生大量的僵尸进程,而没有可用的进程号来产生新进程,导致系统资源不足
因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
"""
僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
"""
# 测试:
# 手动制造一个产生僵尸进程的程序test.py内容如下
# coding:utf-8
from multiprocessing import Process
import time,os
def run():
print('子',os.getpid())
if __name__ == '__main__':
p=Process(target=run)
p.start()
print('主',os.getpid())
time.sleep(1000)
核心思想:父进程的知道子进程的结束,并且明确的回复操作系统,此时操作系统才能回收资源,避免僵尸进程的产生
wait
调用来读取子进程退出状态。比如通过 multiprocessing.Process
产出的进程可以通过子进程的 join()
方法来 wait,也可以在父进程中处理 SIGCHLD
信号,在处理程序中调用 wait
系统调用或者直接设置为 SIG_IGN
来清除僵尸进程。#### 第三种办法就说把进程变成孤儿进程,这样进程就会自动交由 init 进程(pid 为 1 的进程)来处理,一般 init 进程都包含对僵尸进程进行处理的逻辑。(这里有个坑,那就是 docker 容器中一般 pid 为 1 进程就是主程序的进程,而不是我们预期的 init 进程。如果要使用这种方法的话,需要注意一下类似的场景)
创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程。演示如下:
import os,sys
import time
pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
# 执行pid=os.fork()则会生成一个子进程
# 返回值pid有两种值:
# 如果返回的pid值为0,表示在子进程当中
# 如果返回的pid值>0,表示在父进程当中
if pid > 0:
print 'father died..'
sys.exit(0)
# 保证主线程退出完毕
time.sleep(1)
print 'im child', os.getpid(), os.getppid()
# 执行文件,输出结果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
# 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,
# 存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。
守护一个服务,长期驻留在内存中提供服务,不能够受制于终端;
如何让一个进程成为守护进程?
主进程创建守护进程
from multiprocessing import Process
import os,time
def task():
print("进程%s开启" %os.getpid()) # 2.接着会打印进程开启,可以看到打印结果
time.sleep(10)
print("进程%s结束" %os.getpid()) # 看不到,在time.sleep(10)的时候主进程代码结束
if __name__ == '__main__':
p = Process(target=task)
p.daemon = True # 调用守护进程
p.start()
print("主:%s" %os.getpid()) # 1.先打印"主",主进程睡三秒的时间足够守护进程启动起来
time.sleep(3)
# 主进程代码运行完毕,守护进程就会结束
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
print("main-------")
# 打印可能出现的三种结果:
"""
main-------
456
end456
"""
"""
main-------
123
456
end456
"""
"""
123
main-------
456
end456
"""
# 并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process
import os,time
def work():
print("进程%s开启" %os.getpid())
time.sleep(2)
print("进程%s结束" %os.getpid())
if __name__ == '__main__':
for i in range(3):
p=Process(target=work)
p.start()
# 加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process,Lock
import os,time
def work(lock):
lock.acquire() # 锁定
print("进程%s开启" %os.getpid())
time.sleep(2) # 模拟网络延迟
print("进程%s结束" %os.getpid())
lock.release() # 释放
if __name__ == '__main__':
lock=Lock() # 创建锁
for i in range(3):
p=Process(target=work,args=(lock,))
p.start()
文件当数据库,模拟抢票
# 文件db的内容为:{"count":1}
# 注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import json
import os
import time
def check():
with open('db.json',mode='rt',encoding='utf-8') as f:
time.sleep(1)
dic = json.load(f)
print("%s查看到剩余票数为:%s" %(os.getpid(),dic['count']))
def get():
with open('db.json',mode='rt',encoding='utf-8') as f:
time.sleep(1) # 模拟读数据的网络延迟
dic = json.load(f)
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(3) # 模拟写数据的网络延迟
with open('db.json',mode='wt',encoding='utf-8') as f:
json.dump(dic,f)
print('%s购票成功' %os.getpid())
else:
print("购票失败")
def func(mutex):
check()
mutex.acquire()
get()
mutex.release()
# with mutex:
# get()
if __name__ == '__main__':
mutex = Lock() # 在父进程创建锁
for i in range(10): #模拟并发10个客户端抢票
p = Process(target=func,args=(mutex,))
p.start()
# p.join()
print("主")
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
创建队列的类(底层就是以管道和锁定的方式实现):
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。blocked为True(默认值)如果
队列满了就锁住了并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会
抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked
为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果
blocked为False有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出
Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的
数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中
产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中
的队列不会导致get()方法返回错误。
q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认
情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可
以禁 止这种行为
'''
共享内存:
1.管道
tasklist | findstr xxx
ps aux | grep xxx
2.队列
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''
from multiprocessing import Queue
q = Queue(3) # 创建共享的进程队列,指定队列的长度为3,最多放三个值
q.put([1,2,3]) # 放入值到队列中
q.put({"a":1})
q.put("xxx")
# q.put(1000) # 超出值无法放入
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 超值取不到q.get()默认为 q.get(block=True,timeout=None)
# print(q.get(block=True,timeout=3)) # 取不到三秒抛出异常
print(q.get(block=False)) # 取不到值立马抛异常
生产者消费者模型:
该模型有两种角色,一种是生产者,另外一种是消费者
生产者负责产生数据,消费者负责取走数据进行处理
生产者与消费者通过队列通信
优点:解耦合,平衡了生产者的生产力与消费者的处理能力
import random
import time
from multiprocessing import Process, Queue
def producer(q, name, food):
for i in range(3):
res = "%s%s" % (food, i)
time.sleep(random.randint(1, 3))
q.put(res)
print("%s 生产了 %s" % (name, res))
def consumer(q, name):
while True:
res = q.get()
if res is None:
break
time.sleep(random.randint(1, 3))
print("%s 吃了 %s" % (name, res))
if __name__ == '__main__':
q = Queue() # 创建队列
p1 = Process(target=producer, args=(q,"厨师1", "包子"))
p2 = Process(target=producer, args=(q,"厨师2", "烧麦"))
p3 = Process(target=producer, args=(q,"厨师3", "馒头"))
c1 = Process(target=consumer, args=(q, "lxx"))
c2 = Process(target=consumer, args=(q, "hxx"))
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join()
p2.join()
p3.join()
q.put(None)
q.put(None)
print("主")
import random
import time
from multiprocessing import Process, JoinableQueue
def producer(q, name, food):
for i in range(3):
res = "%s%s" % (food, i)
time.sleep(random.randint(1, 3))
q.put(res)
print("%s 生产了 %s" % (name, res))
q.join()
def consumer(q, name):
while True:
res = q.get()
if res is None:
break
time.sleep(random.randint(1, 3))
print("%s 吃了 %s" % (name, res))
q.task_done()
if __name__ == '__main__':
q = JoinableQueue()
p1 = Process(target=producer, args=(q,"厨师1", "包子"))
p2 = Process(target=producer, args=(q,"厨师2", "烧麦"))
p3 = Process(target=producer, args=(q,"厨师3", "馒头"))
c1 = Process(target=consumer, args=(q, "lxx"))
c2 = Process(target=consumer, args=(q, "hxx"))
c1.daemon = True # 主进程结束顺便带走了守护进程
c2.daemon = True
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join()
p2.join()
p3.join() # p1、p2、p3都结束,代表队列一定被取空
print("主")
手机扫一扫
移动阅读更方便
你可能感兴趣的文章