http://www.eefocus.com/spencer/blog/13-11/300493_f17a8.html
毕设进入了纠结阶段,所以需要一些Breakthrough。
现在卡在了视频的采集上,我原先的想法是,将摄像头采集到的数据压缩成mp4格式,然后将mp4的视频流发布到网络中,然后我找来mp4的格式解析文档读,结果一塌糊涂,格式还是比较复杂的,并且从摄像头采集到的数据的压缩的过程也比较麻烦,现在没有时间处理了。于是,我就想起了我的老朋友:mjpg-stream。
mjpg-stream是一个使用网络摄像头监控软件,最令我注意的是,mjpg-stream是开源的,并且代码十分短小,并且很容易编译成功,于是我从大硬盘中翻出了这个两年前把玩过得老家伙。说来也神奇,mjpg-stream在浏览器中的效果就是一张图片而不是视频流,我用wireshark抓包后分析,发现里面的response数据中
Content-type的值是
multipart,于是我觉得这个应该就是问题的关键。经过Google查找后,发现这个在HTTP中叫做长连接,长连接以前听说过没有具体了解过。于是继续学习。习得此文:
http://www.cnblogs.com/zhjxiao/archive/2010/09/07/1820563.html
说道HTTP长连接技术就不能不提服务器推送功能,最著名的就是twitter主页的推送功能,以前一直以为是Ajax搞得,后来才知道是长连接,当然也有别的方式,比如WebSocket等。
简单说说resopnse数据中Content-type的这个值的作用,具体的可以到上面的链接中查看。
长连接HTTP和普通的HTTP不同在于“服务器并不推送整个报文,而是每次发送部分数据”,HTTP可以保持连接,于是可以保证信息的即时传达。
在mjpg-stream中使用了长连接技术,首先浏览器请求
GET /?action=stream HTTP/1.0
之后mjpg-stream返回数据
HTTP/1.0 200 OK
Connection: close
Server: MJPG-Streamer/0.2
Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0
Pragma: no-cache
Expires: Mon, 3 Jan 2000 12:34:56 GMT
Content-Type: multipart/x-mixed-replace;boundary=boundarydonotcross
--boundarydonotcross
这里面的Content-Type就是关键地方了,multipart表示需要长连接,x-mixed-replace表示使用后来的数据替换之前的数据,boundary表示每个数据块的边界,正如其名。而boundarydonotcross就是边界符了,一个数据块以boundarydonotcross字符串为结尾。
这个response是只发送一次的。
继续看wireshark的结果:
Content-Type: image/jpeg
Content-Length: 24038
X-Timestamp: 23639.357295
服务器返回新的数据,这里的Content-Type表示这个数据块是jpeg图片,Content-Length,图片的大小,X-Timestamp貌似是一个mjpg-stream内建的标签,我也不清楚是什么,作用不大,我猜想应该是配合别的客户端使用的。之后就是图片的二进制数据了。
服务器一直不断的发送这个数据,然后浏览器就可已不断的接收数据并显示。
我编写了一个Python脚本,用来验证我的结论。代码如下
import socket
import os
def getResponse(requestString):
method_end = requestString.find("\r\n")+2
requestStrs = requestString[method_end::].split("\r\n")
request = {}
for i in range(0, len(requestStrs)):
requestSplit = requestStrs[i].split(": ")
if len(requestSplit) > 1:
request[requestSplit[0]] = requestSplit[1]
return request
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 8888))
sock.send("GET /?action=stream HTTP/1.0\r\n\r\n")
headrecv = sock.recv(1024)
head_request = getResponse(headrecv)
print head_request
if head_request.has_key("Content-Type") and head_request["Content-Type"].find("multipart") < 0:
print "badHead"
quit()
index = 0
while True:
imageHead = sock.recv(30*1024)
#
print imageHead
image_response = getResponse(imageHead)
if image_response.has_key("Content-Length"):
imageLen = int(image_response["Content-Length"])
print imageLen
image = ""
if len(imageHead) > 100:
#
we had recv the image
image = imageHead[78::]
#
print image
print "have recv"
else:
image = sock.recv(30*1024)
#
print image
print "not recv"
bound = image.find("boundarydonotcross")
#
image = image[:bound:]
print image[-100::]
imageFile = open("%03dcam.jpg"%index, "wb")
imageFile.write(image)
imageFile.close()
index += 1
在操作过程中我发现,有时候sock.recv得到的response头数据带着图片数据,有时没有。这就是代码中的have recv和not recv的含义。我不太清楚原因,但程序可以使用,并在同一目录下产生很多图片,就是每一帧了。
转引用的一篇文章
服务器推送(Server Push)
推送技术的基础思想是将浏览器主动查询信息改为服务器主动发送信息。服务器发送一批数据,浏览器显示这些数据,同时保证与服务器的连接。当服务器需要再次发送一批数据时,浏览器显示数据并保持连接。以后,服务器仍然可以发送批量数据,浏览器继续显示数据,依次类推。
客户端拉曳(Client Pull)
在客户端拖曳技术中,服务器发送一批数据,在HTTP响应或文档头标记中插入指令,让浏览器“在5秒内再次装入这些数据”或“10秒内前往某URL装入数据”。当指定的时间达到时,客户端就按照服务器的指示去做,或者刷新当前数据,或者调入新的数据。
其实push 和 pull 这两种技术手段非常不同,但目的几乎一致,都是为了给最终用户方便的提供最新信息。
在服务器推送技术中,HTTP 连接一直保持着,直到服务器知道自己已结束发送数据并发送一个结束信号,或者客户端中断连接。而在客户端拖曳技术中,并不保持HTTP连接,相反,客户端被告知合时建立新连接,以及建立连接是获取什么数据。
在服务器推送中,奇妙之处在于“multipart/mixed”格式的MIME,它能够使一个报文(或HTTP响应)包含许多数据项、在客户端拖曳中,奇妙之处在于HTTP响应头标(或等效的HTML元素),它能告知客户端在指定的延时时间后执行何种动作。
服务器推送通常效率要比客户端拖曳效率高,因为它不必为后续数据建立新的连接。由于始终保持连接,即使没有数据传输时也是这样,因此服务器必须愿意分配这些TCP/IP端口,对于TCP/IP端口数有限的服务器这将是一个严重的问题。
客户端拖曳效率低,因为这必须每次为传送数据建立新的连接。但是它不必始终保持连接。
在实际情况中,建立HTTP连接通常需要花费相当多的时间,多达一秒甚至更多。因此从性能上考虑,服务器推送对于最终用户更有吸引力,特别是对于需要经常更新信息的情况下。
服务器推送相对客户端拖曳的另一点优势是,服务器推送相对比较容易控制。例如,服务器每一次推送时都保持一个连接,但它又随时可以关闭其中的任何连接,而不需要在服务器上设置特殊的算法。而客户端拖曳在同样的情况下要麻烦许多,它每次要与服务器建立连接,服务器为了处理将客户端拖曳请求与特定的最终用户匹配等情况,需要使用相当麻烦的算法。
如果实现服务器推送的CGI程序是使用Shell脚本语言编写的,有时会存在一些问题。例如,客户端最终用户中断连接,Shell程序通常不能注意到,这将使资源毫无用处的浪费掉,解决这一问题的办法是用Perl或者C来编写这类CGI程序,以使用户中断连接时能够结束运行。
如上所述,在服务器推送中,多个响应中连接始终保持,使服务器可在任何时间发送更多的数据。一个明显的好处是服务器完全能够控制更新数据的时间和频率。另外,这种方法效率高,因为始终保持连接。缺点是保持连接状态会浪费服务器端的资源。服务器推送还比较容易中断。
接下来就大概说说服务器推送技术
服务器在响应请求时,HTTP使用MIME报文格式来封装数据。通常一个HTTP响应只能包含一个数据块。但MIME有一种机制可用一个报文(或HTTP响应)表示将多个数据块,这种机制就是成为“multipart/mixed”的标准MIME类型。multipart/mixed报文大体格式如下:
Content-type:multipart/mixed;boundary=ThisRandomString
--ThisRandomString
Content-type:text/plain
第一个对象的数据。
--ThisRandomString
Content-type:text/plain
第二个对象的数据。
--ThisRandomString--
上述报文包括两上数据块,二者的类型都是“text/plain”。最后一个“ThisRandomString”后的两条短线(--)表示报文结束,后面没有数据。
对于服务器推送,使用一个“multipart/mixed”类型的变种--multipart/x-mixed-replace。这里,“x-”表示属于实验类型。“replace”表示每一个新数据块都会代替前一个数据块。也就是说,新数据不是附加到旧数据之后,而是替代它。
下面是实际使用的“multipart/x-mixed-replace”类型:
Content-type:multipart/x-mixed-replace;boundary=ThisRandomString
--ThisRandomString
Content-type:text/plain
第一个对象的数据
--ThisRandomString
Content-type:text/plain
第二个(最后一个)对象的数据。
--ThisRandomString--
使用这一技术的关键是,服务器并不是推送整个“multipart/x-mixed-replace”报文,而是每次发送后数据块。
HTTP连接始终保持,因而服务器可以按自己需要的速度和频率推送新数据,两个数据块之间浏览器仅需在当前窗口等候,用户甚至可以到其他窗口做别的事情,当服务器需要发送新数据时,它只是源(ABC输入法没那个字*&^$#)传输管道发送数据块,客户端相应的窗口进行自我更新。
在服务器推送技术中,“multipart/x-mixed-replace”类型的报文由唯一的边界线组成,这些边界线分割每个数据块。每个数据块都有自己的头标,因而能够指定对象相关的内容类型和其他信息。由于“multipart/x-mixed-replace”的特性是每一新数据块取代前一数据对象,因而浏览器中总是显示最新的数据对象。
“multipart/x-mixed-replace”报文没有结尾。也就是说,服务器可以永远保持连接,并发送所需的数据。如果用户不再在浏览器窗口中显示数据流,或者浏览器到服务器间的连接中间(例如用户按“STOP”按钮),服务器的推送才会中断。这是人们使用服务器推送的典型方式。
当浏览器发现“Content-type”头标或到达头标结束处时,浏览器窗口中的前一个文档被清除,并开始显示下一个文档。发现下一个报文边界时,就认为当前数据块(文档)已经结束。
总之,服务器推送的数据由一组头标(通常包括“Content-type”)、数据本身和分割符(报文边界)三部分组成。浏览器看到分割符时,它保持状态不变,直到下一个数据块到达。
将以上概念进行用编程方法实现,就可以得到实际的服务器推送程序。例如,下面的Unix shell程序将使浏览器每5秒显示一次服务器上的进程列表:
#!/bin/sh
echo "HTTP/1.1 200"
echo "Content-type: multipart/x-mixed-replace;boundary=--ThisRandomString--"
echo ""
echo "--ThisRandomString--"
while true
do
echo "Content-type: text/html"
echo ""
echo "h2Processes on this machine updated every 5 seconds/h2"
echo "time:"
date
echo "p"
echo "plaintext"
ps -el
echo "--ThisRandomString--"
sleep 5
done
注意到,边界设置在sleep语句之前发送,这能够确保浏览器清除其缓冲区,并显示所接收到的最新数据。
NCSA HTTPD用户在内容类型中不能使用空格,包括边界参数。NCSA HTTPD只能将不带空格字符的字符串作为内容类型。如果在内容类型行中存在空格(冒号后面的空格除外),空格后的任何文本都会被删除。
下面的示例是正确的:
Content-type: multipart/x-mixed-replace;boundary=ThisRandomString
而下例则不能正常工作,因为它在中间有空格:
Content-type: multipart/x-mixed-replace; boundary=ThisRandomString
服务器推送的另一个优点是它可以针对单个内联图象进行。包括图象的文档可以由服务器定时或定周期进行更新。而实现这一点非常简单:只需使IMG元素的SRC属性指向推送一系列图象的URL即可。
如果服务器推送用于单个内联图象,文档中的图象就会一次次被新推送来的图象所代替,而文档本身不需变化(假设文档没有进行服务器推送)。这样,WEB页面中有限的动画就可以为静态画面所代替。
客户端拖曳
客户端拖曳的一个简单用法是使文档按固定周期自动重载。例如,考虑下面的HTML文档:
<
META
HTTP-EQUIV
=
"Refresh"
CONTENT
=
1
>
<
TITLE
>Document ONETITLE
>
<
H1
>This is Document ONE!H1
>
Here's some text.<
P
>
如果将它载入支持动态文档的浏览器(Netscape 1.1以上,Internet Explorer和Mosaic也支持客户端拖曳),它将每隔一秒将自己重载一次。
由于META元素实际是在HTML文档中模拟HTTP响应头标,所以它能够告知浏览器将自身信息当作HTTP响应使用。上例中的META标记相当于:
Refresh:1
这样,实际上就是HTTP头标告知浏览器每一秒更新一次文档。如果需要延时是12秒,那么就是这样的指令:
<
META
HTTP-RQUIV
=
"Refresh"
CONTENT
=
12
>
那么它等效于:
Refresh:12
关于客户端的拖曳我也懒的继续写下去,关于怎么使客户端自动申请其他URL的数据话,请使用如下:
<
META
HTTP-EQUIV
=
"Refresh"
CONTENT
=
"12;URL=http://icools.yeah.net/"
>
注意的是,此处的URL不能使用相对路径,必须全部指定。
其中时间间隔可以设置为0,这样浏览器在当前文档显示完毕后,以最快的速度载入新的数据!