天天看點

WebSocket系列之的網頁端即時通訊實作

本部落格介紹基于html5的Websocket網頁即時通訊技術,前端開發采用ExtJS前端架構

JavaEE架構:Mybatis、SpringMVC

先去官網下載下傳ExtJS架構的資料檔案:

​​​ https://www.sencha.com/products/extjs/evaluate/​​​ 可以參考中文翻譯過來的官網檢視API:

http://extjs-doc-cn.github.io/ext4api/

下載下傳內建的jar:

WebSocket系列之的網頁端即時通訊實作
WebSocket系列之的網頁端即時通訊實作

websocket.css:

@CHARSET "UTF-8";
.l-im-message-warn {
  font-family: "微軟雅黑";
  cursor: default;
  width: 100%;
  padding: 5px 0px 5px 25px;
  -webkit-user-select : none;
  background: url("../images/information.png") no-repeat 5;
}

.l-im-message {
  font-family: "微軟雅黑";
  cursor: default;
  width: 100%;
}

.l-im-message-over {
  background-color: rgba(233, 233, 233, 0.5);
}

.l-im-message-selected {
  background-color: rgba(250, 218, 90, 0.5);
}

.l-im-message-header {
  font-size: 12px;
  padding: 5px 0px 5px 10px;
}

.l-im-message-header-self {
  color: green;
}
.l-im-message-header-remote {
  color: blue;
}

.l-im-message-body {
  font-size: 12px;
  padding: 2px 0px 2px 20px;
}

.user-win {
  background-image: url( ../images/user_win.png )
    !important;
}

.user-online {
  background-image: url( ../images/group.png )
    !important;
}

.user {
  background-image: url( ../images/user.gif )
    !important;
}      

websocket.js:

var websocket;
var isCreatw = false;
var title="";
var win;
var input;
var isQj = true;
var toUser="";
function toUserMsg(toU){
  if((!isQj && toUser == toU) || toU == user){
    win.setTitle(title + "  (已連接配接)   【現在全局對話】");
    isQj = true;
    toUser = "";
  }else{
    win.setTitle(title + "  (已連接配接)   【現在單獨與"+toU+"對話】");
    isQj = false;
    toUser = toU;
  }
}
 function creatw() {
      if(isCreatw){
        alert("已經啟動");
        return;
      }else{
        isCreatw = true;
      }
      //建立使用者輸入框
      input = Ext.create('Ext.form.field.HtmlEditor', {
            region : 'south',
            height : 120,
            enableFont : false,
            enableSourceEdit : false,
            enableAlignments : false,
            listeners : {
              initialize : function() {
                Ext.EventManager.on(me.input.getDoc(), {
                      keyup : function(e) {
                        if (e.ctrlKey === true
                            && e.keyCode == 13) {
                          e.preventDefault();
                          e.stopPropagation();
                          send();
                        }
                      }
                    });
              }
            }
          });
      
      //建立消息展示容器
      var output = Ext.create('MessageContainer', {
            region : 'center'
          });

      var dialog = Ext.create('Ext.panel.Panel', {
            region : 'center',
            layout : 'border',
            items : [input, output],
            buttons : [{
                  text : '發送',
                  handler : send
                }]
          });

      //初始話WebSocket
      function initWebSocket() {
        if (window.WebSocket) {
          websocket = new WebSocket(encodeURI('ws://'+wimadress));
          
          websocket.onopen = function() {
            //連接配接成功
            win.setTitle(title + '  (已連接配接)   【現在全局對話】');
            websocket.send('admin'+user);
          }
          websocket.onerror = function() {
            //連接配接失敗
            win.setTitle(title + '  (連接配接發生錯誤)');
          }
          websocket.onclose = function() {
            //連接配接斷開
            win.setTitle(title + '  (已經斷開連接配接)');
          }
          //消息接收
          websocket.onmessage = function(message) {
            var message = JSON.parse(message.data);
            //接收使用者發送的消息
            if (message.type == 'message') {
              output.receive(message);
            } else if (message.type == 'get_online_user') {
              //擷取線上使用者清單
              var root = onlineUser.getRootNode();
              Ext.each(message.list,function(user){
                var node = root.createNode({
                  id : user,
                  text : user,
                  iconCls : 'user',
                  leaf : true
                });
                root.appendChild(node);
              });
            } else if (message.type == 'user_join') {
              //使用者上線
                var root = onlineUser.getRootNode();
                var user = message.user;
                var node = root.createNode({
                  id : user,
                  text : user,
                  iconCls : 'user',
                  leaf : true
                });
                root.appendChild(node);
            } else if (message.type == 'user_leave') {
                //使用者下線
                var root = onlineUser.getRootNode();
                var user = message.user;
                var node = root.findChild('id',user);
                root.removeChild(node);
            }
          }
        }
      };

      //線上使用者樹
      var onlineUser = Ext.create('Ext.tree.Panel', {
            title : '線上使用者',
            rootVisible : false,
            region : 'east',
            width : 150,
            lines : false,
            useArrows : true,
            autoScroll : true,
            split : true,
            iconCls : 'user-online',
            store : Ext.create('Ext.data.TreeStore', {
                  root : {
                    text : '線上使用者',
                    expanded : true,
                    children : []
                  }
                })
          });
      
      title = '歡迎您:' + user;
      //展示視窗
      win = Ext.create('Ext.window.Window', {
            title : title + '  (未連接配接)',
            layout : 'border',
            iconCls : 'user-win',
            minWidth : 650,
            minHeight : 460,
            width : 650,
            animateTarget : 'websocket_button',
            height : 460,
            items : [dialog,onlineUser],
            border : false,
            listeners : {
              render : function() {
                initWebSocket();
              }
            }
          });

      win.show();
      
      win.on("close",function(){
        websocket.send('LeaveAdmin');
        isCreatw = false;
       });

      //發送消息
      function send() {
        var content = input.getValue();
        if(toUser != ""){content = "admin886"+toUser+"admin888" + content;}
        var message = {};
        if (websocket != null) {
          if (input.getValue()) {
            Ext.apply(message, {
                  from : user,
                  content : content,
                  timestamp : new Date().getTime(),
                  type : 'message'
                });
            websocket.send(JSON.stringify(message));
            //output.receive(message);
            input.setValue('');
          }
        } else {
          Ext.Msg.alert('提示', '您已經掉線,無法發送消息!');
        }
      }
};

