WebSocket整理心得,前后端實現(xiàn) 及遠(yuǎn)端服務(wù)端實現(xiàn)

HTTP 協(xié)議有一個缺陷:通信只能由客戶端發(fā)起。只能是客戶端向服務(wù)器發(fā)出請求,服務(wù)器返回查詢結(jié)果。HTTP 協(xié)議做不到服務(wù)器主動向客戶端推送信息。如果服務(wù)器有連續(xù)的狀態(tài)變化,客戶端要獲知就非常麻煩。我們只能使用"輪詢"(js-定時器,定時發(fā)送請求驗證),每隔一段時候,就發(fā)出一個詢問,了解服務(wù)器有沒有新的信息。最典型的場景就是聊天室。

WebSocket:
它的最大特點就是,服務(wù)器可以主動向客戶端推送信息,客戶端也可以主動向服務(wù)器發(fā)送信息,是真正的雙向平等對話,屬于[服務(wù)器推送技術(shù)]的一種。(前提是保持連接的狀態(tài)?。?br> (1)建立在 TCP 協(xié)議之上,服務(wù)器端的實現(xiàn)比較容易。
(2)與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443,并且握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務(wù)器。
(3)數(shù)據(jù)格式比較輕量,性能開銷小,通信高效。
(4)可以發(fā)送文本,也可以發(fā)送二進(jìn)制數(shù)據(jù)。
(5)沒有同源限制,客戶端可以與任意服務(wù)器通信。
(6)協(xié)議標(biāo)識符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL。

實現(xiàn)單一客戶端連接:頁面?zhèn)鬟f過來用戶的標(biāo)識,在onMessage()中判定用戶實現(xiàn)單一通訊。

ws://example.com:80/some/path
js部分

 <!DOCTYPE html>
 <html>
 <head>
     <title>Java后端WebSocket的Tomcat實現(xiàn)</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">     
 </head>
 <body>
     Welcome<br/><input id="text" type="text"/>
     <button onclick="send()">發(fā)送消息</button>
     <hr/>
     <button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button>
     <hr/>
     <div id="message"></div>
 </body>
 
 <script type="text/javascript">
     var uid = "admin";
     var websocket = null;
     //判斷當(dāng)前瀏覽器是否支持WebSocket
     if ('WebSocket' in window) {
         websocket = new WebSocket("ws://localhost:8080/WebSocketTest/websocket/"+uid);
     }
     else {
         alert('當(dāng)前瀏覽器 Not support websocket')
     }
 
     //連接發(fā)生錯誤的回調(diào)方法
     websocket.onerror = function () {
         setMessageInnerHTML("WebSocket連接發(fā)生錯誤");
     };
 
     //連接成功建立的回調(diào)方法
     websocket.onopen = function () {
         setMessageInnerHTML("WebSocket連接成功");
     }
 
     //接收到消息的回調(diào)方法
     websocket.onmessage = function (event) {
         setMessageInnerHTML(event.data);
     }
 
     //連接關(guān)閉的回調(diào)方法
     websocket.onclose = function () {
         setMessageInnerHTML("WebSocket連接關(guān)閉");
     }
 
     //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。
     window.onbeforeunload = function () {
         closeWebSocket();
     }
 
     //將消息顯示在網(wǎng)頁上
     function setMessageInnerHTML(innerHTML) {
         document.getElementById('message').innerHTML += innerHTML + '<br/>';
     }
 
     //關(guān)閉WebSocket連接
     function closeWebSocket() {
         websocket.close();
     }
 
     //發(fā)送消息
     function send() {
         var message = document.getElementById('text').value;
         websocket.send(message);
         
     }
 </script>
 </html>
java部分
package test;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 注解是一個類層次的注解,它的功能主要是將目前的類定義成一個websocket服務(wù)器端,
 *                 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務(wù)器端
 */
@ServerEndpoint(value = "/websocket/{param}")//{}中的數(shù)據(jù)代表一個參數(shù),多個參數(shù)用/分隔
public class WebSocketTest {
    
