Tornado普通方式实现聊天室
普通的http方式连接的话,基本思路是前端页面通过JS重复连接后端服务器.
核心文件:app.py
#!/usr/bin/env python# -*- coding:utf-8 -*-import tornado.ioloopimport tornado.webimport timeimport jsonclass IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render('index.html')li = [ {'id':123123, 'content': 'asdfasdfasdf'}]class MsgHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): index = self.get_argument('index') index = int(index) if index == 0: self.write(json.dumps(li)) else: self.write(json.dumps(li[index:]))settings = { 'template_path': 'views', 'static_path': 'static',}application = tornado.web.Application([ (r"/index.html", IndexHandler), (r"/msg.html", MsgHandler),], **settings)if __name__ == "__main__": print('http://127.0.0.1:8006') application.listen(8006) tornado.ioloop.IOLoop.instance().start()
index.html聊天页面:
tornado异步非阻塞方式
主要处理文件:
import loggingimport tornado.escapeimport tornado.ioloopimport tornado.webimport os.pathimport uuidfrom tornado.concurrent import Futurefrom tornado import genfrom tornado.options import define, options, parse_command_linedefine("port", default=8888, help="run on the given port", type=int)define("debug", default=False, help="run in debug mode")class MessageBuffer(object): def __init__(self): self.waiters = set() self.cache = [] self.cache_size = 200 def wait_for_messages(self, cursor=None): # Construct a Future to return to our caller. This allows # wait_for_messages to be yielded from a coroutine even though # it is not a coroutine itself. We will set the result of the # Future when results are available. result_future = Future() if cursor: new_count = 0 for msg in reversed(self.cache): if msg["id"] == cursor: break new_count += 1 if new_count: result_future.set_result(self.cache[-new_count:]) return result_future self.waiters.add(result_future) return result_future def cancel_wait(self, future): self.waiters.remove(future) # Set an empty result to unblock any coroutines waiting. future.set_result([]) def new_messages(self, messages): logging.info("Sending new message to %r listeners", len(self.waiters)) for future in self.waiters: future.set_result(messages) self.waiters = set() self.cache.extend(messages) if len(self.cache) > self.cache_size: self.cache = self.cache[-self.cache_size:]# Making this a non-singleton is left as an exercise for the reader.global_message_buffer = MessageBuffer()class MainHandler(tornado.web.RequestHandler): def get(self): self.render("index.html", messages=global_message_buffer.cache)class MessageNewHandler(tornado.web.RequestHandler): def post(self): message = { "id": str(uuid.uuid4()), "body": self.get_argument("body"), } # to_basestring is necessary for Python 3's json encoder, # which doesn't accept byte strings. message["html"] = tornado.escape.to_basestring( self.render_string("message.html", message=message)) if self.get_argument("next", None): self.redirect(self.get_argument("next")) else: self.write(message) global_message_buffer.new_messages([message])class MessageUpdatesHandler(tornado.web.RequestHandler): @gen.coroutine def post(self): cursor = self.get_argument("cursor", None) # Save the future returned by wait_for_messages so we can cancel # it in wait_for_messages self.future = global_message_buffer.wait_for_messages(cursor=cursor) messages = yield self.future if self.request.connection.stream.closed(): return self.write(dict(messages=messages)) def on_connection_close(self): global_message_buffer.cancel_wait(self.future)def main(): parse_command_line() app = tornado.web.Application( [ (r"/", MainHandler), (r"/a/message/new", MessageNewHandler), (r"/a/message/updates", MessageUpdatesHandler), ], 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, debug=options.debug, ) app.listen(options.port) tornado.ioloop.IOLoop.current().start()if __name__ == "__main__": main()
页面index.html
Tornado Chat Demo {% for message in messages %} {% module Template("message.html", message=message) %} {% end %}
消息页面message.html
css页面chat.css
body { background: white; margin: 10px;}body,input { font-family: sans-serif; font-size: 10pt; color: black;}table { border-collapse: collapse; border: 0;}td { border: 0; padding: 0;}#body { position: absolute; bottom: 10px; left: 10px;}#input { margin-top: 0.5em;}#inbox .message { padding-top: 0.25em;}#nav { float: right; z-index: 99;}
js处理页面chat.js
$(document).ready(function() { if (!window.console) window.console = {}; if (!window.console.log) window.console.log = function() {}; $("#messageform").on("submit", function() { newMessage($(this)); return false; }); $("#messageform").on("keypress", function(e) { if (e.keyCode == 13) { newMessage($(this)); return false; } return true; }); $("#message").select(); updater.poll();});function newMessage(form) { var message = form.formToDict(); var disabled = form.find("input[type=submit]"); disabled.disable(); $.postJSON("/a/message/new", message, function(response) { updater.showMessage(response); if (message.id) { form.parent().remove(); } else { form.find("input[type=text]").val("").select(); disabled.enable(); } });}function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined;}jQuery.postJSON = function(url, args, callback) { args._xsrf = getCookie("_xsrf"); $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", success: function(response) { if (callback) callback(eval("(" + response + ")")); }, error: function(response) { console.log("ERROR:", response); }});};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;};jQuery.fn.disable = function() { this.enable(false); return this;};jQuery.fn.enable = function(opt_enable) { if (arguments.length && !opt_enable) { this.attr("disabled", "disabled"); } else { this.removeAttr("disabled"); } return this;};var updater = { errorSleepTime: 500, cursor: null, poll: function() { var args = {"_xsrf": getCookie("_xsrf")}; if (updater.cursor) args.cursor = updater.cursor; $.ajax({url: "/a/message/updates", type: "POST", dataType: "text", data: $.param(args), success: updater.onSuccess, error: updater.onError}); }, onSuccess: function(response) { try { updater.newMessages(eval("(" + response + ")")); } catch (e) { updater.onError(); return; } updater.errorSleepTime = 500; window.setTimeout(updater.poll, 0); }, onError: function(response) { updater.errorSleepTime *= 2; console.log("Poll error; sleeping for", updater.errorSleepTime, "ms"); window.setTimeout(updater.poll, updater.errorSleepTime); }, newMessages: function(response) { if (!response.messages) return; updater.cursor = response.cursor; var messages = response.messages; updater.cursor = messages[messages.length - 1].id; console.log(messages.length, "new messages, cursor:", updater.cursor); for (var i = 0; i < messages.length; i++) { updater.showMessage(messages[i]); } }, showMessage: function(message) { var existing = $("#m" + message.id); if (existing.length > 0) return; var node = $(message.html); node.hide(); $("#inbox").append(node); node.slideDown(); }};
可在这下载: