Netty-SocketIO实现服务器消息推送

传统的Http是基于请求-响应式的协议,需要客户端主动向用户发送请求,才能得到服务器的响应,而在请求同步响应结束后,Http也会关闭,此时服务器便不能再向客户端主动发送消息了。即若客户端想得到服务端的消息,就必须首先发送请求才能得到消息回复。
在有些场景下,如股票价格实时显示、直播、在线聊天等场景,则需要服务器主动向客户端推送消息,显然Http协议并不太适合完全这项工作,而Netty-SocketIO是基于Netty框架下用Java实现Socket通信的组件,可用于服务器主动推送消息到客户端的情形。
文章基于Netty-Socket实现Java后台+socket.io.js前端实现服务器消息推送。

后台服务器部分

  • 引入netty-socketio,在maven中引入jar包,注意版本,笔者刚开始尝试1.6.x的版本,不能正常使用,换成1.7.7版本之后就好了。
<!-- netty socketio -->
    <dependency>
        <groupId>com.corundumstudio.socketio</groupId>
        <artifactId>netty-socketio</artifactId>
        <version>1.7.7</version>
    </dependency>
  • Socket服务器代码,socket服务地址端口设为本机localhost:8089,并在spring Bean加载的时候就开启服务。socket服务需要添加监听事项,本文用spring注入listeners
@Service
public class SocketService implements InitializingBean{

    @Autowired
    private EventListennter listeners;

    public void startServer() {
        Configuration config = new Configuration();
        config.setHostname("localhost");
        config.setPort(8089);

        SocketIOServer server = new SocketIOServer(config);
        server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
            @Override
            public void onConnect(SocketIOClient client) {
                System.err.println(client.getRemoteAddress() + " web客户端接入");
            }
        });

        server.addListeners(listeners);
        server.start();

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("start socket");
        this.startServer();
    }
}
  • 监听器:添加监听事项event
@Component
public class EventListennter {

    //维护每个客户端的SocketIOClient
    private Map<String, List<SocketIOClient>> clients = new ConcurrentHashMap<>();

    @OnConnect
    public void onConnect(SocketIOClient client) {
        System.err.println("建立连接");
    }

    @OnEvent("token")
    public void onToken(SocketIOClient client, SocketIOMessage message) {
        List<SocketIOClient> socketList = clients.get(message.getToken());
        if (null == socketList || socketList.isEmpty()) {
            List<SocketIOClient> list = new ArrayList<>();
            list.add(client);
            clients.put(message.getToken(), list);
        }
        System.err.println("get token Message is " + message.getToken());
    }

    /**
     * 新事务
     * @param client 客户端
     * @param message 消息
     */
    @OnEvent("newAlert")
    public void onAlert(SocketIOClient client, SocketIOMessage message) {
        //send to all users
        Collection<List<SocketIOClient>> clientsList = clients.values();
        for (List<SocketIOClient> list : clientsList) {
            for (SocketIOClient socketIOClient : list) {
                socketIOClient.sendEvent("newAlert", message);
            }
        }
    }

    /**
     * 通知所有在线客户端
     */
    public void sendAllUser() {
        Set<Entry<String,List<SocketIOClient>>> entrySet = clients.entrySet();
        for (Entry<String, List<SocketIOClient>> entry : entrySet) {
            String key = entry.getKey();
            List<SocketIOClient> value = entry.getValue();
            for (SocketIOClient socketIOClient : value) {
                SocketIOMessage message = new SocketIOMessage();
                message.setMessage("send All user Msg" + key);
                socketIOClient.sendEvent("newAlert", message);
            }
        }
    }

    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        System.err.println("关闭连接");
    }
}
  • 消息类封装
public class SocketIOMessage {

    private String token;

    private String message;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

}
  • Controller类,触发给所有客户端发送消息
@Controller
@RequestMapping("/server")
public class SocketController {

    @Autowired
    private EventListennter eventListennter;

    @RequestMapping("/send")
    public void sendMsg() {
        System.err.println("send Msg....");
        eventListennter.sendAllUser();
    }

}

前端部分实现

  • 引入jquery和socket.io.js
<html>
<head>
<meta charset="UTF-8">
<title>socket test</title>
<script src="./jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" src="./socket.io.js"></script>
</head>
<body>
<h1>Netty-socketio demo</h1>
    <br />
    <div id="console" class="well"></div>
    <form class="well form-inline" onsubmit="return false;">
        <input id="token" class="input-xlarge" type="text" placeholder="token . . " />
        <input id="to" class="input-xlarge" type="text" placeholder="to. . . " />
        <input id="content" class="input-xlarge" type="text" placeholder="content. . . " />
        <button type="button" onClick="sendMessage()" class="btn">Send</button>
        <button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
    </form>
<script type="text/javascript">
    var socket = io.connect('http://localhost:8089');
    socket.on('connect',function() {
        alert("user connect");
        console.log("user connect");
    });

    socket.on('newAlert', function(data) {
        alert("receive alert");
        console.log("receive alert..." + data.message);
    });

    socket.on('disconnect',function() {
        alert("user disconnect");
        console.log("user disconnect");
    });

    function sendDisconnect() {
        socket.disconnect();
    }

    function sendMessage() {
        console.log("send message token");
        socket.emit('token', {
            token : $('#token').val(),
            message : 'message token'
        });
    }

</script>
</body>
</html>

运行结果

  • 浏览器打开3~4个页面连接
    客户连接
    客户连接4

  • 连接后,触发服务器给所有用户(客户端)发送消息,收到结果如下
    1
    2
    3

注意事项

  • netty-SocketIO中添加监听器,有这样一个方法addEventListener,这个方法的第3个传参是一个接口DataListener,如下采用匿名类实现,DataListener需要实现onData(SocketIOClient client, SocketIOMessage data, AckRequest ackSender) 其中的AckRequest可以用来同步返回给客户端(其实这个类是封装了SocketIOClient对象),其中AckRequest.sendAckData(Ojbect obj)底层是调用了SocketIOClient的send方法,所以其本质与SocketIOClient是一样的。
server.addEventListener("test", SocketIOMessage.class, new DataListener<SocketIOMessage>() {

            @Override
            public void onData(SocketIOClient client, SocketIOMessage data,
                    AckRequest ackSender) throws Exception {
                System.err.println("receive from web " + data.toString());
                SocketIOMessage send = new SocketIOMessage();
                send.setMessage("test Ack");
                send.setToken("server token");
                ackSender.sendAckData(send);
            }

        });
  • SocketIOClientsendEvent方法在socket.io.js客户端可用socket.on('event', function(){....})接收处理,那么AckRequest方法发送的消息怎么接收呢?
    其实,socket.io.js客户端的emit函数可以传3个参数,最后1个参数便回调处理AckRequest返回的同步消息。
function sendTest() {
        console.log("send test...");
        socket.emit('test', {
            token : $('#content').val(),
            message: 'test ackData'
        }, 
        function(data) {  //处理AckRequest返回的消息
            console.log(data.message);
        });
    }

猜你喜欢

转载自blog.csdn.net/ljyhust/article/details/77620620