通常HTTP都是TCP的短连接,但本案例中传输的是视频流,短连接无法实现,可以使用服务器数据推送的方式。也就是在一次HTTP请求当中,一直使用一个TCP连接持续向客户端发送数据。

说明本地数据local的特点

例子一:

>>> import threading
>>> L = threading.local()        #创建一个本地数据
>>>
>>> L.x =1                           #创建本地对象L的一个属性x
>>> L.x                              #在主线程中可以访问这个属性
1
>>> def f():print(L.x)>>> f()                             #在主线程中可以访问这个属性
1
>>> threading.Thread(target=f).start()  #新创建一个线程并启动,通过异常可以看出,找不到local这个对象L没有属性x
Exception in thread Thread-15:
Traceback (most recent call last):File "C:\Python27\lib\threading.py", line 801, in __bootstrap_innerself.run()File "C:\Python27\lib\threading.py", line 754, in runself.__target(*self.__args, **self.__kwargs)File "<pyshell#46>", line 3, in f
AttributeError: 'thread._local' object has no attribute 'x'

例子二:

>>> import threading
>>> L = threading.local()
>>>
>>> L.x =1
>>> L.x
1
>>> def f():print(L.x)
>>> f()
1
>>> def g():L.x =5print (L.x)>>> threading.Thread(target = g).start()
5
>>>
L.x
1

说明:同一个本地数据的对象L,但在不同线程中,他的x属性的值不同。

>>> from select import select
>>> help(select)
Help on built-in function select in module select:select(...)select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)Wait until one or more file descriptors are ready for some kind of I/O.The first three arguments are sequences of file descriptors to be waited for:rlist -- wait until ready for readingwlist -- wait until ready for writingxlist -- wait for an ``exceptional condition''If only one kind of condition is required, pass [] for the other lists.A file descriptor is either a socket or file object, or a small integergotten from a fileno() method call on one of those.The optional 4th argument specifies a timeout in seconds; it may bea floating point number to specify fractions of seconds.  If it is absentor None, the call will never time out.The return value is a tuple of three lists corresponding to the first threearguments; each contains the subset of the corresponding file descriptorsthat are ready.*** IMPORTANT NOTICE ***On Windows and OpenVMS, only sockets are supported; on Unix, all file
descriptors can be used.

help(select)

>>> d = {0:kobe,1:bob}
>>> for x in d.itervalues():print x
kobe
bob

1、例子实现的代码:

import os, cv2, time, struct, threading
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import TCPServer, ThreadingTCPServer
from threading import Thread, RLock
from select import selectclass 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] = pwself.lock.release()return prdef unregister(self, pr):self.lock.acquire()self.pipes.pop(pr)self.lock.release()pr.close()pw.close()def capture(self):cap = self.capwhile 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))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 pipe in pipes:os.write(pipe, n)os.write(pipe, frame)self.lock.release()def run(self):for frame in self.capture():self.send(frame)class JpegRetriever(object):def __init__(self, streamer):self.streamer = streamerself.local = threading.local()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 datadef __enter__(self):if hasattr(self.local, 'pipe'):raise RuntimeError()self.local.pipe = streamer.register()return self.retrieve()def __exit__(self, *args):self.streamer.unregister(self.local.pipe)del self.local.pipereturn Trueclass Handler(BaseHTTPRequestHandler):retriever = None@staticmethoddef setJpegRetriever(retriever):Handler.retriever = retrieverdef do_GET(self):if self.retriever is None:raise RuntimeError('no retriver')if self.path != '/':returnself.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)httpd.serve_forever()

2、通过串口摄像头实现例子:

自己的台式机,不像笔记本自带摄像头,所以通过《python使用ctypes模块调用C语言动态库》中实现了,存储一个串口摄像头的照片。现在修改代码,并在代码中比较详细注释了。

