项目 08 WebSocket

项目班 08 WebSocket

  app.py 更新 添加两个路由

       handlers = [
            ('/', main.IndexHandler),
            ('/explore', main.ExploreHandler),
            ('/post/(?P<post_id>[0-9]+)', main.PostHandler),
            ('/upload', main.UploadHandler),
            ('/login', auth.LoginHandler),
            ('/logout', auth.LogoutHandler),
            ('/signup', auth.SignupHandler),
            ('/room', chat.RoomHandler),
            ('/ws', chat.ChatSocketHandler),
        ]

  base.html 更新

{% block extra_scripts %}{% end %} #在body最后添加这一条

  templates/message.html 添加HTML文件

<div class="message" id="m{{ message["id"] }}">{% module linkify(message["body"]) %}</div>

   templates/room.html 添加聊天室html

{% extends 'base.html' %}

{% block title %}room page{% end %}

{% block content %}


    <div id="body">
      <div id="inbox">
        {% for message in messages %}
          {% include "message.html" %}
        {% end %}
      </div>
      <div id="input">
        <form action="/a/message/new" method="post" id="messageform">
          <table>
            <tr>
              <td><input name="body" id="message" style="width:500px"></td>
              <td style="padding-left:5px">
                <input type="submit" value="提交">
                <input type="hidden" name="next" value="{{ request.path }}">
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
{% end %}

{% block extra_scripts %}
<script src="{{ static_url("js/chat.js") }}" type="text/javascript"></script>
{% end %}

  handlers/chat.py 添加聊天的handlers

import logging
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
import uuid

from .main import AuthBaseHandler

class RoomHandler(AuthBaseHandler):
    """
    聊天室页面
    """
    def get(self):
        self.render("room.html", messages=ChatSocketHandler.cache)


class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    waiters = set()    # 等待接收信息的用户
    cache = []         # 存放消息
    cache_size = 200   # 消息列表的大小

    def get_compression_options(self):
        """ 非 None 的返回值开启压缩 """
        return {}

    def open(self):
        """ 新的WebSocket连接打开 """
        logging.info("new connection %s" % self)
        ChatSocketHandler.waiters.add(self) #在集合中添加用户,出现相同用户会去重

    def on_close(self):
        """ WebSocket连接断开 """
        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:] #如果列表长度大于200个元素,只显示最后200个元素 [-200:-1]

    @classmethod
    def send_updates(cls, chat):
        """给每个等待接收的用户发新的消息"""
        logging.info("sending message to %d waiters", len(cls.waiters)) #logging类似于print,但又比print高级
        for waiter in cls.waiters:
            try:
                waiter.write_message(chat) #给每个waiter发送消息
            except:
                logging.error("Error sending message", exc_info=True)

    def on_message(self, message):
        """ WebSocket 服务端接收到消息 """
        logging.info("got message %r", message)
        parsed = tornado.escape.json_decode(message) #通过json解码message
        chat = { #创建一个chat字典,id为不重复的uuid字符串,body为上面json解码后的一个body
            "id": str(uuid.uuid4()),
            "body": parsed["body"],
        }
        chat["html"] = tornado.escape.to_basestring(self.render_string("message.html", message=chat))
                            #将chat赋给message,放入message.html里面渲染后变成一个html代码,然后通过tornado自带to_basestring方法
                            #转化为chat字典中html键的值;render_string只会返回字节流,需要用to_basestring来转化

        ChatSocketHandler.update_cache(chat) #执行更新消息列表函数
        ChatSocketHandler.send_updates(chat) #执行发送消息函数

  static/js/chat.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;
        }
    });
    $("#message").select();
    updater.start();   // 开始 WebSocket
});

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 + "/ws";
        updater.socket = new WebSocket(url);  // 初始化 WebSocket
        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);  // 添加消息 DIV 到页面
        // node.toggle();
    }
};

 

posted on 2018-06-23 19:23  许铖铖  阅读(148)  评论(0编辑  收藏  举报