实验项目二 编写多人网页版聊天程序

  很快到了第二个项目的DDL了。这个项目之所以一直拖是因为在Tornado文档中就有官方示例,不过一开始难以静下心读代码,所以先把网页的html解决了。

 

  Part1 网页界面

  参考了hack.chat,在放弃了许多想法后最后的成品长这样:

图1 登录界面

 

图2 输入框界面

  聊天界面的输入框用的是textarea,做了随着文字量改变大小的效果,参考了ExpandingArea。做这个项目的时候蹦出了“Cannot read property 'addEventListener' of null”的提示,之前也遇到过,把scipt部分代码放在网页代码下方就解决了,实际上就是网页与js的加载顺序问题一样,jQuery的加载也如此。

  放弃了(以后回顾的时候可以加上?):

    1、输入框在上,形成类似微博事件流的聊天界面。

    2、气泡实现本地发言靠右,网络发言靠左的形式(最后放弃全边框,只保留了一边),以及更复杂的气泡(用户名+气泡的形式)。

    3、和Hack.chat一样的主题栏,可以切换css形式,虽然想到了实现方法(onmouseon+display+z-index等),临近ddl加上与需求无关,就没做了。

    4、回车键直接提交文本,这么做也美观,但耗时长感知性不强也没做了。

 

  Part2 Tornado 与 WebSocket

  根据前人总结先把框架写出来:

# coding = utf-8

import tornado.web
import tornado.ioloop
import os.path
 
# WebSocket 实现轮询到双工的转变
class ChatHandler(tornado.websocket.WebSocketHandler):  # 继承Handler以处理来自WebSocket协议的请求
   
    pool = set()  # 用户池
   
    def open(self):
    self.pool.add(self)  
    for _ in self.pool:
        pass  # 入场信息
   
    def on_close(self):
        self.pool.remove(self)
        for _ in self.pool:
            pass  # 离场信息

    def on_message(self, message):
        pass #全场发送消息
 
    class IndexHandler(tornado.web.RequestHandler):
        def get(self, *args, **kwags):
            self.render('chat_room.html')
 
if __name__ == "__main__":
    app = tornado.web.Application(
        [
        ],
 
    # 设置静态路径,不设置的话会出现无法加载css/js的问题
        template_path = os.path.join(os.path.dirname(__file__), "templates"),
        static_path = os.path.join(os.path.dirname(__file__), "static"),
 
        debug = True
    )
 
    app.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
       
根据之前的知识和WS相关写的框架

  最开始是模仿hack.chat那样弹窗输入用户名的,但是貌似Tornado没办法直接从网页中读取DOM参数,最后还是只能做了一个登录界面提交表单。

  后记:怎么不可以,jQuery可以,cookie也可以……

 

  整个功能的前后端逻辑是:用户访问host:8888/ → Tornado匹配并转到index.html → 用户输入用户名,点击按钮访问/home → Tornado接受表单信息并转到chat_room.html → 用户开始聊天。而WebSocket是在chat_room.html创建一个WebSocket对象,并连接到ws://host:8888/ws,Tornado匹配/ws并用继承了WebSocketHandler的类处理来自WebSocket协议的请求。如此一来,根据上述内容补充代码即可。

var ws = new WebSocket("ws://127.0.0.1:8888/chat");  // 指向对应Handler

ws.onmessage = function(e){
    var data = JSON.parse(e.data)
    //$("#container").append("<div id=\"msg\">" + data.uid + " says: " + data.msg + "</div>")  最初版本
    //$("#container").append("<div id=\"own_msg\"> debug: 1." + data.uid + "2." + $("#username").text() + "</div>")
    if (data.uid == $("#username").text()){  // 区分本机和网络信息
        $("#container").append("<div id=\"own_msg\"> " + data.msg + "</div>")
    }else{
        $("#container").append("<div id=\"msg\">" + data.uid + " : " + data.msg + "</div>")
    }
}

function sendMsg(){
    var msg = $("#textarea").val();
    // $("#container").append("<div id=\"msg\">" + $("#username").text() + " says: " + msg + "</div>")
    ws.send(msg);  // 向WebSocket发送消息,对应Handler类的on_message
    $("#textarea").val("");
}
JS部分
# WebSocket 实现轮询到双工的转变
class ChatHandler(WebSocketHandler):  # 继承Handler以处理来自WebSocket协议的请求
  
  pool = set()  # 用户池
  
  def open(self):
    self.pool.add(self)
    print("user login!")
  
  def on_close(self):
    self.pool.remove(self)
    print("user logout!")

  def on_message(self, message):
    uid_t = ""
    uid = self.get_cookie("uname")
    print("user {} send a message: {}".format(uid, message))
    for u in self.pool:
      print("updating")
      u.write_message(dict(
        uid = uid,
        msg = message,
        uid_t = uid_t
      ))

  def check_origin(self, origin):
        return True  # 允许WebSocket的跨域请求
Tornado部分

  

  最终实现效果如图:

图3 聊天界面

 

  Part3 jQuery与其它

  之前觉得jQuery是个麻烦的东西,用下来发现只用从公用网络导入包,就多了很多实用的功能。本次感触最深的就是js调用DOM时对script和html的加载顺序限制很大,要放之前我肯定还是老实的把script部分放下面,但因为做动态文本框的时候用到了jQuery,一搜才知道jQuery调用DOM过于方便了,以后我就是jQuery单推人了(bushi

  放两个值得学习的链接吧:

    1、jQuery教程

    2、WebSocket教程

  本次实验还有保存聊天记录的需求,因为数据都能在服务器端获取到,如下图所示。在python中用一个list保存,在下次登陆的时候匹配一下用户名并显示在网页上就可以了,然而跟我最开始构思的聊天室差距太大了,就不做了去赶原型图了

图4 服务器端获取到的消息

 

 


2021年4月5日

18点16分

H40Y

 

posted @ 2021-04-05 18:19  H40Y  阅读(5)  评论(0)    收藏  举报