ps:这个几天前就做好了,因为去九寨沟,再上有点bug, so,回来才补上.
# WebSocket
HTTP是无状态的协议,对于那些实时要求比较高的应用来说,比如说在线游戏、在线证券、设备监控、新闻在线播报、RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。
解决这些问题最常用的就是轮询 (Polling) 和 Comet 技术,而 Comet 技术实际上是轮询技术的改进,又可细分为两种实现方式,一种是长轮询机制,一种称为流技术。
HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备实时通讯能力。WebSocket 协议本质上是一个基于 TCP 的协议。但这个协议目前仍是草案,只有最新的一些浏览器可以支持它。但是,它的好处是显而易见的,随着支持它的浏览器越来越多,我们将看到它越来越流行。
# Tornado的WebSocket模块
Tornado在websocket模块中提供了一个WebSocketHandler类。这个类提供了和已连接的客户端通信的WebSocket事件和方法的钩子。当一个新的WebSocket连接打开时,open方法被调用,而on_message和on_close方法分别在连接接收到新的消息和客户端关闭时被调用。
此外,WebSocketHandler类还提供了write_message方法用于向客户端发送消息,close方法用于关闭连接。
class EchoHandler(tornado.websocket.WebSocketHandler):
def open(self):
self.write_message('connected!')
def on_message(self, message):
self.write_message(message)
# 在线多人聊天
这里我参考别人一个简单的demo.记得在网上看过,BS总体设计,能让客户端做的,就尽量不要服务端来完成.所以,这里我uid,生成,json处理都交给了客户端,不过服务端还是避免不了处理,我这没学到家啊.
-
服务端设计
把所有的客户端对象存在一个对象或列表中,消息群发实际上就是一个消息转发每个客户端。
def send_to_all(self, message): for c in clients: c.write_message(json.dumps(message))
另外用户数变化时,发送更新用户列表消息
def updatelist(): total = len(clients) userlist = [] for c in clients: userlist.append(c.username) # pass msg = { 'type': 'list', 'total': total, 'userlist': userlist, } return msg
-
客户端设计
这部分主要用javascript完成,包括注册用户,发送消息,实时接收消息,注销等。
-
提交用户名及生成uid(为了同一昵称可以存在多用户)
function genUid(){ var uid = ""; for(var i=0;i<4;i++) uid+=Math.floor(Math.random()*10); return uid } function usernameSubmit(){ uid = genUid() username = d.getElementById("username").value; if(username != ""){ d.getElementById("username").value = ''; d.getElementById("loginbox").style.display = 'none'; d.getElementById("chatlog").style.display = 'block'; this.init(username); } return false; }
这里两个div,打开时loginbox显示,提示注册,注册成功后,聊天信息chatlog的div显示。
-
生成消息窗口
这里我参考demo完成了消息更新,更列表更新,原理很简单就是把消息生成对应的div,并书写相应的css就可以相应的效果。这里系统消息采用绿色字体,所以用if else 来判断.
function createChatEntry(username, uid, message) { var entry = document.createElement("div"); entry.setAttribute("id","chat_list"); if (username == "SYSTEM") { var sys_msg = document.createElement("span"); sys_msg.setAttribute("class","sys_message"); sys_msg.innerHTML = username + "(" + uid + "): " + message; entry.appendChild(sys_msg); } else { var dom_uname = document.createElement("span"); dom_uname.setAttribute("class","chat_username"); dom_uname.innerHTML = username + "(" + uid + "): "; entry.appendChild(dom_uname); var dom_msg = document.createElement("span"); dom_msg.setAttribute("class","chat_message"); dom_msg.innerHTML = message; entry.appendChild(dom_msg); } return entry; }
-
消息处理
function openWS(messageContainer, userListContainer) { ws = new WebSocket("ws://127.0.0.1:8003/chat"); ws.onmessage = function(e) { var data = JSON.parse(e.data); if (data.type == 'msg' || data.type == 'sys'){ messageContainer.appendChild(createChatEntry(data.username, data.uid, data.message)); } if (data.type == 'list'){ userListContainer.appendChild(createUserlist(data.total, data.userlist)); } scrollToBottom(); }; }
-
发送消息
function sendMessage() { var data = { type: "msg", username: username, uid: uid, message: document.getElementById("message").value }; if(data.username && data.message) { sendMsg(JSON.stringify(data)); } }
-
最后处理一些细节,让浏览器滚动条保持在最低部,清空发送消息框.
function scrollToBottom(){ // w.scrollTo(0, window.getElementById("chatbox").innerHeight); var obj = document.getElementById('chatbox'); obj.scrollTop = obj.scrollHeight; //清除消息框 document.getElementById("message").value=""; }
-
# 前端
这里前端还是采用Bootstrap 界面还是我一贯的清新简单风格。
# 坑
- Still in CONNECTING State
我注册用户时,采用直接发送消息,但一开始注册用户时,因为socket还在连接,所以导致发送消息失败提示
InvalidStateError “Still in CONNECTING State”
google大法好,对于我对于js都没入门的来说,在stackoverflow找到了解决办法,具体看参考地址。
- 服务端重启
如果服务端重启的话,就会导致userlist 清空,而相应的uid ,username 就没有了,所以发送消息就出问题, 我本意是重新注册,但是还是偷懒,与直接超时刷新页面,让用户重新注册.有机会改进.
# Demo
http://chat.linsir.org运行在Raspberry PI 上
# 总结
WebSocket协议目前仍是草案,在它完成时可能还会修改。尽管 HTML5 WebSocket 目前还有一些局限性,但是已经是大势所趋。
# 源代码
最后上代码:
https://github.com/vi5i0n/webchat
参考地址:
- [Simple WebSockets chat with Tornado Python] 2
- 使用 HTML5 WebSocket 构建实时 Web 应用
- Tornado WebSockets - InvalidStateError “Still in CONNECTING State”
- WebSocket+HTML5实现聊天室
- document.createElement()的用法