基于WebSocket的在线聊天室

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)

websocket


# 在线多人聊天

这里我参考别人一个简单的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完成,包括注册用户,发送消息,实时接收消息,注销等。

    1. 提交用户名及生成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显示。

    2. 生成消息窗口

      这里我参考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;
      }
      
    3. 消息处理

      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();
        };
      }
      
    4. 发送消息

      function sendMessage() {
        var data = { 
                     type: "msg",
                     username: username,
                     uid: uid,
                     message: document.getElementById("message").value };
        
        if(data.username && data.message) {
          sendMsg(JSON.stringify(data));
        }
      }
      
    5. 最后处理一些细节,让浏览器滚动条保持在最低部,清空发送消息框.

      function scrollToBottom(){
        // w.scrollTo(0, window.getElementById("chatbox").innerHeight);
        var obj = document.getElementById('chatbox');
        obj.scrollTop = obj.scrollHeight;
        //清除消息框
        document.getElementById("message").value="";
      }
      
      

# 前端

这里前端还是采用Bootstrap 界面还是我一贯的清新简单风格。


#

  1. Still in CONNECTING State

我注册用户时,采用直接发送消息,但一开始注册用户时,因为socket还在连接,所以导致发送消息失败提示

 InvalidStateError “Still in CONNECTING State”

google大法好,对于我对于js都没入门的来说,在stackoverflow找到了解决办法,具体看参考地址。

  1. 服务端重启

如果服务端重启的话,就会导致userlist 清空,而相应的uid ,username 就没有了,所以发送消息就出问题, 我本意是重新注册,但是还是偷懒,与直接超时刷新页面,让用户重新注册.有机会改进.


# Demo

webchat

http://chat.linsir.org运行在Raspberry PI 上


# 总结

WebSocket协议目前仍是草案,在它完成时可能还会修改。尽管 HTML5 WebSocket 目前还有一些局限性,但是已经是大势所趋。


# 源代码

最后上代码:

https://github.com/vi5i0n/webchat


参考地址:

  1. [Simple WebSockets chat with Tornado Python] 2
  2. 使用 HTML5 WebSocket 构建实时 Web 应用
  3. Tornado WebSockets - InvalidStateError “Still in CONNECTING State”
  4. WebSocket+HTML5实现聊天室
  5. document.createElement()的用法

--EOF--


>看不到评论?GFW!!!