long poll的原理是,客户端与服务器将建立一条长连接,也就是说,客户端会发出一个请求,而服务器,将阻塞请求,直到有数据需要传递,才会返回。
返回之后,客户端将关闭此连接,然后再次发出一个请求,建立一个新的连接,再次等待服务器推送数据.
服务器端
服务器端实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
from uuid import uuid4
import json
#服务器端保存的字符串
class Announce():
subject = "nima"
callbacks = []
def register(self, callback):
self.callbacks.append(callback)
#改变后,会推送给保存的注册的客户端
def changeSubject(self, data):
self.subject = data
self.notifyCallbacks()
def getJson(self):
return json.dumps({'content':self.subject})
def notifyCallbacks(self):
for c in self.callbacks:
self.callbackHelper(c)
self.callbacks = []
def callbackHelper(self, callback):
callback(self.getJson())
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class ChatHandler(tornado.web.RequestHandler):
def post(self):
content = self.get_argument('content')
self.application.announce.changeSubject(content)
# StatusHandler的处理是异步的
class StatusHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
self.application.announce.register(self.async_callback(self.on_message))
def on_message(self, data):
self.write(data)
# 必须要finish 否则服务器会一直阻塞
self.finish()
class Application(tornado.web.Application):
"""
"""
def __init__(self):
"""
"""
self.announce = Announce()
handlers = [
(r'/',MainHandler),
(r'/chat',ChatHandler),
(r'/status',StatusHandler),
]
settings = {
'template_path': 'templates',
'static_path': 'static',
'debug': True
}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
tornado.options.parse_command_line()
app = Application()
server = tornado.httpserver.HTTPServer(app)
server.listen(8000)
tornado.ioloop.IOLoop.instance().start()
客户端与服务器端的链接,会一直保存着,当发生改变时,服务器才会把数据推送给客户端. 这其实,就是设计模式中的观察者模式。
客户端
客户端js代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script type="text/javascript">
$(function(){
setTimeout(requestInventory, 100);
$("#mypp").click(function(){
$.post("//localhost:8000/chat",{
content: $("#message").val()
},type="json");
});
});
function requestInventory(){
$.getJSON("//localhost:8000/status",{},function(data, Status, xhr){
var content = data.content;
var txt = "<p>"+content+"</p>"
$("#chatContent").html(txt);
setTimeout(requestInventory, 0);
});
}
</script>
可以看到,在requestInventory()中,每次数据返回后,setTimeout(requestInventory, 0);将建立一条新的链接.
其实还有一种类似与long poll的技术,iframe流方式,这种方式在页面插入一个隐藏的iframe.利用其src属性,在服务器和客户端之间建立一条长连接。与long poll不同的是,iframe流的这条连接会一直存在,而不是像long poll在数据返回后,客户端关闭此连接,然后重新开启一条连接。
但是,comet中采用的长连接,也会大量的消耗服务器的带宽和资源。
websocket
websocket,是html5引入的一个特性,也是未来的趋势,web socket 是在浏览器和服务器之间进行全双工通信的网络技术,既然是全双工通信,那么服务器自然可以主动传送数据给服务器,而且通信协议的header也很小,相比与之前的long poll, web socket 能够更好的节省服务器资源和宽带并达到实时通信.
服务器端代码 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import tornado.web
import tornado.websocket
import tornado.httpserver
import tornado.ioloop
import tornado.options
import json
class Announce():
subject = "nima"
callbacks = []
def register(self, callback):
self.callbacks.append(callback)
def unregister(self, callback):
self.callbacks.remove(callback)
def getJson(self):
return json.dumps({'content':self.subject})
def changeSubject(self, data):
self.subject = data
self.notifyCallbacks()
def notifyCallbacks(self):
for c in self.callbacks:
self.callbackHelper(c)
def callbackHelper(self, callback):
callback(self.getJson())
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class ChatHandler(tornado.web.RequestHandler):
def post(self):
content = self.get_argument('content')
self.application.announce.changeSubject(content)
# 注意,这个类的父类,用来进行web socket的WebSocketHandler
class StatusHandler(tornado.websocket.WebSocketHandler):
def open(self):
self.application.announce.register(self.callback)
def on_close(self):
self.application.announce.unregister(self.callback)
def on_message(self, message):
pass
def callback(self, data):
self.write_message(data)
class Application(tornado.web.Application):
"""
"""
def __init__(self):
"""
"""
self.announce = Announce()
handlers = [
(r'/',MainHandler),
(r'/chat',ChatHandler),
(r'/status',StatusHandler),
]
settings = {
'template_path': 'templates',
'static_path': 'static',
'debug': True
}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
tornado.options.parse_command_line()
app = Application()
server = tornado.httpserver.HTTPServer(app)
server.listen(8000)
tornado.ioloop.IOLoop.instance().start()
客户端,也是js对websocket 的操作.
注意 var host ='ws://localhost:8000/status'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="text/javascript">
$(function(){
requestInventory();
$("#mypp").click(function(){
$.post("chat",{
content: $("#message").val()
},type="json");
});
});
function requestInventory(){
var host ='ws://localhost:8000/status'
var websocket = new WebSocket(host);
websocket.onopen = function (evt) {};
websocket.onmessage = function (evt){
var content = $.parseJSON(evt.data)['content'];
var txt = "<p>"+ content +"</p>"
$("#chatContent").html(txt);
};
websocket.onerror = function (evt) {};
}
</script>
html5就是未来的趋势,而且,chrome,firefox,opera和safari都支持,IE,从版本10开始也支持了。
关于知乎的服务器推送
来自前知乎工程师孙竟分享
Chrome下使用的是JSONP Long Polling(究竟起什么名字别计较),其他浏览器没去看。
所谓JSONP,就是生成一个script标签,把回调函数的名称作为HTTP请求的参数。服务器解析回调函数名,返回类似callback(data)的代码,使浏览器可以跨域执行异步请求。
至于知乎想跨域执行的原因,估计是想让专门的服务器来处理事件更新,并减少cookie的传输量吧。
而Long Polling是说服务器在接收到这个请求后,阻塞而不立刻返回。如果有新的事件,或者达到超时时间(知乎目前设为30秒),再响应这个请求。
客户端接收到响应后,再发起一个新的请求。
这比轮询节省了无谓的开销,但要在服务器端保持大量长连接(Tornado正好擅长这个)。
想效率更高的话可以使用streaming,也就是在使用HTTP长连接的同时,将Transfer-Encoding设为chunked。当服务器有新的事件时,就发送响应,但不关闭连接。
客户端接收到响应后,处理该响应,也不关闭连接。
服务器想再次发送响应时,直接使用现有的连接即可。
这比Long Polling节省了多次建立和关闭连接的过程,也不用担心重建连接时错过新事件(如果你没在服务器端保存的话)。但因为是单向的,就需要客户端每隔一段时间发送心跳包,以证明客户端还在线。
此外,这种方式明显不能用JSONP实现,因为script标签在全部加载完前是不会执行里面的代码的,因此一次HTTP请求,只有一次执行机会。
还想高效就用WebSocket吧。它仍然以HTTP作为传输层,在建立连接时,客户端发起WebSocket握手请求,其头部内容沿用HTTP协议的定义;服务器接到请求后,返回验证信息,并用101状态码提示切换到WebSocket协议。这样一个WebSocket连接就建立好了。后续发送数据时,就不需要带HTTP请求头了。
它是双向的,因此服务器和客户端都能随时发送数据,且发送完不需要断开连接。任意一方主动断开连接时,对方都会收到onclose事件(服务器端的API就自己实现吧);当然你也能直接用这个连接来发送心跳包,协议里还为此预留了2个操作码(不过暂时没有浏览器端的JavaScript API)。
并且,WebSocket也是能跨域的,所以从功能和性能上来说,它都足够优秀了。
缺点就是低端的浏览器不支持,例如IE 6~IE 9。当然,支持WebSocket的Web服务器也比较少(Tornado表示无压力)。
另外,WebSocket的协议版本很多,各种浏览器支持的版本不一样:
https://en.wikipedia.org/wiki/WebSocket#Browser_support 。例如iOS设备支持的hixie-76
就是已被摈弃的,所以实现时还得考虑向下兼容的问题。
类似的还能通过Flash 、Silverlight和Java applet等浏览器插件来建立socket连接。缺点是没装插件就用不了,例如iOS设备支持WebSocket,但不支持后三者。
本文网址: https://pylist.com/topic/44.html 转摘请注明来源