    private String uname;
    //
    // 靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計成線程安全的。
    private static int onlineCount = 0;
    // concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的MyWebSocket對象。若要實現(xiàn)服務(wù)端與單一客戶端通信的話,可以使用Map來存放,其中Key可以為用戶標(biāo)識
    private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
    // 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
    private Session session;
    /**
     * 連接建立成功調(diào)用的方法
     * 
     * @param session
     * 可選的參數(shù)。session為與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
     */
    @OnOpen
    public void onOpen(@PathParam(value = "param") String uid, Session session) {
        this.session = session;
        this.uname = uid;
        System.out.println(uid);
        webSocketSet.add(this); // 加入set中
        addOnlineCount(); // 在線數(shù)加1
        System.out.println("有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCount());
    }
    /**
     * 連接關(guān)閉調(diào)用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this); // 從set中刪除
        subOnlineCount(); // 在線數(shù)減1
        System.out.println("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
    }
    /**
     * 收到客戶端消息后調(diào)用的方法
     * 
     * @param message
     *            客戶端發(fā)送過來的消息
     * @param session
     *            可選的參數(shù)
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        // 群發(fā)消息
        for (WebSocketTest item : webSocketSet) {
            try {
                if (item.uname.equals(this.uname)) {
                    item.sendMessage(item.uname + ":" + message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }
    /**
     * 發(fā)生錯誤時調(diào)用
     * 
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("發(fā)生錯誤");
        error.printStackTrace();
    }

    /**
     * 這個方法與上面幾個方法不一樣。沒有用注解,是根據(jù)自己需要添加的方法。
     * 
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        // this.session.getAsyncRemote().sendText(message);
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        WebSocketTest.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        WebSocketTest.onlineCount--;
    }

}

以上代碼來自網(wǎng)絡(luò)??梢詫崿F(xiàn)前段和后端的相互通訊。

--------------------------------------------------------------分割線------------------------------------------------------------------------

而我在項目中,需要后臺與另一個服務(wù)端(非本服務(wù)端)建立連接,并相互通訊。本人初學(xué),總結(jié)和參考了網(wǎng)上各位前輩的方法,實現(xiàn)如下:

后臺代碼:

    

    public String getAccess(String access) {
        System.out.println("dao:"+access);
         Session session = null; 
         WebSocketContainer container = ContainerProvider.getWebSocketContainer();  
         String uri = "ws://localhost:8095//testWebsocket/websocket/userSever/8080"; 
         try {
            session = container.connectToServer(MyClient.class, URI.create(uri));
        } catch (DeploymentException e) {           
            e.printStackTrace();
        } catch (IOException e) {           
            e.printStackTrace();
        }  
         /**發(fā)送頁面?zhèn)鬟^來的數(shù)據(jù)*/
         try {
            session.getBasicRemote().sendText(access);
        } catch (IOException e) {       
            e.printStackTrace();
        }
        String acc = "";
        while(true) {
            acc = MyCache.catche2.get("a1");
            if(acc!=null) {
                return acc;             
            }
             try {
                Thread.sleep(500);
            } catch (InterruptedException e) {              
                e.printStackTrace();
            }
        }
        
        
    }
}
@ClientEndpoint  
public class MyClient {  
    @OnOpen  
    public void onOpen(Session session) {  
        System.out.println("Connected to endpoint: " + session.getBasicRemote());  
        try {  
            session.getBasicRemote().sendText("Hello");  
        } catch (IOException ex) {  
        }  
    }  

    @OnMessage  
    public void onMessage(String message) {  
        System.out.println(message);  
        MyCache.catche2.put("a1", message);
    }  

    @OnError  
    public void onError(Throwable t) {  
        t.printStackTrace();  
    }  
}

遠(yuǎn)端服務(wù)端:

/**在遠(yuǎn)端服務(wù)器上建立websocket服務(wù)器*/
@ServerEndpoint(value = "/websocket/{relationId}/{userCode}")
public class UserWebSocketMain {
    /**
     * 打開連接時觸發(fā)
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("relationId") String relationId,
                       @PathParam("userCode") int userCode,
                       Session session){
        System.out.println("連接成功"+relationId+":"+userCode);  
        SessionUtils.put(relationId, userCode, session);
    }

    /**
     * 收到客戶端消息時觸發(fā)
     * @param relationId
     * @param userCode
     * @param message
     * @return
     */
    @OnMessage
    public String onMessage(@PathParam("relationId") String relationId,
                            @PathParam("userCode") int userCode,
                            String message) {
        try {           
                sendMessage("userSever", 8080, message);
        } catch (IOException e) {           
            e.printStackTrace();
        }
        return "Got your message (" + message + ").Thanks !";
        
    }

