在線聊天室的實現(3)--簡易聊天室的實現


前言:
  就如前文所講述的, 聊天室往往是最基本的網絡編程的學習案例. 本文以WebSocket為底層協議, 實現一個簡單的聊天室服務.
  服務器采用Netty 4.x來實現, 源於其對websocket的超強支持, 基於卓越的性能和穩定.
  本系列的文章鏈接如下:
  1). websocket協議和javascript版的api 
  2). 基於Netty 4.x的Echo服務器實現 

初步構想:
  本文對聊天室服務的定位還是比較簡單. 只需要有簡單的賬戶體系, 能夠實現簡單的群聊功能即可.
  流程設計初稿:
  1). 用戶登陸
  
  2). 群聊界面
  
  這里沒有聊天室的選擇, 只有唯一的一個. 這邊有沒有表情支持, 內容過濾. 一切皆從簡.

協議約定:
  在websocket協議的基礎之上, 我們引入應用層的聊天協議.
  協議以JSON作為數據交互格式, 並進行擴展和闡述.
  • 請求形態約定

request: {cmd:"$cmd", params: {}} 

  • 響應形態約定

response: {cmd:"$cmd", retcode:$retcode, datas:{}}	// retcode => 0: success, 1: fail

  
  1). 用戶登陸請求:

  request: {cmd:"login", params: {username:"$username"}}
  response: 
    成功: {cmd:"login", retcode: 0, datas:{username:$username, userid:$userid}}
    失敗: {cmd:"login", retcode:1, datas:{}}

  注: 成功后, 返回分配的userid(全局唯一).
  2). 消息發送請求:

  request: {cmd:"send_message", params:{message:$message, messageid:$messageid}}
  response: 
    成功: {cmd:"send_message", retcode:0, datas:{messageid:$messageid}}
    失敗: {cmd:"send_message", retcode:1, datas:{messageid:$messageid}}	

  注: 這邊的messageid是必須的, 可以提示那條消息成功還是失敗
  3). 消息接受(OnReceive):

  刷新用戶列表
    {cmd:"userlist", retcode:0, datas: {users: [{userid:$userid, username:$username}, {userid:$userid, username:$username}]}}
  接收消息
    {cmd:"receive_message", retcode:0, datas: {message:"$message", username:"$username", userid:"$userid", timestamp:"$timestamp"}}

服務端實現:
  借助Netty 4.x來構建服務器端, 其Channel的pipeline設定如下:

serverBootstrap.group(bossGroup, workerGroup)
  .channel(NioServerSocketChannel.class)
  .childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
      ChannelPipeline cp = socketChannel.pipeline();
      // *) 支持http協議的解析
      cp.addLast(new HttpServerCodec());
      cp.addLast(new HttpObjectAggregator(65535));
      // *) 對於大文件支持 chunked方式寫
      cp.addLast(new ChunkedWriteHandler());
      // *) 對websocket協議的處理--握手處理, ping/pong心跳, 關閉
      cp.addLast(new WebSocketServerProtocolHandler("/chatserver"));
      // *) 對TextWebSocketFrame的處理
      cp.addLast(new ChatLogicHandler(userGroupManager));
    }
  });

  注: HttpServerCodec / HttpObjectAggregator / ChunkedWriteHandler / WebSocketServerProtocolHandler, 依次引入極大簡化了websocket的服務編寫.
  ChatLogicHandler的定義, 其對TextWebSocketFrame進行解析處理, 並從中提取聊天協議的請求, 並進行相應的狀態處理.

@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame twsf) throws Exception {
  String text = twsf.text();
  JSONObject json = JSON.parseObject(text);

  if ( !json.containsKey("cmd") || !json.containsKey("params") ) {
    ctx.close();
    return;
  }

  String cmd = json.getString("cmd");
  if ( "login".equalsIgnoreCase(cmd) ) {
    // *) 處理登陸事件
    handleLoginEvent(ctx, json.getJSONObject("params"));
  } else if ( "send_message".equalsIgnoreCase(cmd) ) {
    // *) 處理群聊消息事件
    handleMessageEvent(ctx, json.getJSONObject("params"));
  }
}

  其實這邊還可以再加一層, 用於聊天協議的請求/響應的封裝, 這樣結構上更清晰. 但為了簡單起見, 就省略了. 

