前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >resin4.0.44+websocket 实现私信功能服务端消息推送

resin4.0.44+websocket 实现私信功能服务端消息推送

作者头像
哎_小羊
发布2018-01-02 15:10:19
1.8K0
发布2018-01-02 15:10:19
举报
文章被收录于专栏:哎_小羊

最近项目开发中,碰到一个新的开发需求——私信功能。

项目要求:类似微博中发送私信功能,给对方发送一条私信消息,如果对方在线就立马接受到消息提示,并显示到页面上。如果对方不在线,则下次登录以后,显示消息提示。

技术选择:websocket也是目前比较流行的接收服务器端消息的一门HTML5技术,我们服务器采用的是resin4.0+,所以综合考虑采用基于resin的websocket形式实现该功能。

软件版本:resin4.0.44、websocket、SpringMVC、redis 这里着重强调下,项目架构是SpringMVC结构,这里就不在赘述Spring相关的配置,主要介绍下resin下的websocket如何实现消息推送。 第一步: 新建websocket数据封装Bean,用来保存websocket+user对应信息。

代码语言:javascript
复制
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请求暂存起来。

代码语言:javascript
复制
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封装过一次的,我们只需实现它,然后进行自己的业务逻辑处理即可。

代码语言:javascript
复制
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代码)

代码语言:javascript
复制
//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下跑。。。)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-10-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档