    /**
     * 異常時觸發(fā)
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnError
    public void onError(@PathParam("relationId") String relationId,
                        @PathParam("userCode") int userCode,
                        Throwable throwable,
                        Session session) {
        //SessionUtils.remove(relationId, userCode);
        throwable.printStackTrace();
    }

    /**
     * 關(guān)閉連接時觸發(fā)
     * @param relationId
     * @param userCode
     * @param session
     */
    @OnClose
    public void onClose(@PathParam("relationId") String relationId,
                        @PathParam("userCode") int userCode,
                        Session session) {       
        SessionUtils.remove(relationId, userCode);
    }

    public void sendMessage( String relationId,int userCode,String message) throws IOException {
        Session session = SessionUtils.get(relationId, userCode);
        System.out.println(session);
        if(session!=null) {
            session.getBasicRemote().sendText(message);
        }else {
            
        }
        
        // this.session.getAsyncRemote().sendText(message);
    }
    
}
/**管理連接*/
public class SessionUtils {

    public static Map<String, Session> clients = new ConcurrentHashMap<>();
    private static final AtomicInteger connectionIds = new AtomicInteger();
    

    public static void put(String relationId, int userCode, Session session){
        System.out.println("保存會話"+relationId+":"+userCode);  
        
        clients.put(getKey(relationId, userCode), session);
    }

    public static Session get(String relationId, int userCode){
        System.out.println("取出會話"+relationId+":"+userCode);  
        return clients.get(getKey(relationId, userCode));
    }

    public static void remove(String relationId, int userCode){
        System.out.println("關(guān)閉連接"+relationId+":"+userCode);  
        clients.remove(getKey(relationId, userCode));
    }

    /**
     * 判斷是否有連接
     * @param relationId
     * @param userCode
     * @return
     */
    public static boolean hasConnection(String relationId, int userCode) {
        System.out.println("判斷是否有會話:"+relationId+":"+userCode);  
        return clients.containsKey(getKey(relationId, userCode));
    }

    /**
     * 組裝唯一識別的key
     * @param relationId
     * @param userCode
     * @return
     */
    public static String getKey(String relationId, int userCode) {
        System.out.println("組裝會話唯一標(biāo)識"+relationId+":"+userCode);  
        return relationId + "_" + userCode;
    }
}

將兩邊服務(wù)端啟動,調(diào)用后臺的getAccess()方法就可以建立連接,因為想在getAccess()中直接拿到響應(yīng)的數(shù)據(jù),所以在MyClient 類中做了一個全局Map(MyCache.catche2),當(dāng)消息傳回調(diào)用MyClient 的onMessage()方法時將數(shù)據(jù)存入map,再在getAccess()中用一個輪詢得到map的數(shù)據(jù)(方法很笨,只是不知道還有什么其他方法,見諒!?。。?/p>

以上可實現(xiàn)遠(yuǎn)端服務(wù)端連接,本人java小白剛參加工作,寫此心得希望各位大神指正,對于獲取即時數(shù)據(jù)這一塊,我用了map,不知還有其他方法可以實現(xiàn),跪求?。。。。。?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢敢當(dāng)閱讀 9,037評論 0 50
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評論 19 139
  • 提示:由于產(chǎn)品、預(yù)算、目的不同,所以每個賣家都對于廣告有不同的看法。以下分享內(nèi)容僅針對節(jié)日季和跨年廣告的組合形式以...
    SodaG閱讀 271評論 0 0
  • 女兒是一個生活熱情很高的孩子,特別是對長輩,對師長更是尊敬。剛看到老師讓寫這個孝的事情的時候,還在想平時基...
    洛水河畔閱讀 435評論 0 1

友情鏈接更多精彩內(nèi)容