分享

Netty-SocketIO:最好用的Java版即时消息推送

 KyunraWang 2018-08-06

Netty-SocketIO是一个开源的、基于Netty的、Java版的即时消息推送项目。通过Netty-SocketIO,我们可以轻松的实现服务端主动向客户端推送消息的场景,比如说股票价格变化、K线图、消息提醒等。它和websocket有相同的作用,只不过Netty-SocketIO可支持所有的浏览器。

GitHub项目地址为: https://github.com/mrniko/netty-socketio

Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。Socket.IO实现的Polling通信机制包括Adobe Flash Socket、AJAX长轮询、AJAX multipart streaming、持久Iframe、JSONP轮询等。Socket.IO能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。

一、下载Netty-SocketIO

  1. socket.io-client-master
  2. netty-socketio-master
  3. netty-socketio-demo-master

从netty-socketio的git上可下载到以上三个压缩包,分别对应的是web 客户端的所需文件、netty socketio的java服务端实现、以及对应的可以应用web推送的demo。

二、部署server的资源项目

从git上下载的socketio server压缩包中没有项目所需的jar包,我是自己新建了一个项目,本来上传到了CSDN的代码库里了,但写博客的时候怎么也打不开了,那么只能提供对应项目导航图,同时提供jar包的下载地址,需要的可动手去获得。

这里写图片描述

http:///网站上课下载到需要的所有jar包。

然后将jar包添加到项目中。 
切记jdk的版本一定要在1.7以上,我在本地环境中用的1.7,但是服务器上一直用的是1.6,部署项目的时候没有注意,导致服务端可以接收到客户端的socketio 的connect,但是客户端的response相应中却连接不通,开始的时候以为是跨域问题导致的,搜了好多帖子,但问题根本就是不是跨域引起的,jdk的版本换成1.7就ok了,因为netty的nio是基于java的1.7的。

三、应用server

在需要应用socketio的项目上右键,为项目添加socketio项目支持(注意红色标出的)。 
这里写图片描述

新建main类。

public static void main(String[] args) throws InterruptedException {

    Configuration config = new Configuration();
    config.setHostname("localhost");
    config.setPort(10015);
    SocketIOServer server = new SocketIOServer(config);
    server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
        @Override
        public void onConnect(SocketIOClient client) {
            logger.info(client.getRemoteAddress() + " web客户端接入");
            client.sendEvent("helloPush", "hello");
        }
    });
                    // 握手请求
    server.addEventListener("helloevent", HelloUid.class, new DataListener<HelloUid>() {
        @Override
        public void onData(final SocketIOClient client, HelloUid data, AckRequest ackRequest) {
            // 握手
            if (data.getMessage().equals("hello")) {
                int userid = data.getUid();
                logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);


                // send message back to client with ack callback
                // WITH data
                client.sendEvent("hellopush", new AckCallback<String>(String.class) {
                    @Override
                    public void onSuccess(String result) {
                        logger.info("ack from client: " + client.getSessionId() + " data: " + result);
                    }
                }, sessionTime);

            } else {
                logger.info("行情接收到了不应该有的web客户端请求1111...");
            }
        }
    });

        server.start();

        Thread.sleep(Integer.MAX_VALUE);

        server.stop();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

当client通过ip和端口连接到server后,会构造一个SocketIOClient client的对象,在实际的应用中,可以将该client保存起来,通过client.sendEvent("helloPush", "hello");就可以向client端发送相应的数据了。

四、应用client

①、引入socket.io.js

<script type="text/javascript" src="${ctx}/components/socketio/socket.io.js"></script>
  • 1

②、创建connection的公共方法