# -*- coding: cp936 -*-
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
from time import sleep#数据源,不断从摄像头获取数据,并发送到管道中去。
class JpegStreamer(Thread):def __init__(self, camera):Thread.__init__(self)#self.cap = cv2.VideoCapture(camera)  # 打开摄像头文件 openCV是图像处理,视觉工具,可以打开图像或视频
self.lock = RLock()                    #RLock递归锁RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况#使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐self.pipes = {}"""注册接口,想使用源中的数据,需要调用 这个接口。将数据源写入管道,用户从管道读出数据"""def register(self):pr, pw = os.pipe()  # 创建一个管道self.lock.acquire()     #对列表进行互斥锁操作self.pipes[pr] = pw  # 维护管道的写端  把写端入在了pipes字典  {pr:pw}
        self.lock.release()return pr  # 把读端返回给用户接口def unregister(self, pr):self.lock.acquire()pw = self.pipes.pop(pr)   #将读的键删除值删除
        self.lock.release()pr.close()                 #关闭读写管道
        pw.close()"""图像采集函数""""""#装饰器(decorator)可以给函数动态加上功能# @property装饰器就是负责把一个方法变成属性调用的# 把一个getter方法变成属性,只需要加上@property就可以了,此时,本例中@property本身又创建了另一个装饰器@capture.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作@capture.setterdef capture(self):xxxx只不过这里只可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性因为调用capture()函数时,"()"没有加,就相当于使用他的属性,定义函数时就要使用@property装饰器"""@propertydef capture(self):'''cap = self.capwhile 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))  # 将获取的数据帧编码成jpg图片yield data.tostring()'''while True:frame = cv2.imread(r"D:\test123.jpg")  #opencv库函数 从地址文件获取照片数据,解码器以像素BGR顺序存储到矩阵中。通过照片的内容决定照片的格式,而不是通过后缀名实现的ret, data = cv2.imencode('.jpg', frame, (cv2.IMWRITE_JPEG_QUALITY, 40))  # 将获取的数据帧编码成jpg图片。 将解码后的数据矩阵编码成相应的格式  返回是一个bufferyield data.tostring()                  #返回一个生成器,变成generator的函数,在每次调用next()或for循环迭代的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。#tostring具体用法不太知道,这是openCV的依赖库numpy模块里的函数。应该是转换将16进制转为相应的字符串  0x16改为”16“,python写文件是写的字符串或者是反过来的?????sleep(0.2)                                #延时200ms"""将其中的一帧发送到所有已注册的管道中"""def send(self, frame):n = struct.pack('l', len(frame))    #struct模块用于python的值和c结构转换。官方文档https://docs.python.org/3/library/struct.html#format-characters#这里的意思是把frame的长度按long型转换 如3转换为b‘\x03x00\x00\x00’即0x00000003""">>> from struct import *>>> pack('hhl', 1, 2, 3)     #h  对应C_TYPE 为 shortb'\x00\x01\x00\x02\x00\x00\x00\x03'>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')(1, 2, 3)"""self.lock.acquire()if len(self.pipes):             #self.pipes字典里有值时,注册后自然就会有值 了#_, pipes, _ = select([], self.pipes.itervalues(), [], 1)  #linux里可以用select监测socket和文件的数据,但windows只能监测socket里的,不能监测文件的sleep(0.2)"""|  iteritems(...)|      D.iteritems() -> an iterator over the (key, value) items of D|  |  iterkeys(...)|      D.iterkeys() -> an iterator over the keys of D|  |  itervalues(...)|      D.itervalues() -> an iterator over the values of D"""for pipe in self.pipes.itervalues():    #返回字典值的生成器,pipes的键是读pipe 值 是写pipe#if(len(pipe)):os.write(pipe, n)                   #把长度写入pipeos.write(pipe, frame)               #把图像帧写入pipe
        self.lock.release()"""从capture()获取一帧数据,再发送到管道中去"""def run(self):for frame in self.capture:      #循环迭代获取的图片,并把图片数据发送到管道中
            self.send(frame)class JpegRetriever(object):def __init__(self, streamer):self.streamer = streamer        # 持有一个streamer对象(数据源对象)self.local = threading.local()  # 需要将每个线程使用的管道,实现线程本地数据,以后每注册一个管道都应该成为这个 self.local的一个属性# self.pipe = streamer.register()    #多线程就是多线程Retriever来处理,这里用相同的本地数据在不同线程不同值的特点.# 调用注册register()接口拿道管道def retrieve(self):while True:ns = os.read(self.local.pipe, 8)     #写入的时候明明是4个字节long的长度,读出时为什么读8个长度n = struct.unpack('l', ns)[0]       #unpack返回的是一个元组,即使只有一个元素如unpack('l','\x03\x00\x00\x00') 返回 (3,)返回这里加了[0]表示返回元组的第0项data = os.read(self.local.pipe, n)  #从管道中读出图片数据yield data                          #返回生成器,图片数据的生成器,向Handler返回"""retriever是单线程时没有—__enter__()。有注册也有注销,最好实现成上下文管理器。__enter__()""""""使用管道时,每次使用都需要注册,使用完后都需要注销,这样最好使用上下文管理实现上下文管理器 __enter__进入函数"""def __enter__(self):# self.pipe = streamer.register()if hasattr(self.local, 'pipe'):  # 为了避免重复进入enter,判断local下是否有pipe属性,没有时,才注册。hasattr()查看某一对象是否包含某一属性。raise RuntimeError()self.local.pipe = streamer.register()  # 注册成为一个本地的管道return self.retrieve()  # 得到每一帧的生成器"""def cleanup(self):self.streamer.unregister(self.pipe)""""""上下文管理器的退出函数,这里先不关心异常,用*args收集异常,压制所有异常"""def __exit__(self, *args):self.streamer.unregister(self.local.pipe)del self.local.pipe  # 使用完毕后删除本地属性return Trueclass Handler(BaseHTTPRequestHandler):         #用于实现HTTP服务器,但自身不能实现,需要通过继承子类,实现do_METHOD(METHOD为具体的方法如GET、POST等)retriever = None@staticmethod                       #装饰器 使setJpegRetriever成为静态的方法。参见C++的类里静态方法,只能类调用 ,不能实例调用def setJpegRetriever(retriever):Handler.retriever = retriever  #将传入的retriever对象赋值给类的retriever的对象def do_GET(self):if self.retriever is None:raise RuntimeError('no retriver')"""http响应头部的构造"""if self.path != '/':returnself.send_response(200)self.send_header("Content-type", 'multipart/x-mixed-replace;boundary=abcde')self.end_headers()"""#调用retrieve()每次拿到一帧数据并通过send_frame()变成hettp的响应发送出去for frame in self.retriever.retrieve():self.send_frame(frame)""""""使用上下文管理,得到数据帧生成器"""with self.retriever as frames:  # 增加上下文管理  打开retriever,循环迭代  得到图到每一帧的数据for frame in frames:self.send_frame(frame)   #调用send_frame 将图片的的数据发送到http中去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)                  #定义JpegStreamer线程类的对象streamer.start()                            #线程启动调用 run()方法
