Bootstrap

WebSocket通信,多客户端数据同时发送

WebSocket通信,多客户端数据同时发送

目标:当一个客户端改变了某项数据后,其他的客户端都可以通过 WebSocket方式 看到新的数据。
单浏览器推送

需要用到的pom坐标:

<dependency>
  	<groupId>javax.websocket</groupId>
  	<artifactId>javax.websocket-api</artifactId>
  	<version>1.0-rc5</version>
  	<scope>provided</scope>
</dependency> 
<dependency>
	<groupId>org.springframework</groupId>
  	<artifactId>spring-websocket</artifactId>
  	<version>4.3.16.RELEASE</version>
</dependency>

页面:

<div>
    <input type="button" value="发送数据" onclick="send('黎明')"/> <input type="button" value="关闭连接" onclick="onClose()"/>
    <div id="ht" style="font-size: 15px;"></div>
</div>

<script type="text/javascript">
    let webSocket;
    if ('WebSocket' in window) {
        webSocket = new WebSocket('ws://localhost:8088/你的项目名/coverSocket');
    } else if ('MozWebSocket' in window) {
        webSocket = new MozWebSocket('ws://localhost:8088/你的项目名/coverSocket');
    } else {
        console.log('此浏览器暂不支持webSocket');
    }
    if (webSocket != null) {
        webSocket.onopen = function (event) {
            onOpen(event)
        };
        webSocket.onclose = function () {
            onClose()
        };
        webSocket.onerror = function (event) {
            onError(event)
        };
        webSocket.onmessage = function (event) {
            onMessage(event)
        };
        //监听窗口关闭
        window.onbeforeunload = function () {
            webSocket.close();
        };
    }

    function onOpen(event) {
        console.info("WebSocket连接打开" + event);
    }

    function onClose() {
      	webSocket.close();
        console.info("WebSocket连接关闭");
    }

    function onError(event) {
        console.info("WebSocket发生错误" + event);
    }

    //接收数据
    function onMessage(event) {
        $('#ht').append('<br>' + event.data)
    }

    //发送数据
    function send(st) {
        return webSocket.send(st);
    }
</script>

java类:

import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.SpringConfigurator;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

//configurator = SpringConfigurator.class 通过这个类,该WebSocket中就可以注入Spring容器的bean。
@ServerEndpoint(value = "/coverSocket", configurator = SpringConfigurator.class)
public class WebSocketServer {

	//你注入的BeanService
	@Autowired
    private BeanService beanService;

    @OnOpen
    public void onOpen(Session session) {
        String id = session.getId();
        System.err.println("通道打开"+id);
    }
    @OnClose
    public void onClose(Session session) {
        String id = session.getId();
        System.err.println("通道关闭"+id);
    }
    @OnError
    public void onError(Session session,Throwable error){
        System.err.println("WebSocket发生错误");
        error.printStackTrace();
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException, InterruptedException {
    	//你业务层操作的方法
    	MyBean bean = beanService.getByUniqueKey("xx","xx", MyBean .class);
        session.getBasicRemote().sendText("欢迎您:"+bean.toString());
    }
}
  • 这种是最基本的从库里查数据然后推送给一个浏览器,就不解释了。
  • 如果要实现从一个浏览器中点击按钮,给多个浏览器推送数据,怎么实现呢?
  • 那就需要把浏览器和服务端的通信保存起来(session),每次有数据传输时,都把session取出来,然后发送数据。
  • 注意:这个session不是平时保存用户信息的servlet session,而是websocket的session。
多浏览器同步推送

前端需要修改的代码如下:

function myBrowser(){
        let userAgent = navigator.userAgent; //取得浏览bai器du的userAgent字符串
        let isOpera = userAgent.indexOf("Opera") > -1;
        if (isOpera) {
            return "Opera"
        } //判断zhi是否Opera浏览器
        if (userAgent.indexOf("Firefox") > -1) {
            return "Firefox";
        } //判断是否Firefox浏览器
        if (userAgent.indexOf("Chrome") > -1){
            return "Chrome";
        }
        if (userAgent.indexOf("Safari") > -1) {
            return "Safari";
        } //判断是否Safari浏览器
        if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
            return "IE";
        } //判断是否IE浏览器
    }
    //需要修改的部分
 webSocket = new 
 WebSocket('ws://localhost:8088/你的项目名/coverSocket/'+myBrowser());
  • 需要传一个值用来区分每一个浏览器的标识
  • 这里我用了谷歌和火狐两个浏览器测试,就加了一个方法获取当前浏览器的标识,把这个标识传给后台。

后台修改后的代码

//configurator = SpringConfigurator.class 通过这个类,该WebSocket中就可以注入Spring容器的bean。
@ServerEndpoint(value = "/coverSocket/{name}", configurator = SpringConfigurator.class)
@Component
public class WebSocketServer {

	//你注入的业务Service
    @Autowired
    private BeanService BeanService;

	//存放用户信息的map
    private Map<String,Session> concurrentHashMap=new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(@PathParam("name")String name, Session session) {
        concurrentHashMap.put(name,session);
        String id = session.getId();
        System.err.println("通道打开"+id);
    }

    @OnClose
    public void onClose(@PathParam("name")String name,Session session) {
        concurrentHashMap.remove(name);
        String id = session.getId();
        System.err.println("通道关闭"+id);
    }

    @OnError
    public void onError(Session session,Throwable error){
        System.err.println("WebSocket发生错误");
        error.printStackTrace();
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException, InterruptedException {
        //你业务层操作的方法
    	MyBean bean = beanService.getByUniqueKey("xx","xx", MyBean .class);
        for(Map.Entry<String,Session> user:concurrentHashMap.entrySet()){
        	//这里可以开启线程发送数据更好
            user.getValue().getBasicRemote().sendText("欢迎您:"+bean.toString());
        }
    }
  • 添加了一个类对象ConcurrentHashMap,作用就是把每个连接的客户端信息存放到这个Map里
  • 在注解@ServerEndpoint定义一个参数name,并在open和close方法接收了这个参数,就是每次webSocket开启链接过关闭连接时,都根据这个参数把客户端信息存放到Map里,就是客户端的标识。
  • onMessage方法里面的就是循环将Map里的用户信息取出来并输出数据到前端。
  • 一定要加@Component,因为我这里测试多客户端使用的是同一个对象,否则每次调用都会创建不同的对象,那样就存不住客户端信息了。

同时用谷歌和火狐打开此页面,点击火狐的 发送数据按钮,发现火狐和谷歌都接受到了数据,测试成功!

;