function connectQuotation(uid, callback) {
    // 链接行情server
    socket = io.connect('http://localhost:10015');

    // 如果用户在web端登陆,那么发送握手请求
    if (uid) {
        // 连接上server后
        socket.on('connect', function() {
            // 发送握手请求
            var jsonObject = {
                uid : parseInt(uid),
                message : "hello"
            };
            this.emit('helloevent', jsonObject);

            this.on('hellopush', function(data, ackServerCallback, arg1) {
                // base64转码的数据,可忽视
                YUNM.session = {
                    sessionId : $.base64.atob(data.sessionId),
                    time : $.base64.atob(data.time)
                };
                if (ackServerCallback) {
                    ackServerCallback('server message was delivered to client!');
                }
            });
        });
    }

    // 如果web端session超时,socket断开,10分钟扫描一次
    int = window.setInterval(function() {
        // 我是通过ajax判断session超时的,你也可以通过其他方式
        $.ajax({
            type : 'POST',
            url : common.ctx + "/getSessionTimeout",
            dataType : "json",
            cache : false,
            success : function(json) {
                var timeout = parseInt(json.message);
                // session超时后,socket断开,服务端就可以监听到释放资源
                if (timeout == 0) {
                    socket.disconnect();
                }
            },
            error : function() {
                socket.disconnect();
                // 清除
                window.clearInterval(int);
            }

        });
    }, YUNM._set.interval);

    callback();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

③、需要web推送的页面进行socketio的连接

$(function() {
connectQuotation($("#global_uid").val(), function() {
    socket.on("pushQuotation", function(message) {

        if (message.type == "dealOrder") {
            var msg = message.response.result;
            // 输出服务端消息
            YUNM.debug(msg);
        }
    });
});
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

五、注意事项

  1. 关于socketio的ssl应用,一直没有弄出来,主要是java的toolkey生成的jks总是不被浏览器识别,socketio提供的例子也不能运行,在各大网站上暂时没有解决问题。可通过浏览器添加https的例外做法来规避错误,但不可取,后面还需要研究。
  2. socketio在浏览器刷新后,旧的连接断开,需要重新建立连接,这个请注意。
  3. 客户端session超时后,切记关闭socket,我是通过10分钟一次的扫描进行的session超时验证,该方法不够理想。
  4. server端,在实际应用中,需要将客户端断开的socket进行垃圾清理,注意判断方法如下:
NamespaceClient client = (NamespaceClient) socket;

if (client.getBaseClient().isConnected()) {
    SessionManager.getSession(getSession().getSessionId());
} else {
    logger.warn("web发送心跳包失败,客户端连接线程[" + this.getName() + "]已断开");
    running = false;
    break;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

整体Netty-SocketIO的Web推送还是非常易于掌握的,但是缺少api,做起来需要不停实践,另外解决问题的方法不多,希望本篇可以给你提供些许帮助。


2017年12月26日补充:

项目迁移到了另外一台阿里云服务器,于是Netty-SocketIO的server在启动的时候出现了以下错误:

java.net.BindException: Cannot assign requested address: bind
    at sun.nio.ch.Net.bind0(Native Method)
    at sun.nio.ch.Net.bind(Unknown Source)
    at sun.nio.ch.Net.bind(Unknown Source)
    at sun.nio.ch.ServerSocketChannelImpl.bind(Unknown Source)
    at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:127)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

问题发生在server.start()处,进行javaChannel().bind(localAddress, config.getBacklog());域名和端口绑定时出现了错误:

private SocketIOServer server = null;
server = new SocketIOServer(NetConfigManager.getWebQuotationConfig());// 此处的Configuration对象的Hostname设置的是域名,Port设置为10015

server.start();
  • 1
  • 2
  • 3
  • 4

最开始从日志里面找出了这样一条消息:

socketio.SocketIOServer$1: SocketIO server start failed at port: 10015!

直觉告诉我,阿里云的安全组在作怪,把10015端口禁掉了。于是我就去改安全组,把10015的入方向和出方向都放开了,但问题依然没有解决。

接下来,我只能丈二和尚摸不着头脑的去百度,他娘的结果没什么可靠的信息,都是一些乱七八糟的东西——什么端口被占用,使用netstat -ano查询端口,还有什么hosts文件要修改,等等,一系列坑爹又不负责任的答案。

折腾了接近四个半小时的时间,我™几乎要疯了,脑子一股热血升腾,恨不得把鼠标给砸了。

在气晕的状态下,我努力的控制着自己,问:“明明上一台阿里服务器的时候,这样配置,socketio就是正常的,现在怎么™就error了?”

试着让自己恢复冷静,就能想到办法。“不是端口的问题,那肯定是域名的问题!”

我试了试“localhost”,发现SocketIOServer竟然start成功了!????这说明什么问题,说明域名没有localhost的权限!但改成localhost有毛用,客户端不可能访问到服务器的localhost啊?

接着,我想到了IP地址!!!!!把域名换成公网的IP地址,竟然™的SocketIOServer start成功了!!!!

此时有两种解决方案,一是使用IP来替代域名,另外一个就是继续找域名的解决方案——一个不懂网络的程序员注定在网络安全方面吃点亏!,我™就是受害者之一。

好了,问题记录就先到这里——解决问题的最好办法就是让自己冷静下来!!!!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多