1. 程式人生 > >resin4.0.44+websocket 實現私信功能服務端訊息推送

resin4.0.44+websocket 實現私信功能服務端訊息推送

最近專案開發中,碰到一個新的開發需求——私信功能。

專案要求:類似微博中傳送私信功能,給對方傳送一條私信訊息,如果對方線上就立馬接受到訊息提示,並顯示到頁面上。如果對方不線上,則下次登入以後,顯示訊息提示。

技術選擇:websocket也是目前比較流行的接收伺服器端訊息的一門HTML5技術,我們伺服器採用的是resin4.0+,所以綜合考慮採用基於resin的websocket形式實現該功能。

軟體版本:resin4.0.44、websocket、SpringMVC、redis
這裡著重強調下,專案架構是SpringMVC結構,這裡就不在贅述Spring相關的配置,主要介紹下resin下的websocket如何實現訊息推送。
第一步:
新建websocket資料封裝Bean,用來儲存websocket+user對應資訊。

package com.gochina.tc.websocket;

import com.caucho.websocket.WebSocketContext;

/**
 * websocket封裝bean
 * @author hwy
 *
 */
public class WebSocketBean {

    private String userCode;//使用者的code
    private int hashCode;//websocket的hashCode
    private WebSocketContext webSocketContext;//websocketContext
public WebSocketBean(String userCode, int hashCode, WebSocketContext webSocketContext) { super(); this.userCode = userCode; this.hashCode = hashCode; this.webSocketContext = webSocketContext; } public String getUserCode() { return userCode; } public
void setUserCode(String userCode) { this.userCode = userCode; } public int getHashCode() { return hashCode; } public void setHashCode(int hashCode) { this.hashCode = hashCode; } public WebSocketContext getWebSocketContext() { return webSocketContext; } public void setWebSocketContext(WebSocketContext webSocketContext) { this.webSocketContext = webSocketContext; } }

第二步:
新建MyWebSocketServlet,用來連線前端websocket與下邊的listener建立關係的入口統一配置,將所有接受到的使用者的websocket請求暫存起來。

package com.gochina.tc.websocket;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.caucho.websocket.WebSocketContext;
import com.caucho.websocket.WebSocketListener;
import com.caucho.websocket.WebSocketServletRequest;

@Controller
@RequestMapping(value = "/websocket")   
@SuppressWarnings("serial")
public class MyWebSocketServlet extends HttpServlet{

    private static List<WebSocketBean> socketList = new ArrayList<WebSocketBean>();

    @RequestMapping(value = "{userCode}")
    public void service(HttpServletRequest req,HttpServletResponse res,@PathVariable("userCode") String userCode) 
            throws IOException, ServletException{
       //當呼叫了下面的startWebSocket函式後,該socket就會和相應的listener建立起對應關係
        WebSocketListener listener = new MyWebSocketListener();

        WebSocketServletRequest wsReq = (WebSocketServletRequest) req;
        WebSocketContext webSocketContext = wsReq.startWebSocket(listener);

        WebSocketBean webSocketBean = new WebSocketBean(userCode, webSocketContext.hashCode(), webSocketContext);
        socketList.add(webSocketBean);
    }

    /**
     * 獲取連線的websocket列表
     * @return
     */
    public static List<WebSocketBean> getSockList(){
        return socketList;
    }
}

注意:這裡的@RequestMapping(value = “{userCode}”)中的userCode是前端建立連線時,傳過來的使用者的唯一標示,根據這個標示確定是哪位使用者發起的websocket請求。
第三步:
新建MyWebSocketListener,這個是用來監聽websocket整個生命週期,包含啟動、關閉、失去連線等等一系列操作。WebSocketListener是resin封裝過一次的,我們只需實現它,然後進行自己的業務邏輯處理即可。

package com.gochina.tc.websocket;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.caucho.websocket.WebSocketContext;
import com.caucho.websocket.WebSocketListener;
import com.gochina.tc.util.redis.RedisUtil;