retriever = JpegRetriever(streamer)         #定义JpegRetriever类的实例,
    Handler.setJpegRetriever(retriever)print 'Start server...'# httpd = TCPServer(('', 9000), Handler)     #TCPServer和ThreadingTCPServer的差别是。在处理每一次的http请求的时候,ThreadingTCPServer会创建一个独立的线程来执行Handler中的do_GEThttpd = ThreadingTCPServer(('', 9000), Handler)httpd.serve_forever()                       #启动服务

 只有一个 JpegRetriever对象,也就是只有一个管道,在多链接时,也就是多线程时是不行的。每个客户端(每个线程应该有一个独立的管道)
    使用线程本地数据,可以解决这个问题,一个JpegRetriever对象,创建本地数据管道,这样每个线程的数据都是独立的。

下面是实验中的结果图:

3、串口摄像头实现视频播放

上面只是使用现有图片做为测试,下面使用摄像头,使用了ctypes模块调用串口摄像头C语言驱动,再进行测试

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
import ctypes
from time import sleepclass JpegStreamer(Thread):def __init__(self, camera):Thread.__init__(self)#self.cap = cv2.VideoCapture(camera)
self.testdll = ctypes.CDLL(r"D:\project program\photo\VS_dll\photo_dll\photo_dll.dll")ret = self.testdll.ComCamInit()type_uchar_array_20k = ctypes.c_char * (20 * 1024)self.my_array = type_uchar_array_20k()self.uwLenPara = ctypes.c_ushort(0)self.testdll.BSP_CameraGetPhoto.restype = ctypes.c_ubyteself.lock = RLock()self.pipes = {}def register(self):pr, pw = os.pipe()self.lock.acquire()self.pipes[pr] = pwself.lock.release()return prdef unregister(self, pr):self.lock.acquire()self.pipes.pop(pr)self.lock.release()pr.close()pw.close()ret = self.testdll.BSP_RearCommClose()def capture(self):"""cap = self.capwhile 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))yield data.tostring()"""while True:ret = self.testdll.BSP_CameraGetPhoto(1, self.my_array, ctypes.byref(self.uwLenPara))#yield self.my_array.tostring()yield self.my_arraydef send(self, frame):n = struct.pack('l', len(frame))self.lock.acquire()if len(self.pipes):#_, pipes, _ = select([], self.pipes.itervalues(), [], 1)#for pipe in pipes:#sleep(0.1)for pipe in self.pipes.itervalues():os.write(pipe, n)os.write(pipe, frame)self.lock.release()def run(self):for frame in self.capture():self.send(frame)class JpegRetriever(object):def __init__(self, streamer):self.streamer = streamerself.local = threading.local()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 datadef __enter__(self):if hasattr(self.local, 'pipe'):raise RuntimeError()self.local.pipe = streamer.register()return self.retrieve()def __exit__(self, *args):self.streamer.unregister(self.local.pipe)del self.local.pipereturn Trueclass Handler(BaseHTTPRequestHandler):retriever = None@staticmethoddef setJpegRetriever(retriever):Handler.retriever = retrieverdef do_GET(self):if self.retriever is None:raise RuntimeError('no retriver')if self.path != '/':returnself.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)httpd.serve_forever()

