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);
        });
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章