//用于展示使用者的聊天資訊
Ext.define('MessageContainer', {

  extend : 'Ext.view.View',

  trackOver : true,

  multiSelect : false,

  itemCls : 'l-im-message',

  itemSelector : 'div.l-im-message',

  overItemCls : 'l-im-message-over',

  selectedItemCls : 'l-im-message-selected',

  style : {
    overflow : 'auto',
    backgroundColor : '#fff'
  },

  tpl : [
      '<div class="l-im-message-warn">歡迎使用即時通訊系統。</div>',
      '<tpl for=".">',
      '<div class="l-im-message">',
      '<div class="l-im-message-header l-im-message-header-{source}">{from}  {timestamp}</div>',
      '<div class="l-im-message-body">{content}</div>', '</div>',
      '</tpl>'],

  messages : [],

  initComponent : function() {
    var me = this;
    me.messageModel = Ext.define('Leetop.im.MessageModel', {
          extend : 'Ext.data.Model',
          fields : ['from', 'timestamp', 'content', 'source']
        });
    me.store = Ext.create('Ext.data.Store', {
          model : 'Leetop.im.MessageModel',
          data : me.messages
        });
    me.callParent();
  },

  //将伺服器推送的資訊展示到頁面中
  receive : function(message) {
    var me = this;
    message['timestamp'] = Ext.Date.format(new Date(message['timestamp']),
        'H:i:s');
    if(message.from == user){
      message.source = 'self';
    }else{
      message.source = 'remote';
    }
    me.store.add(message);
    if (me.el.dom) {
      me.el.dom.scrollTop = me.el.dom.scrollHeight;
    }
  }
});      

業務代碼編寫:

ChatServer.java

package com.appms.websocket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Date;

import net.sf.json.JSONObject;

import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;


/**
 * 即時通訊
 */
public class ChatServer extends WebSocketServer{

  public ChatServer(int port) throws UnknownHostException {
    super(new InetSocketAddress(port));
  }

  public ChatServer(InetSocketAddress address) {
    super(address);
  }

  /**
   * 觸發連接配接事件
   */
  @Override
  public void onOpen( WebSocket conn, ClientHandshake handshake ) {
  }

  /**
   * 觸發關閉事件
   */
  @Override
  public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
    userLeave(conn);
  }

  /**
   * 用戶端發送消息到伺服器時觸發事件
   */
  @Override
  public void onMessage(WebSocket conn, String message){
    message = message.toString();
    if(null != message && message.startsWith("admin")){
      this.userjoin(message.replaceFirst("admin", ""),conn);
    }if(null != message && message.startsWith("LeaveAdmin")){
      this.userLeave(conn);
    }if(null != message && message.contains("admin886")){
      String toUser = message.substring(message.indexOf("admin886")+8, message.indexOf("admin888"));
      message = message.substring(0, message.indexOf("admin886")) +"[私信]  "+ message.substring(message.indexOf("admin888")+8, message.length());
      ChatServerPool.sendMessageToUser(ChatServerPool.getWebSocketByUser(toUser),message);//向所某使用者發送消息
      ChatServerPool.sendMessageToUser(conn, message);//同時向本人發送消息
    }else{
      ChatServerPool.sendMessage(message.toString());//向所有線上使用者發送消息
    }
  }

  public void onFragment( WebSocket conn, Framedata fragment ) {
  }

  /**
   * 觸發異常事件
   */
  @Override
  public void onError( WebSocket conn, Exception ex ) {
    ex.printStackTrace();
    if( conn != null ) {
      //some errors like port binding failed may not be assignable to a specific websocket
    }
  }

  
  /**
   * 使用者加入處理
   * @param user
   */
  public void userjoin(String user, WebSocket conn){
    JSONObject result = new JSONObject();
    result.element("type", "user_join");
    result.element("user", "<a onclick=\"toUserMsg('"+user+"');\">"+user+"</a>");
    ChatServerPool.sendMessage(result.toString());        //把目前使用者加入到所有線上使用者清單中
    String joinMsg = "{\"from\":\"[系統]\",\"content\":\""+user+"上線了\",\"timestamp\":"+new Date().getTime()+",\"type\":\"message\"}";
    ChatServerPool.sendMessage(joinMsg);            //向所有線上使用者推送目前使用者上線的消息
    result = new JSONObject();
    result.element("type", "get_online_user");
    ChatServerPool.addUser(user,conn);              //向連接配接池添加目前的連接配接對象
    result.element("list", ChatServerPool.getOnlineUser());
    ChatServerPool.sendMessageToUser(conn, result.toString());  //向目前連接配接發送目前線上使用者的清單
  }
  
  /**
   * 使用者下線處理
   * @param user
   */
  public void userLeave(WebSocket conn){
    String user = ChatServerPool.getUserByKey(conn);
    boolean b = ChatServerPool.removeUser(conn);        //在連接配接池中移除連接配接
    if(b){
      JSONObject result = new JSONObject();
      result.element("type", "user_leave");
      result.element("user", "<a onclick=\"toUserMsg('"+user+"');\">"+user+"</a>");
      ChatServerPool.sendMessage(result.toString());      //把目前使用者從所有線上使用者清單中删除
      String joinMsg = "{\"from\":\"[系統]\",\"content\":\""+user+"下線了\",\"timestamp\":"+new Date().getTime()+",\"type\":\"message\"}";
      ChatServerPool.sendMessage(joinMsg);          //向線上使用者發送目前使用者退出的消息
    }
  }
  public static void main( String[] args ) throws InterruptedException , IOException {
    WebSocketImpl.DEBUG = false;
    int port = 8887; //端口
    ChatServer s = new ChatServer(port);
    s.start();
    System.out.println( "伺服器的端口" + s.getPort() );
  }

}      

ChatServerPool.java:

package com.appms.websocket;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.java_websocket.WebSocket;

/**
 * 即時通訊
 */
public class ChatServerPool {

  private static final Map<WebSocket,String> userconnections = new HashMap<WebSocket,String>();
  
  /**
   * 擷取使用者名
   * @param session
   */
  public static String getUserByKey(WebSocket conn){
    return userconnections.get(conn);
  }
  
  /**
   * 擷取WebSocket
   * @param user
   */
  public static WebSocket getWebSocketByUser(String user){
    Set<WebSocket> keySet = userconnections.keySet();
    synchronized (keySet) {
      for (WebSocket conn : keySet) {
        String cuser = userconnections.get(conn);
        if(cuser.equals(user)){
          return conn;
        }
      }
    }
    return null;
  }
  
  /**
   * 向連接配接池中添加連接配接
   * @param inbound
   */
  public static void addUser(String user, WebSocket conn){
    userconnections.put(conn,user); //添加連接配接
  }
  
  /**
   * 擷取所有的線上使用者
   * @return
   */
  public static Collection<String> getOnlineUser(){
    List<String> setUsers = new ArrayList<String>();
    Collection<String> setUser = userconnections.values();
    for(String u:setUser){
      setUsers.add("<a onclick=\"toUserMsg('"+u+"');\">"+u+"</a>");
    }
    return setUsers;
  }
  
  /**
   * 移除連接配接池中的連接配接
   * @param inbound
   */
  public static boolean removeUser(WebSocket conn){
    if(userconnections.containsKey(conn)){
      userconnections.remove(conn); //移除連接配接
      return true;
    }else{
      return false;
    }
  }
  
  /**
   * 向特定的使用者發送資料
   * @param user
   * @param message
   */
  public static void sendMessageToUser(WebSocket conn,String message){
    if(null != conn && null != userconnections.get(conn)){
      conn.send(message);
    }
  }
  