实现结果,延时非常大,串口摄像头获取数据较慢。

已发现的问题就是windows的select的使用和linux的区别,虽然使用的python,但是底层还是调用系统底层的api实现的,并不是通过语言自身实现。所以不同平台,还是会有不同。

另外在已显示的页面,再刷新时会出现异常,这个异常号通过网上查找,是因为客户端断开连接时,出现的。也就是刷新时会出现。也许是正常现象,因为程序里没有对异常进行处理,直接抛给了系统。

转载于:https://www.cnblogs.com/smulngy/p/9010189.html

8-4 如何使用线程本地数据相关推荐

  1. 线程本地数据ThreadLocal

    layout: post title: "线程本地数据ThreadLocal" subtitle: " "每个线程都有自己的数据,互不干扰."&quo ...

  2. java多线程之线程本地数据ThreadLocal

    layout: post title: "java多线程之线程本地数据ThreadLocal" subtitle: " "每个线程都有自己的数据,互不干扰.&q ...

  3. 多样化实现Windows phone 7本地数据访问5——深入Rapid Repository

    上一篇多样化实现Windows Phone 7本地数据访问<4>--Rapid Repository  中初步的介绍Repid Repository作为Windows phone 7数据库 ...

  4. 【Android 异步操作】手写 Handler ( Message 消息 | ThreadLocal 线程本地变量 | Looper 中的消息队列 MessageQueue )

    文章目录 一.Message 消息 二.ThreadLocal 线程本地变量 三.Looper 中的消息队列 MessageQueue 一.Message 消息 模仿 Android 中的 Messa ...

  5. 线程本地存储器——Windows核心编程学习手札之二十一

    线程本地存储器 --Windows核心编程学习手札之二十一 C/C++运行期库使用线程本地存储器,运行期库是在多线程应用程序出现前设计的,因此运行期库里的大多数函数是用于单线程应用程序的.函数strt ...

  6. 详解线程本地变量ThreadLocal

    并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变 ...

  7. 取本地数据_深入理解Kafka服务端之Follower副本如何同步Leader副本的数据

    一.场景分析Kafka采用的是主写主读的方式,即客户端的读写请求都由分区的Leader副本处理,那么Follower副本要想保证和Leader副本数据一致,就需要不断地从Leader副本拉取消息来进行 ...

  8. react 线程_React式服务中的线程本地状态可用性

    react 线程 任何架构决策都需要权衡. 如果您决定采用React式,也没有什么不同,例如,一方面使用React式流实现几乎可以立即获得更好的资源利用率,但另一方面会使调试更加困难. 引入React ...

  9. Java中的线程本地存储

    开发人员中鲜为人知的功能之一是线程本地存储. 这个想法很简单,并且在需要数据的情况下很有用. 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值. 大多数主要的编程 ...

  10. iOS - LocalCache 本地数据缓存

    1.自定义方式本地数据缓存 1.1 自定义缓存 1 沙盒路径下的 Library/Caches 用来存放缓存文件,保存从网络下载的请求数据,后续仍然需要继续使用的文件,例如网络下载的离线数据,图片,视 ...

