1 搭建運行websocket的環境(這裏只用tomcat說明)html
爲了能讓websocket運行起來,須要tomcat 7.0版本以上,可是目前X5使用的是tomcat6,能夠經過如下兩種方式達到條件前端
1.1 經過替換掉X5裏面的tomcat來升級,替換步驟以下:java
step1 下載解壓版的tomcat 8mysql
https://tomcat.apache.org/download-80.cgiweb
step2 把tomcat 8拷貝到平臺版本中把名字改成平臺版本默認帶的tomcat目錄的名字sql
step3 把平臺默認帶的tomcat中的apache-tomcat\conf\context.xml文件和apache-tomcat\conf\Catalina\localhost下面的配置文件拷貝到tomcat 8中數據庫
step4 在本身的tomcat的lib中放數據庫驅動,平臺默認的tomcat的lib下帶的數據庫驅動以下: apache
jtds-1.2.jar、mysql-connector-java-5.1.36-bin.jar、ojdbc14.jarjson
step5 若是tomcat 8端口號不是8080,須要修改model同級的conf/server.xml中配置的地址中的端口號windows
step6 部署後輸入http://IP:端口默認不會跳轉到平臺的頁面中以及地址欄中圖標是tomcat默認的,不是平臺的藍色圖標;若是須要默認跳轉到平臺頁面而且用平臺的圖標,須要把平臺默認帶的tomcat\webapps\ROOT下的index.html和favicon.ico兩個文件拷貝到本身的tomcat\webapps\ROOT下
step7 須要在apache-tomcat\bin\startup.bat中配置java的環境變量
1 set JRE_HOME=..\..\java\jre1.8 2 set JAVA_HOME= 3 set CATALINA_BASE=..\..\apache-tomcat 4 set PATH=%JRE_HOME%\bin;%PATH%
(若是按照以上步驟有疑問能夠參考官網帖子http://docs.wex5.com/bex5-deploy-question-list-4001/)
注意:安裝以上步驟部署後開發工具點擊運行時會出現如下報錯
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory
at org.apache.catalina.startup.Bootstrap.<clinit>(Bootstrap.java:49)
Caused by: java.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
解決方法可參考http://docs.wex5.com/wex5-deploy-quetion-list-0006/
在Studio中,執行如下操做:
step1 在右上角選擇「Java」,切換到Java視圖
step2 選擇「運行 –> 調試配置…」,彈出調試配置對話框
step3 在「調試配置」對話框中選擇「Java應用程序 –> Tomat 5.x」, 選擇「類路徑」選項卡中,將%TOMCAT_HOME%\bin\tomcat-juli.jar添加至「用戶條目」中,以後點「應用」
step4 以後運行Tomcat不能經過Tomcat圖標快捷方式啓動, 必須在Java視圖中,使用下圖中的「Tomcat 5.x」啓動Tomcat
step5 啓動tomcat後, 會出現如下錯誤
[JPivot] 13 六月 2016 17:29:26,072 ERROR [Session ] com.tonbeller.tbutils.res.JNDIResourceProvider#close: error closing context javax.naming.OperationNotSupportedException: Context is read only at org.apache.naming.NamingContext.checkWritable(NamingContext.java:961) at org.apache.naming.NamingContext.close(NamingContext.java:761) at com.tonbeller.tbutils.res.JNDIResourceProvider.close(JNDIResourceProvider.java:68) at com.tonbeller.tbutils.res.CompositeResourceProvider.close(CompositeResourceProvider.java:56) at com.tonbeller.tbutils.res.ResourcesFactory.initialize(ResourcesFactory.java:163) at com.tonbeller.tbutils.res.ResourcesFactory.<init>(ResourcesFactory.java:92) at com.tonbeller.tbutils.res.ResourcesFactory.<clinit>(ResourcesFactory.java:89) at com.tonbeller.tbutils.res.ResourcesFactoryContextListener.contextInitialized(ResourcesFactoryContextListener.java:23) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5068) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5584) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652) at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:679) at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1966) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
處理方式:在%JUSTEP_HOME%\runtime\ReportServer\WEB-INF\classes目錄下添加一個resfactory.properties文件,文件內容爲
tbeller.usejndi=false
以上的操做貌似對tomcat8並不生效,並且也麻煩,因此我的建議最好採用另外一種方法來知足運行需求
1.2 經過在X5以外部署新的tomcat七、8來運行websocket
step1 安裝JRE,若是使用X5自帶的則直接跳過這步
step2 解壓安裝tomcat8(下載地址參考上面的方法)
step3 配置環境變量(百度就知道),可是爲了避免影響同服務器的其餘tomcat,可使用上面方法的step7
step4 修改tomcat的端口,讓它不與其餘tomcat端口衝突
2 請求websocket使用ws仍是wss的選擇
當使用websocket的項目使用的是https,若是使用ws協議請求鏈接websocket會出現如下錯誤
Mixed Content: The page at 'https://xxx' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://xxx/websocket/?rid=18'.
This request has been blocked; this endpoint must be available over WSS.
恰好發現有個博客遇到了一樣的問題 https://mengkang.net/774.html
爲了解決這一問題,提供瞭如下兩種方法
2.1 讓https請求中容許使用ws
在頁面頭信息增長
header('Content-Security-Policy: connect-src *;');
(這個方法我沒測試過,能夠本身根據那個博客試一下)
2.2 爲websocket所在tomcat增長ssl證書
假如websocket部署的是跟X5部署的服務器是同一個,那麼X5使用https的話,直接把證書配置進websocket的tomcat就好了;可是也有可能websocket所在的服務器和x5並不必定在同一個服務器,因此下面講一下let‘s encrypt證書的申請方法。
(let‘s encrypt是什麼本身百度就能夠了,反正就是一個免費的好用的證書)
本方法是在windows服務器下申請證書,若是是其餘系統,能夠自行百度,通常使用openssl進行申請,我這裏是採用證書官方提供的工具進行申請
step1 下載一個letsencrypt-win-simple工具,放入服務器解壓
https://pan.baidu.com/s/1g_Enw7CHrRGxkfwYBY8F-g
step2 點擊letsencrypt運行
step3 填寫我的郵箱以接收證書過時等信息
step4 贊成一些啥協議(相似安裝時候選的贊成,還正只能選Y)
step5 選擇證書,選擇M
step6 填寫域名
step7 填寫域名對應服務的根目錄文件路徑,例如我用的端口80的是tomcat,那麼我就要填tomcat的webapps下的ROOT路徑
它會把一個文件放入到根路徑下,並經過域名+根路徑方式來訪問,若是訪問到了就會生成證書,證書地址它會在命令窗口顯示出來
通常會生成到C:\Users\Administrator\AppData\Roaming\letsencrypt-win-simple,這個路徑直接輸入到系統地址欄跳轉
step8 上面步驟經過後還會詢問是否須要自動續證書(證書有效期3個月),若是須要,後面會要求填寫服務器登錄的帳號密碼,這裏就直接跳過,能夠本身嘗試
step9 獲得證書後,就能夠到tomcat 8 中增長如下代碼
<Connector port="443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" URIEncoding="UTF-8"> <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <SSLHostConfig> <Certificate certificateKeyFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-key.pem" certificateFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-crt.pem" certificateChainFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-chain.pem" type="RSA" /> </SSLHostConfig> </Connector>
這樣就能經過https進行訪問了
(在網上看到個差很少的證書申請博客 https://blog.csdn.net/qq_27424559/article/details/67661220)
3 websocket代碼實現
3.1 後端服務
(項目名爲ZCServer)
step1 增長一個監聽用的websocket
1 package com.zc.websocket; 2 3 import java.io.IOException; 4 import java.util.HashMap; 5 import java.util.Map; 6 import java.util.concurrent.CopyOnWriteArraySet; 7 8 import javax.websocket.OnClose; 9 import javax.websocket.OnOpen; 10 import javax.websocket.Session; 11 import javax.websocket.server.ServerEndpoint; 12 13 14 @ServerEndpoint("/listener") 15 public class Listener { 16 17 private static int onlineCount = 0; 18 19 private static Map<String,CopyOnWriteArraySet<Listener>> webSocketMap = new HashMap<String,CopyOnWriteArraySet<Listener>>(); 20 21 private String fEventID = null; 22 23 //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 24 private Session session; 25 26 27 public static CopyOnWriteArraySet<Listener> getwebSocketSet(String fEventID){ 28 29 return Listener.webSocketMap.get(fEventID); 30 } 31 32 /** 33 * 鏈接創建成功調用的方法 34 * 35 * @param session 可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 36 * 37 */ 38 @OnOpen 39 public void onOpen(Session session) { 40 this.session = session; 41 42 this.fEventID = session.getRequestParameterMap().get("fEventID").get(0); 43 44 CopyOnWriteArraySet<Listener> socekts = webSocketMap.get(this.fEventID); 45 if(socekts==null){ 46 socekts = new CopyOnWriteArraySet<Listener>(); 47 webSocketMap.put(this.fEventID,socekts); 48 } 49 50 socekts.add(this); 51 52 addOnlineCount(); 53 System.out.println("有新監聽加入!當前監聽數爲" + getOnlineCount()); 54 } 55 56 /** 57 * 鏈接關閉調用的方法 58 */ 59 @OnClose 60 public void onClose() { 61 62 webSocketMap.get(this.fEventID).remove(this); 63 64 65 subOnlineCount(); 66 System.out.println("有一監聽關閉!當前監聽數爲" + getOnlineCount()); 67 } 68 69 /** 70 * 這個方法與上面幾個方法不同。沒有用註解,是根據本身須要添加的方法。 71 * 72 * @param message 73 * @throws IOException 74 */ 75 public void sendMessage(String message) throws IOException { 76 this.session.getBasicRemote().sendText(message); 77 } 78 79 80 public static synchronized int getOnlineCount() { 81 return onlineCount; 82 } 83 84 public static synchronized void addOnlineCount() { 85 Listener.onlineCount++; 86 } 87 88 public static synchronized void subOnlineCount() { 89 Listener.onlineCount--; 90 } 91 92 }
代碼使用註解的方式,不須要進行配置,使用類變量記錄須要監聽的對象,實現分組管理不一樣的監聽
step2 增長一個用於通知監聽的websocket(也能夠用一個servlet)
package com.zc.websocket; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import com.alibaba.fastjson.JSONObject; @ServerEndpoint("/notifier") public class Notifier { /** * 收到客戶端消息後調用的方法 * * @param message * 客戶端發送過來的消息 * @param session * 可選的參數 */ @OnMessage public void onMessage(String message, Session session) { System.out.print("有新的通知:"+message); JSONObject values = JSONObject.parseObject(message); CopyOnWriteArraySet<Listener> websocketSet = Listener.getwebSocketSet(values.getString("fEventID")); if(websocketSet==null){ System.out.println(" 監聽數量:0"); return; }else{ System.out.println(" 監聽數量:"+websocketSet.size()); } // 羣發消息 for (Listener item : websocketSet) { try { item.sendMessage(values.getString("message")); } catch (IOException e) { e.printStackTrace(); continue; } } } }
3.2 前端調用
step1 定義一個js工具
define(function(require) { var Model = function() { this.callParent(); }; var URI = "wss://zcit2018.cn"; // var URI = "ws://baxd.ys100.com:8999"; var err = { "type":"鏈接失敗", "URI":URI, "address":"/UI2/B/M000_Core/process/Utils/WebSocketUtils.js" }; /** * 註冊一個fEventID的消息接收監聽 * @param fEventID 監聽標識 * @params callback 回調函數,帶參數 * @returns websocket對象 */ Model.setListener = function(fEventID,callback){ if(!fEventID){ throw "fEventID 不能爲空!"; } var websocket = null; //判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { try{ websocket = new WebSocket(URI+"/ZCServer/listener?fEventID="+fEventID); }catch(e){ console.log(err); return false; } } else { console.log('當前瀏覽器 Not support websocket'); return false; } //鏈接發生錯誤的回調方法 websocket.onerror = function() { console.log(err); }; //鏈接成功創建的回調方法 websocket.onopen = function() {}; //接收到消息的回調方法 websocket.onmessage = function(event) { if(callback&& typeof callback =="function"){ callback(event); } }; //鏈接關閉的回調方法 websocket.onclose = function() {}; return websocket; }; /** * 給fEventIDs發送通知message * @params fEventIDs 監聽者IDs數組 * @params message 消息內容 * */ Model.sendNotic = function(fEventIDs,message){ if(!Array.isArray(fEventIDs)){ throw "fEventIDs 必須爲數組!"; } var websocket = null; //判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { try{ websocket = new WebSocket(URI+"/ZCServer/notifier"); }catch(e){ console.log(err); return false; } } else { console.log('當前瀏覽器 Not support websocket'); return false; } websocket.onopen = function() { for ( var i in fEventIDs) { websocket.send(JSON.stringify({ "fEventID" : fEventIDs[i], "message" : message?message:"" })); } websocket.close(); }; }; return Model; });
增長監聽器的代碼(引入工具js就不描述了)
1 websocket.setListener("10e0495c-650c-486a-8d65-552f103aa243",function(re){ 2 // 處理邏輯 3 });
增長通知的代碼
websocket.sendNotic(['10e0495c-650c-486a-8d65-552f103aa243'],message);
一個處理可能須要引發多個監聽器的響應,因此參數使用的數組
整個websocket基本就是這樣,可是有時候進行通知的未必是經過前端操做的,因此還須要提供一個java工具用於通知