  /**
   * 向所有的使用者發送消息
   * @param message
   */
  public static void sendMessage(String message){
    Set<WebSocket> keySet = userconnections.keySet();
    synchronized (keySet) {
      for (WebSocket conn : keySet) {
        String user = userconnections.get(conn);
        if(user != null){
          conn.send(message);
        }
      }
    }
  }
}      

寫個過濾器,在項目執行時啟動:

package com.appms.filter;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.java_websocket.WebSocketImpl;

import com.appms.base.BaseController;
import com.appms.base.Const;
import com.appms.utils.Tools;
import com.appms.websocket.ChatServer;
import com.appms.websocket.OnlineChatServer;

public class StartFilter extends BaseController implements Filter{

  /**
   * 初始化
   */
  public void init(FilterConfig fc) throws ServletException {
    this.startWebsocketInstantMsg();
    this.startWebsocketOnline();
  }
  
  /**
   * 啟動即時聊天服務
   */
  public void startWebsocketInstantMsg(){
    WebSocketImpl.DEBUG = false;
    ChatServer s = null;
    try {
      String strWEBSOCKET = Tools.readTxtFile(Const.WEBSOCKET);//讀取WEBSOCKET配置,擷取端口配置
      if(null != strWEBSOCKET && !"".equals(strWEBSOCKET)){
        String strIW[] = strWEBSOCKET.split(",fh,");
        if(strIW.length == 4){
          s = new ChatServer(Integer.parseInt(strIW[1]));
          s.start();
        }
      }
      System.out.println( "websocket伺服器啟動,端口" + s.getPort() );
    } catch (UnknownHostException e) {
      e.printStackTrace();
    }
  }
  
  /**
   * 啟動線上管理服務
   */
  public void startWebsocketOnline(){
    WebSocketImpl.DEBUG = false;
    OnlineChatServer s = null;
    try {
      String strWEBSOCKET = Tools.readTxtFile(Const.WEBSOCKET);//讀取WEBSOCKET配置,擷取端口配置
      if(null != strWEBSOCKET && !"".equals(strWEBSOCKET)){
        String strIW[] = strWEBSOCKET.split(",fh,");
        if(strIW.length == 4){
          s = new OnlineChatServer(Integer.parseInt(strIW[3]));
          s.start();
        }
      }
      System.out.println( "websocket伺服器啟動,端口" + s.getPort() );
    } catch (UnknownHostException e) {
      e.printStackTrace();
    }
  }
  
  
  //計時器
  public void timer() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 9); // 控制時
    calendar.set(Calendar.MINUTE, 0);     // 控制分
    calendar.set(Calendar.SECOND, 0);     // 控制秒

    Date time = calendar.getTime();     // 得出執行任務的時間

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
      public void run() {
        //PersonService personService = (PersonService)ApplicationContext.getBean("personService");
      }
    }, time, 1000*60*60*24);// 這裡設定将延時每天固定執行
  }


  public void destroy() {
    // TODO Auto-generated method stub
    
  }


  public void doFilter(ServletRequest arg0, ServletResponse arg1,
      FilterChain arg2) throws IOException, ServletException {
    // TODO Auto-generated method stub
    
  }
  
}      

在web.xml裡配置:

<filter>
    <filter-name>startFilter</filter-name>
    <filter-class>com.appms.filter.StartFilter</filter-class>
  </filter>      

在jsp頁面進行調用:

<script type="text/javascript">var wimadress="127.0.0.1:8887";</script>
  <script type="text/javascript">var oladress="127.0.0.1:8889";</script>
  <link rel="stylesheet" type="text/css" href="plugins/websocket/ext4/resources/css/ext-all.css">
  <link rel="stylesheet" type="text/css" href="plugins/websocket/css/websocket.css" />
  <script type="text/javascript" src="plugins/websocket/ext4/ext-all-debug.js"></script>
  <script type="text/javascript" src="plugins/websocket/websocket.js"></script>
    <!--引入屬于此頁面的js -->
  <script type="text/javascript" src="source/js/jquery-1.8.3.js"></script>      
ul class="am-avg-sm-1 am-avg-md-4 am-margin am-padding am-text-center admin-content-list ">
      <li onclick="creatw();"><a href="javascript:;"><span class="am-icon-btn am-icon-file-text"></span><br/>即時通訊<br/></a></li>
      <li><a href="fusioncharts/index.do" class="am-text-warning"><span class="am-icon-btn am-icon-briefcase"></span><br/>圖表統計<br/></a></li>
      <li><a href="#" class="am-text-danger"><span class="am-icon-btn am-icon-recycle"></span><br/>昨日通路<br/>80082</a></li>
      <li><a href="#" class="am-text-secondary"><span class="am-icon-btn am-icon-user-md"></span><br/>線上使用者<br/>3000</a></li>
    </ul>