客戶端的實現:
  web客戶端最重要的還是, 對chatclient的封裝. 其封裝了websocket了, 實現了業務上的login和sendmessage函數.

(function(window) {

  ChatEvent.LOGIN_ACK = 1001;
  ChatEvent.SEND_MESSAGE_ACK = 1002;
  ChatEvent.USERLIST = 2001;
  ChatEvent.RECEIVE_MESSAGE = 2002;
  ChatEvent.UNEXPECT_ERROR = 10001;

  function ChatEvent(type, retcode, msg) {
    this.type = type;
    this.retcode = retcode; // retcode : 0 => success, 1 => fail
    this.msg = msg;
  }

  WebChatClient.UNCONNECTED = 0;
  WebChatClient.CONNECTED = 1;
  WebChatClient.LOGINED = 1;

  function WebChatClient() {
    this.websocket = null;
    this.state = WebChatClient.UNCONNECTED;
    this.chatEventListener = null;
  }

  WebChatClient.prototype.init = function(wsUrl, chatEventListener) {
    this.state = WebChatClient.UNCONNECTED;
    this.chatEventListener = chatEventListener;
    this.websocket = new WebSocket(wsUrl); //創建WebSocket對象
    var self = this;
    this.websocket.onopen = function(evt) {
      self.state = WebChatClient.CONNECTED;
    }
    this.websocket.onmessage = function(evt) {
      var res = JSON.parse(evt.data);
      if ( !res.hasOwnProperty("cmd") || !res.hasOwnProperty("retcode") || !res.hasOwnProperty("datas") ) {
        self.chatEventListener(new ChatEvent(ChatEvent.UNEXPECT_ERROR, 0));
      } else {
        var cmd = res["cmd"];
        var retcode = res["retcode"];
        var datas = res["datas"];
        switch(cmd) {
          case "login":
            self.chatEventListener(new ChatEvent(ChatEvent.LOGIN_ACK, retcode, datas));
            break;
          case "send_message":
            self.chatEventListener(new ChatEvent(ChatEvent.SEND_MESSAGE_ACK, retcode, datas));
            break;
          case "userlist":
            self.chatEventListener(new ChatEvent(ChatEvent.USERLIST, retcode, datas));
            break;
          case "receive_message":
            self.chatEventListener(new ChatEvent(ChatEvent.RECEIVE_MESSAGE, retcode, datas));
            break;
        }
      }
    }
    this.websocket.onerror = function(evt) {
    }
  }

  WebChatClient.prototype.login = function(username) {
    if ( this.websocket.readyState == WebSocket.OPEN && this.state == WebChatClient.CONNECTED ) {
      var msgdata = JSON.stringify({cmd:"login", params:{username:username}});
      this.websocket.send(msgdata);
    }
  }

  WebChatClient.prototype.sendMessage = function(message) {
    if ( this.websocket.readyState == WebSocket.OPEN && this.state == WebChatClient.LOGINED ) {
      var msgdata = JSON.stringify({cmd:"send_message", params:{message:message}});
      this.websocket.send(msgdata);
    }
  }

  // export
  window.ChatEvent = ChatEvent;
  window.WebChatClient = WebChatClient;

})(window);

代碼下載:
  由於采用websocket作為底層網絡通訊協議, 因此需要瀏覽器支持(最好為Chrome).
  服務器和web客戶端的源碼下載地址為: http://pan.baidu.com/s/1pJDYo3p.
  文件的目錄結構如下:
  

后期展望:
  編寫完初步版本后, 一群有愛的小伙伴們幫我友情測試了一把. 中間反饋了很多體驗上的改進意見. 比如Enter鍵自動發送, 最新留言與頁面底部同步, 自己名稱高亮顯示.
  也有反饋添加表情等高大上的功能, ^_^. 總之感覺棒棒噠, 覺得再做一件很偉大的事情. oh yeah.

寫在最后:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM