#如何使用线程本地数据
#实际案例:实现一个web视频监控服务器,服务器端采集摄像头数据,客户端使用浏览器
#通过http请求接收数据,服务器使用推送方式(multipart/x-mixed-replace)
# 一直使用一个tcp连接向客户端传递数据,这种方式将持续占用一个线程,导致单线程服务器
# 无法处理多客户端请求
#改写程序,在每个线程中处理一个客户端请求,支持多客户端访问。
#解决方案:threading.local函数可以创建线程本地数据空间,其下属性对每个线程独立存在
"""
import threading
l=threading.local()
l.x=1 #x主线程本地的数据 其他地方访问不到
def f():
print l.x
f()
threading.Thread(target=f).statr()#启动子线程中会抛出异常找不到属性
"""
import os,cv2,time,struct,threading
from BaseHTPSever import HTTPSever,BaseHTTPRequestHandler
from SocketSever import TCPServer,ThreadingTCPServer
from threading import Thread,RLock
from select import select
#JpegStreamer负责采集数据 独立线程
class JpegStreamer(Thread):
def __init__(self,camera):
Thread.__init__(self)
self.cap =cv2.VideoCapture(camera)
self.lock=RLock()
self.pipes={}
#数据输出采用了一个注册的接口 如果想获取数据就调用这个函数
def register(self):
pr,pw = os.pipe()#在内部 创建管道
self.lock.acquire()
self.pipes[pr] =pw#维护管道的写端 来了数据从写端把数据写入进去
self.lock.release()
return pr#并且把管道的读端返回给用户
def unregister(self,pr):
self.lock.acquire()
self.pipes.pop(pr)
self.lock.release()
#数据采集 返回一个生成器对象
def capture(self):
cap=self.cap
while cap.isOpend():
ret,frame=cap.read()#在其内部使用opencvde 库
if ret:
#ret,data=cv2.imencode('.jpg',frame)
ret, data = cv2.imencode('.jpg', frame,(cv2.IMWRITE_JPEG_QUALITY,40))#并且编码成jpg图片
yield data.tostring()
#把其中的一帧发送到所有已注册的的管道当中去
def send(self,frame):
n=struct.pack('l',len(frame))
self.lock.acquire()
if len(self.pipes):
_,pipes,_=select([],self.pipes.itervalues(),[],1)
for pip in pipes:
os.write(pipe,n)
self.lock.release()
def run(self):
for frame in self.capture():#从capture()拿到数据
self.send(frame)#发送给所有的管道
class JpegRetriever(object):
def __init__(self,streamer):
self.streamer=streamer
#把每一个线程使用的pipe实现成线程本地数据 创建local
self.local=threading.local()
# self.pipe=streamer.register()
def retrieve(self):
while True:
ns=os.read(self.local.pipe,8)
n=struct.unpack('l',ns)[0]
data=os.read(self.local.pipe,n)
yield data
#使用上下文管理器
def __enter__(self):
if hasattr(self.local,'pipe'):
raise RuntimeError()
self.local.pipe = streamer.register()
return self.retrieve()
#def cleanup(self):
def __exit__(self, *args):
self.streamer.unregister(self.local.pipe)
#使用完之后删除相关属性
del self.local.pipe
return True#压制所有异常
class Handler(BaseHTTPRequestHandler):
retriever =None#只有一个对象
@staticmethod
def setJpegRetriever(retriever):
Handler.retriever=retriever
def do_GET(self):
if self.retriever is None:
raise RuntimeError('no retriever')
if self.path!='/':
return
self.send_response(200)#关于jpG响应头部的构造
self.send_header('Cintent-type','multipart/x-mixed-replace;boundary =abcde')
self.end_headers()
#使用时候使用上下文管理
with self.retriever as frames:
#for frame in self.retriever.retrieve():
for frame in frames:
self.send_frame(frame)
def send_frame(self,frame):
self.wfile.write('--abcde\r\n')
self.wfile.write('Content.Type:image/jpeg\r\n')
self.wfile.write('Content.Length:image/jpeg\r\n' % len(frame))
self.wfile.write(frame)
if __name__=='__main__':
streamer =JpegStreamer(0)
streamer.start()
retriever =JpegRetriever(streamer)
Handler.setJpegRetriever(retriever)
print('Start sever....')
#httpd=TCPServer(('',9000),Handler)
httpd = ThreadingTCPServer(('', 9000), Handler)#会创建独立的线程
httpd.serve_forever()