/**
 * websocket訊息監聽
 * @author hwy
 *
 */
public class MyWebSocketListener implements WebSocketListener{

    private Logger log = LoggerFactory.getLogger(MyWebSocketListener.class);

    /**
     * 移除websocket
     * @param webSocketContext
     */
    public void remove(WebSocketContext webSocketContext){
        List<WebSocketBean> socketList = MyWebSocketServlet.getSockList();
        WebSocketBean webSocketBean = null;
        for(WebSocketBean socket:socketList){
            if(socket.getHashCode() == webSocketContext.hashCode()){
                webSocketBean = socket;
                break;
            }
        }
        if(webSocketBean != null){
            socketList.remove(webSocketBean);
        }
    }

    /**
     * 關閉連線
     */
    @Override
    public void onClose(WebSocketContext webSocketContext) throws IOException {
         remove(webSocketContext);
         log.info(webSocketContext.hashCode()+" is closed");
    }

    /**
     * 斷開連線
     */
    @Override
    public void onDisconnect(WebSocketContext webSocketContext) throws IOException {
         remove(webSocketContext);
         log.info(webSocketContext.hashCode()+" is disconnect");
    }

    /**
     * 接收二進位制訊息
     */
    @Override
    public void onReadBinary(WebSocketContext arg0, InputStream arg1)
            throws IOException {

    }

