实验项目二 编写多人网页版聊天程序
很快到了第二个项目的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()
最开始是模仿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(""); }
# 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的跨域请求
最终实现效果如图:

图3 聊天界面
Part3 jQuery与其它
之前觉得jQuery是个麻烦的东西,用下来发现只用从公用网络导入包,就多了很多实用的功能。本次感触最深的就是js调用DOM时对script和html的加载顺序限制很大,要放之前我肯定还是老实的把script部分放下面,但因为做动态文本框的时候用到了jQuery,一搜才知道jQuery调用DOM过于方便了,以后我就是jQuery单推人了(bushi
放两个值得学习的链接吧:
1、jQuery教程
本次实验还有保存聊天记录的需求,因为数据都能在服务器端获取到,如下图所示。在python中用一个list保存,在下次登陆的时候匹配一下用户名并显示在网页上就可以了,然而跟我最开始构思的聊天室差距太大了,就不做了去赶原型图了。

图4 服务器端获取到的消息
2021年4月5日
18点16分
H40Y

浙公网安备 33010602011771号