最新文章

  1. 如何用 Slack 和 Kubernetes 构建一个聊天机器人?| 附代码
  2. 创建一个自定义颜色IRgbColor
  3. [轉載]用PHP的ob_start();控制您的浏览器cache!
  4. ArcGIS实验教程——实验十四:空间数据库的建立
  5. 苹果AirPods 2新爆料:无线充电盒重量增加 支持快速充电
  6. Spring MVC之cookies跟session 数据绑定
  7. 每日算法系列【LeetCode 503】下一个更大元素 II
  8. 1.1HashMap
  9. 给博客添加搜狗云输入法
  10. java实训心得体会3篇_java实训心得体会3篇
  11. java web 页面布局框架_jsp框架(jsp实现页面框架布局)
  12. 深入探究:TIFF格式的影像如何转jpg (保持色彩不变)
  13. C++:wchar_t 和C++新增类型:char16_t char32_t
  14. 腾讯android一键root工具,腾讯一键Root工具
  15. 蓝的成长记——追逐DBA(11):回家后的安逸,晕晕乎乎醒了过来
  16. 使用Arduino开发板制作自动药物提醒器
  17. 论文编写工具使用(1)latex软件
  18. cos41度用计算机怎么算,用电监察计算(精品)
  19. Pom.xml文件教程详解
  20. 架构师速成4.4-我该学什么语言

热门文章

  1. 动画效果之时间轴对象构造器(即逐个执行动画)
  2. MySQL学习(一、概述和表的基本操作)
  3. 开局崩盘!IDEA 2020 无法启动的解决办法|赠送 IDEA 2020 新功能
  4. springboot获取Spring容器中的bean(ApplicationContextAware接口的应用)避免过多的或不用if..else,switch
  5. 适配器模式之观察者模式
  6. Spring 注解 @Controller,@Service,@Repository,@Component,重定向 与 服务端跳转
  7. 小D课堂 - 零基础入门SpringBoot2.X到实战_第8节 数据库操作之整合Mybaties和事务讲解_34、SpringBoot整合Mybatis实操和打印SQL语句...
  8. 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第4节 方法引用_2_方法引用_通过对象名引用成员方法...
  9. Django的Form另类实现SelectMultiple
  10. MDK 中 [WEAK] 的作用