7.4【多线程与多进程】使用线程本地数据

一个web视频监控服务器,服务器端采集摄像头数据,客户端使用浏览器通过http请求接收视频数据(tcp的短连接),服务器使用推送的方式(multipart/x-mixed-replace)一直使用一个tcp连接向客户端传递数据。这种方式将持续占用一个线程,导致单线程服务器无法处理多客户端请求
改写,在每个线程中处理一个客户端请求,支持多客户端访问

import os,cv2,time,struct,threading
from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler
from SocketServer import TCPServer,ThreadingTCPServer
from threading import Thread,RLock
from select import select

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()
		pr.close()
		pw.close()
	def capture(self): # 数据采集部分
		cap = self.cap
		while cap.isOpened():
			ret, frame = cap.read() 
			if ret:
				#ret, data = cv2.imencode('.jpg', frame)
				ret, data = cv2.imencode('.jpg', frame, (cv2.IMWRITE_JPEG_QUALITY, 40))# opencv的库,每次从摄像头读取1帧,并且编码成jpg图片返回
				yield data.tostring() # 返回一个生成器对象
	def send(self, frame): # 把1帧发送到所有注册的管道中去
		n = struct.pack('l', len(frame))
		self.lock.acquire()
		if len(self.pipes):
			_, pipes, _ = select([], self.pipes.itervalues(), [], 1)
			for pipe in pipes:
				os.write(pipe, n)
				os.write(pipe, frame)
		self.lock.release()
	def run(self):
		for frame in self.capture(): # 从capture中拿到1帧数据,发送所有管道
			self.send(frame)
				
class JpegRetriever(object):
	def __init__(self, streamer):
		self.streamer = streamer # 持有上面对象
		self.pipe = streamer.register() # 获取一个管道
	def retrieve(self): # 每次从管道中读取1帧数据,并返回
		while True:
			ns = os.read(self.pipe, 8)
			n = struct.unpack('l', ns)[0]
			data = os.read(self.pipe, n)
			yield data
	def cleanup(self):
		self.streamer.unregister(self.pipe)

class Handler(BaseHTTTPRequestHandler):
	retriever = None
	@staticmethod
	def setJpegRetriever(retriever):
		Handler.retriever = retriever
	def do_GET(self):
		if self.retriever is None:
			raise RuntimeError('no retriver')
		if self.path != '/':
			return
		self.send_response(200)
		self.send_header('Content-type', 'multipart/x-mixed-replace;boundary=abcde')
		self.end_headers()
		for frame in self.retriever.retrieve():
			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:%d\r\n\r\n' % len(frame))
		self.wfile.write(frame)

if __name__ == '__main__':
	streamer = JpegStreamer(0)
	streamer.start()

	retriever = JpegRetriever(streamer)
	Handler.setJpegRetriever(retriever)

	print 'Start server...'
	httpd = TCPServer(('', 9000), Handler)
	htttpd.serve_forever()

# 127.0.0.1:9000 	开多个窗口后,只有第一个有摄像头数据
修改1:ThreadingTCPServer替换TCPServer,在处理每一次http请求时,ThreadingTCPServer会创建一个独立的线程,来执行do_GET
修改2:Retriever对象只存在一个,并且以静态方法设置,若只有1个连接,没问题,但多个客户端连接时,不能只访问一个pipe进行数据通信,每个客户端应该有独立的pipe;
#
threading.local函数可以创建线程本地数据空间。其下属性对每个线程独立存在

import threading
l = threading.local()
l.x = 1
def f():
	print l.x
f() # 1
threading.Thread(traget=f).start() # 在子线程中运行f,抛出异常,找不到x

def f():
	l.x = 5
 threading.Thread(traget=f).start() # 启动一个子线程运行f
 l.x # 1 ;	主线程中访问l.x,说明每个线程中的x是独立的

#
import os,cv2,time,struct,threading
from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler
from SocketServer import TCPServer,ThreadingTCPServer
from threading import Thread,RLock
from select import select

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()
		pr.close()
		pw.close()
	def capture(self): # 数据采集部分
		cap = self.cap
		while cap.isOpened():
			ret, frame = cap.read() 
			if ret:
				#ret, data = cv2.imencode('.jpg', frame)
				ret, data = cv2.imencode('.jpg', frame, (cv2.IMWRITE_JPEG_QUALITY, 40))# opencv的库,每次从摄像头读取1帧,并且编码成jpg图片返回
				yield data.tostring() # 返回一个生成器对象
	def send(self, frame): # 把1帧发送到所有注册的管道中去
		n = struct.pack('l', len(frame))
		self.lock.acquire()
		if len(self.pipes):
			_, pipes, _ = select([], self.pipes.itervalues(), [], 1)
			for pipe in pipes:
				os.write(pipe, n)
				os.write(pipe, frame)
		self.lock.release()
	def run(self):
		for frame in self.capture(): # 从capture中拿到1帧数据,发送所有管道
			self.send(frame)
				
class JpegRetriever(object):
	def __init__(self, streamer):
		self.streamer = streamer
		#self.pipe = streamer.register() # 每个线程使用的pipe实现成线程本地数据
		self.local = threading.local() # 之后每注册一个pipe,都应该是local的属性
	def __enter__(self): # 每次使用pipe需要注册,使用完后需要注销;使用上下文管理,把这个类实现成上下文管理器,实现__enter__方法
		if hasattr(self.local, 'pipe'): # 为了避免重复进入enter,查看local对象是否包含pipe属性;
			raise RuntimeError()
		self.local.pipe = streamer.register() # 进行注册
		return self.retrieve()
	def retrieve(self): # 每次从管道中读取1帧数据,并返回
		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 cleanup(self):
	def __exit__(self, *args): # 不关注异常,*args收集
		self.streamer.unregister(self.local.pipe)
		del self.local.pipe # 使用完后,删除
		return True # 压制异常

class Handler(BaseHTTTPRequestHandler):
	retriever = None
	@staticmethod
	def setJpegRetriever(retriever):
		Handler.retriever = retriever
	def do_GET(self):
		if self.retriever is None:
			raise RuntimeError('no retriver')
		if self.path != '/':
			return
		self.send_response(200)
		self.send_header('Content-type', 'multipart/x-mixed-replace;boundary=abcde')
		self.end_headers()
		with self.retriever as frames: # 使用时,上下文管理
			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:%d\r\n\r\n' % len(frame))
		self.wfile.write(frame)

if __name__ == '__main__':
	streamer = JpegStreamer(0)
	streamer.start()

	retriever = JpegRetriever(streamer)
	Handler.setJpegRetriever(retriever)

	print 'Start server...'
	httpd = ThreadingTCPServer(('', 9000), Handler)
	htttpd.serve_forever()

# 127.0.0.1:9000 	开多个窗口后,都有摄像头数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值