關(guān)于WebSocket
WebSocket API是下一代客戶端-服務(wù)器的異步通信方法蔗喂。該通信取代了單個(gè)的TCP套接字谚赎,使用ws或wss協(xié)議,可用于任意的客戶端和服務(wù)器程序冗尤。WebSocket目前由W3C進(jìn)行標(biāo)準(zhǔn)化弃鸦。WebSocket已經(jīng)受到Firefox 4绞吁、Chrome 4、Opera 10.70以及Safari 5等瀏覽器的支持唬格。
WebSocket API最偉大之處在于服務(wù)器和客戶端可以在給定的時(shí)間范圍內(nèi)的任意時(shí)刻家破,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信西轩,因?yàn)锳jax技術(shù)需要客戶端發(fā)起請(qǐng)求员舵,而WebSocket服務(wù)器和客戶端可以彼此相互推送信息脑沿;XHR受到域的限制藕畔,而WebSocket允許跨域通信。
Ajax技術(shù)很聰明的一點(diǎn)是沒有設(shè)計(jì)要使用的方式庄拇。WebSocket為指定目標(biāo)創(chuàng)建注服,用于雙向推送消息
精簡(jiǎn)的說韭邓,websocket在任意時(shí)刻服務(wù)器和客戶端之間相互發(fā)送信息,而不是傳統(tǒng)客服端發(fā)送request 服務(wù)器來響應(yīng)
使用這個(gè)機(jī)制可以達(dá)到推送的效果
Tornado對(duì)于websocket的實(shí)現(xiàn)
官方文檔tornado.websocket.WebSocketHandler
使用時(shí)繼承這個(gè)類溶弟,比如:
class EchoWebSocket(websocket.WebSocketHandler):
#連接websocket服務(wù)器時(shí)進(jìn)行的event
def open(self):
print "WebSocket opened"
#收到信息的時(shí)候進(jìn)行的動(dòng)作
def on_message(self, message):
#write_message用于主動(dòng)寫信息女淑,這里將收到的信息原樣返回
self.write_message(u"You said: " + message)
#關(guān)系連接時(shí)的動(dòng)作
def on_close(self):
print "WebSocket closed"
#主動(dòng)調(diào)用close()函數(shù)可以關(guān)閉這個(gè)連接
關(guān)于js、android(java)端都與實(shí)現(xiàn)websocket的方法辜御,相關(guān)資料也容易找到
和上面的例子對(duì)應(yīng)的js是
var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
ws.send("Hello, world");
};
ws.onmessage = function (evt) {
alert(evt.data);
};
使用websocket聊天
這個(gè)例子來自tornado的github
#!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Simplified chat demo for websockets.
Authentication, error handling, etc are left as an exercise for the reader :)
"""
import logging
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import os.path
import uuid
from tornado.options import define, options
define("port", default=8888, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/chatsocket", ChatSocketHandler),
]
settings = dict(
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
)
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", messages=ChatSocketHandler.cache)
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
waiters = set()
cache = []
cache_size = 200
def get_compression_options(self):
# Non-None enables compression with default options.
return {}
def open(self):
ChatSocketHandler.waiters.add(self)
def on_close(self):
ChatSocketHandler.waiters.remove(self)
@classmethod
def update_cache(cls, chat):
cls.cache.append(chat)
if len(cls.cache) > cls.cache_size:
cls.cache = cls.cache[-cls.cache_size:]
@classmethod
def send_updates(cls, chat):
logging.info("sending message to %d waiters", len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.write_message(chat)
except:
logging.error("Error sending message", exc_info=True)
def on_message(self, message):
logging.info("got message %r", message)
parsed = tornado.escape.json_decode(message)
chat = {
"id": str(uuid.uuid4()),
"body": parsed["body"],
}
chat["html"] = tornado.escape.to_basestring(
self.render_string("message.html", message=chat))
ChatSocketHandler.update_cache(chat)
ChatSocketHandler.send_updates(chat)
def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
我們看ChatSocketHandler
這個(gè)類:
waiters = set()
cache = []
cache_size = 200
這三個(gè)類的成員變量分別維護(hù)了在線聊天者"waiters"
聊天內(nèi)容緩存cache
和最大緩沖值 200
重載了open ()和close()函數(shù)分別用于進(jìn)入聊天室和離開聊天室
def open(self):
ChatSocketHandler.waiters.add(self)
def on_close(self):
ChatSocketHandler.waiters.remove(self)
重載了on_message(self, message)函數(shù)用于處理消息進(jìn)來時(shí)的處理
構(gòu)造了聊天體后用兩個(gè)類函數(shù)進(jìn)行操作
update_cache將聊天信息進(jìn)行更新
send_updates對(duì)所有在線的人(waiters)進(jìn)行推送
parsed = tornado.escape.json_decode(message)
chat = {
"id": str(uuid.uuid4()),
"body": parsed["body"],
}
chat["html"] = tornado.escape.to_basestring(
self.render_string("message.html", message=chat))
ChatSocketHandler.update_cache(chat)
ChatSocketHandler.send_updates(chat)
這樣服務(wù)器端就基本完成了 鸭你,再看下客戶端
這里只放上js最基本的文件,有一定jquery基礎(chǔ)很容易看懂擒权,這里不做解釋
// Copyright 2009 FriendFeed
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
$("#messageform").live("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
$("#message").select();
updater.start();
});
function newMessage(form) {
var message = form.formToDict();
updater.socket.send(JSON.stringify(message));
form.find("input[type=text]").val("").select();
}
jQuery.fn.formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
var updater = {
socket: null,
start: function() {
var url = "ws://" + location.host + "/chatsocket";
updater.socket = new WebSocket(url);
updater.socket.onmessage = function(event) {
updater.showMessage(JSON.parse(event.data));
}
},
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
};
利用websocket實(shí)現(xiàn)推送
原理已經(jīng)很清晰了袱巨,可以在android的onmessage函數(shù)里增加彈出提示就可以了
使用websocket進(jìn)行推送有兩個(gè)缺點(diǎn)
服務(wù)器需要維護(hù)所有在線設(shè)備,開銷很大
需要android啟動(dòng)這個(gè)進(jìn)程并保持不斷才可以進(jìn)行推送