    /**
     * 接收文字訊息併發送訊息
     */
    @Override
    public void onReadText(WebSocketContext webSocketContext, Reader reader)
            throws IOException {
        PrintWriter out = null;
        int ch;
        String text = "";
        while ((ch = reader.read()) >= 0) {
            text = text+(char)ch;
        }
        int hashCode = webSocketContext.hashCode();

        List<WebSocketBean> socketList = MyWebSocketServlet.getSockList();
        for(WebSocketBean socket:socketList){
            try{
                if(socket.getHashCode() == hashCode){
                    int count = 0;
                    Object o = RedisUtil.get("tc_user_message_"+socket.getUserCode());//從redis中獲取訊息數目
                    if(o != null){
                        count = Integer.parseInt(o+"");
                    }
                    out = socket.getWebSocketContext().startTextMessage();
                    out.print(count);
                    out.close();
                    break;
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                if(out != null){
                    out.close();
                }
                if(reader != null){
                    reader.close();
                }
            }
        }

    }

    /**
     * 開始連線
     */
    @Override
    public void onStart(WebSocketContext webSocketContext) throws IOException {
        webSocketContext.setTimeout(43200000);//設定連線關閉時間
        log.info(webSocketContext.hashCode()+" is start");
    }

    /**
     * 連線超時
     */
    @Override
    public void onTimeout(WebSocketContext webSocketContext) throws IOException {
         remove(webSocketContext);
         log.info(webSocketContext.hashCode()+" is timeOut");
    }

     /**
     * 給所有線上使用者傳送訊息
     * @param text
     */
    public void sendToOnlingUsers(String text){
        List<WebSocketBean> socketList = MyWebSocketServlet.getSockList();
        PrintWriter out = null;
        for(WebSocketBean socket:socketList){
            try{
                out = socket.getWebSocketContext().startTextMessage();
                out.print(text);
                out.close();
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                if(out != null){
                    out.close();
                }
            }
        }
    }

    /**
     * 給某個使用者傳送訊息
     * @param text
     * @param userCode
     */
    public void sendToOneUser(String text,String userCode){
        List<WebSocketBean> socketList = MyWebSocketServlet.getSockList();
        int i = 0;
        PrintWriter out = null;
        for(WebSocketBean socket:socketList){
            String code = socket.getUserCode();
            if(code.equals(userCode)){
                try{
                    out = socket.getWebSocketContext().startTextMessage();
                    out.print(text);
                    out.close();
                }catch(Exception e){
                    e.printStackTrace();
                }finally{
                    if(out != null){
                        out.close();
                    }
                }
            }
        }
    }
}

注意:這裡面有幾個地方需要著重注意下。
首先,在onStart方法是websocket每次建立連線時會觸發,每次關閉連線和斷開連線的時候,會相應觸發onStop()和onDisconnect()方法,需要移除暫存的websocket物件。
其次,webSocketContext.setTimeout(43200000);//設定連線關閉時間,在onStart方法裡面有個設定連線關閉時間的方法,此處有坑。。。最初我的resin版本是4.0.44版本以下的,測試發現,每次連線在200s以後就會莫名的斷開,即使設定了setTimeOut()依舊200s自動關閉。而且官網例項中明確寫了在onStart()中setTimetOut()即可修改連線關閉時間的。。。百思不得姐的時候。google翻看resin更新日誌中無意間看到了這個 resin change log
Resin Change Log

Resin 3.1 changes
4.0.44 - in progress

jsee: self signed cert should support Firefox and Chrome default cipher-suites(#5884)
jsee: self signed cert should check expire (#5885)
class-loader: excessive reread of jar certificates (#5850, rep by konfetov)
log: add sanity check for log rollover (#5845, rep by Keith F.)
deploy (git): use utf-8 to store path names (#5874, rep by alpor9)
websocket: setTimeout was being overridden by Port keepaliveTimeout (#5841, rep by A. Durairaju)
jni: on windows, skip JNI for File metadata like length (#5865, rep by Mathias Lagerwall)
db: isNull issues with left join (#5853, rep by Thomas Rogan)
websocket: check for socket close on startTextMessage (#5837, rep by samadams)
log: when log rollover fails, log to stderr (#5855, rep by Rock)
filter: allow private instantiation (#5839, rep by V. Selvaggio)
rewrite: added SetRequestCharacterEncoding (#5862, rep by Yoon)
health: change health check timeout to critical instead of fatal to allow for development sleep (#5867)
alarm: timing issue with warnings and alarm extraction (#4854, rep by SHinomiya Nobuaki)
session: orphan deletion throttling needs faster retry time (rep by Thomas Rogan)
mod_caucho: slow PUT/POST uploads with Apache 2.4 (#5846, rep by Stegard)
好吧,果斷將resin版本升至最新版4.0.44。
第四步:
前臺js發起websocket請求。(只複製js程式碼)

//websocket獲取未讀訊息數量
 if (userId != null && userId != '') {
    var webSocket ;
    if(window.WebSocket) {//google & Firefox
        webSocket = new WebSocket('ws://127.0.0.1:8080/websocket/'+userId);
     }else if('MozWebSocket' in window) {
            webSocket = new WebSocket('ws://127.0.0.1:8080/websocket/'+userId);
     }else{//不支援websocket瀏覽器
        getApiData(API.unReadCountApi+"?userCode="+userId, findUnReadMessageCount);
     }
    if(webSocket){
        webSocket.onerror = function(event) {
            console.log("connection error: "+event);
        };

        webSocket.onopen = function(event) {
            console.log("connection established");
            webSocket.send('1');
        };

        //接受到訊息後,顯示到頁面
        webSocket.onmessage = function(event) {
            console.log("connection receive message: "+event.data);
            getUnReadMessageCount(event.data);
        };

        webSocket.onclose = function(event) {
            console.log("connection close");
            webSocket.close();
        };
    }
 }

這裡是前端js發起websocket請求程式碼斷,大致寫法都類似,只是注意一點的是 webSocket = new WebSocket(‘ws://127.0.0.1:8080/websocket/’+userId);裡面的請求地址寫法,測試時把127.0.0.1換成線上域名的時候,訊息接收不到,訊息地址不通,無奈換成了ip地址就可以接受到了。不知是我伺服器配置問題還是什麼問題,希望瞭解的同學告訴我一下,感激不盡。
還有一點就是在不支援websocket的瀏覽器的時候,可以使用ajax長輪詢獲取伺服器端訊息,網上有一個socket.js對websocket支援的比較好,包括對不支援的瀏覽器的相容問題,好像Spring4.0+已經支援socket.js了,有時間可以學習下,我這裡是偷懶了,放棄了對不支援websocket的瀏覽器(IE10一下),只是在開啟頁面的時候請求了一次。

經過上面的四步的配置,一個基於resin4.0+websocket實現服務端訊息推送的功能就實現了。

當然現在用的比較多的還是tomcat7.0+ 和websocket整合實現該功能,(弱弱吐槽下,別再resin下使用javaee7+webscoket的方式實現,走過的路就是流過的淚啊! 當初第一版後臺使用的是javaee7的websocket實現方式,在本地tomcat7+以上測試,沒有問題,由於過於自信,直接丟到線上resin伺服器下,然後悲劇就發生了。。。啟動正常,訪問直接導致伺服器CPU 300%,難忘的加班開始了。如果大家需要,我也可以寫一篇基於javaee7+websocket簡版實現服務端訊息推送功能(非整合式Spring4.0+那種),最後強調不要在resin下跑。。。不要在resin下跑。。。不要在resin下跑。。。)

相關推薦

resin4.0.44+websocket 實現私信功能服務訊息

最近專案開發中,碰到一個新的開發需求——私信功能。 專案要求:類似微博中傳送私信功能,給對方傳送一條私信訊息,如果對方線上就立馬接受到訊息提示,並顯示到頁面上。如果對方不線上,則下次登入以後,顯示訊息提示。 技術選擇:websocket也是目前比較流行的接收

使用spring boot +WebSocket實現(後臺主動)訊息

前言:使用此webscoket務必確保生產環境能相容/支援!使用此webscoket務必確保生產環境能相容/支援!使用此webscoket務必確保生產環境能相容/支援!主要是tomcat的相容與支援。有個需求:APP使用者產生某個操作,需要讓後臺管理系統部分人員感知(表現為一

spring boot 整合activemq 進行服務訊息(web頁面)

最近公司的專案裡有需要服務端向web端實時推送訊息的需求,網上搜索了一番,有前端頁面通過定時任務向後臺傳送ajax請求重新整理,有使用第三方提供的訊息服務(GoEasy),前者因為會有很多請求是無用的,容易加大伺服器負荷造成宕機,後者現在收費了(免費的也只能用一

webSocket-簡單的服務定時以及重連

本文章是對webSocket的學習,在使用webSocket進行客戶端-服務端的互動。 參考文章: Java基於Socket的簡單推送,在文章在服務端 輸入後回車 ,可進行對客戶端的資訊傳送,同時進行回饋。 以下為自行改進:服務端定時推送資訊到客戶端,可根據自行需要進行調整。

android通過xmpp實現伺服器到客戶功能

  最近專案中要做推送功能,除了自己知道的友盟推送外還不知道其它的實現方式,於是就上網百度了一下要實現推送的基本途徑,發現主要還有以下幾種方式。   1.客戶端建立一個socket,與伺服器端的serversocket連線,其實就是客戶端與伺服器一直保持連線,這個其實本質上

android客戶訊息功能實現方案

1.使用第三方推送平臺 如友盟、極光、百度等現成的訊息推送。好處:訊息及時、穩定,整合簡單。不需要自己搭建支援伺服器,但是可能涉及到收費、保密、服務質量、擴充套件等問題。 2、MQTT協議實現android推送 MQTT是一個輕

WebSocket實現前後訊息

環境 jdk8 tomcat7 谷歌瀏覽器和火狐瀏覽器(瀏覽器得支援websocket) 本文用webSocket建立一個簡單的聊天室,直接上程式碼。。。 websocket 用到jar包: <dependency> &l

Java後實現安卓/IOS移動訊息(百度雲

本文主要介紹Java伺服器端如何藉助第三方推送平臺(百度雲推送)推送給移動端訊息。 使用案例介紹: 根據客戶的需求,需要做一個類似淘寶訊息推送的功能,客戶下訂單、訂單付款、訂單商品已發貨,以及客戶完成評論,都需要以訊息推送提示的方式告知商家和賣家這麼一個功能,由於之前沒有實現過這方面的功

SpringBoot2.x服務主動SSE

講解SpringBoot2.x服務端主動推送Sever-Send-Events              1、localhost:8080/index.html     2、需要把

Asp.Net Core 2.1 中 利用SignalR 服務主動資料

最近正在學習 SignalR 在Asp.Net Core中的應用(資料推送等等。。。)。以下為個人學習時遇到問題的記錄和解決方法。對於Asp.Net Core我也是剛剛學習,所以不保證完全正確。如果有錯誤還請大家指正,多謝多謝!!!在完整版(傳統版)的 Asp.Net  程式

3、客戶服務訊息服務主動訊息

index.html <!-- 客戶端 --> <!DOCTYPE html> <html lang="en"> <head>

使用pushplus+python實現亞馬遜到貨訊息微信

 xbox series和ps5發售以來,國內黃牛價格一直居高不下。雖然海外amazon上ps5補貨很少而且基本撐不過一分鐘,但是xbox series系列明顯要好搶很多。 日亞、德亞的xbox series x/s都可以直郵中國大陸,所以我們只需要藉助指令碼,監控相關網頁的動態,在補貨的第一時刻通

OAuth2.0學習(3-1)服務實現

other cti info ase service packages artifacts ace ews 開源 http://oltu.apache.org/ 其他 http://www.oschina.net/project/tag/307/oauth?lang=19&

在WinCE 6.0系統下實現USB功能定製

USB的廣泛應用就不用多說了,相信目前的各個領域的嵌入式產品中,很少有不用USB的。USB是主從結構的,分為USB Host和USB Slave,從USB1.0,USB1.1到現在的USB2.0,基於USB2.0還有USB OTG,也就是同時支援Host和Slave裝置。目前最新的好像是USB3.0,剛開

在Spring Boot框架下使用WebSocket實現聊天功能

上一篇部落格我們介紹了在Spring Boot框架下使用WebSocket實現訊息推送,訊息推送是一對多,伺服器發訊息傳送給所有的瀏覽器,這次我們來看看如何使用WebSocket實現訊息的一對一發送,模擬的場景就是利用網頁來實現兩個人線上聊天。OK,那我們來看看這個要怎麼

SpringBoot整合WebSocket實現多個服務通訊

import com.test.www.socket.WebSocketServer; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.

【微信小程式遇到的坑】使用webSocket實現聊天功能 (支援圖片預覽 + 語音 + 檢視歷史聊天記錄 附完整程式碼)

在一般web服務中,大多使用短連線來向伺服器請求資源,與伺服器的互動頻率低,次數少。而在一些需要與伺服器互動頻繁,需要及時收到伺服器推送的場景,比如直播、聊天、多人實時遊戲,更適合使用 webSocket 進行通訊。 長連的生命週期介紹 webSocket的生命週期一共有

國標GBT28181協議,註冊功能服務與客戶實現程式碼

國標GBT28181協議的使用者註冊時候,需要使用者名稱密碼認證,其本質是使用 http digest的演算法, http digest演算法,在RFC2617 [HTTP Authentication: Basic and Digest Access Authentica

(二)websocket實現訊息之基於spring4.0實現

  1、新建springBoot專案,新增依賴        &n

CXF+Spring+Hibernate實現RESTful webservice服務實例

fast anti vax apach sql xsd txadvice component path 1.RESTful API接口定義 /* * Copyright 2016-2017 WitPool.org All Rights Reserved. * *