React+SpringBoot通过WebSocket实时统计在线人数

   日期:2020-04-30     浏览:225    评论:0    
核心提示:一、基本概念WebSocket是一种网络通信协议,如果服务器有连续的状态变化,客户端要获知就非常麻烦网络

一、基本概念

WebSocket 是一种网络通信协议,如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)所以这里使用WebSocket 通过登录后跳转到首页,向后台WebSocket 建立长链接来达到"即使通讯",随着用户页面打开或关闭后台群发消息来实时更改页面显示的人数,当然这里目前不涉及登录后的上线下线以及帐号登录挤掉功能,如果需要可以通过发送消息来改变。

二、SpringBoot 后台实现WebSocket

pom.xml

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

WebSocketConfig.java

package com.kero99.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration  
public class WebSocketConfig {  
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
} 

WebSocketController.java

package com.kero99.socket;


import java.io.IOException;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/scoket")
public class WebSocketController {
//	@Autowired
//	private RedisOperator redisOperator;
	@RequestMapping(value="/sendAll", method=RequestMethod.GET)
	
	String sendAllMessage(@RequestParam(required=true) String message){
		try {
			WebSocketServer.BroadCastInfo(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "ok";
	}
	@RequestMapping(value="/sendOne", method=RequestMethod.GET)
	
	String sendOneMessage(@RequestParam(required=true) String message,@RequestParam(required=true) String id){
		try {
			WebSocketServer.SendMessage(id,message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "ok";
	}
}

WebSocketServer.java

package com.kero99.socket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
	
	private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
	private static final AtomicInteger OnlineCount = new AtomicInteger(0);
	// concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。
	private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
	

	
	@OnOpen
	public void onOpen(Session session) throws IOException {
		SessionSet.add(session); 
		int personCount = OnlineCount.incrementAndGet(); // 在线数加1
		System.out.println("有连接加入,当前连接数为:"+personCount);
		log.info("有连接加入,当前连接数为:{}", personCount);
//		SendMessage(session, "连接成功,当前连接人数为:"+personCount);
//		SendMessage(session,String.valueOf(personCount));
		BroadCastInfo(String.valueOf(OnlineCount.get()));
	}
	
	
	@OnClose
	public void onClose(Session session) throws IOException {
		int personCount = OnlineCount.decrementAndGet();
		System.out.println("有连接关闭,当前连接数为:"+personCount);
		log.info("有连接关闭,当前连接数为:{}", personCount);
		SessionSet.remove(session);	
	}

	
	@OnMessage
	public void onMessage(String message, Session session) throws IOException {
		log.info("来自客户端的消息:{}",message);
//		System.out.println("来自客户端的消息:"+message);
//		SendMessage(session, "收到消息,消息内容:"+message);
		if(message.equals("管理平台")) {
			System.out.println("收到平台类型:"+message);
		}
//		if(message.equals("新增人数")) {
//			System.out.println("打开页面:"+message);
//			BroadCastInfo(String.valueOf(OnlineCount.get()+1));
//		}
		if(message.equals("关闭页面")) {
			System.out.println("收到关闭页面:"+message);
			//在线数加-1
			BroadCastInfo(String.valueOf(OnlineCount.get()-1));
		}		
	}

	
	@OnError
	public void onError(Session session, Throwable error) {
		log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId());
		System.out.println("发生错误:{},Session ID: "+error.getMessage()+session.getId());
		error.printStackTrace();
	}

	
	public static void SendMessage(Session session, String message) {
		try {
			session.getBasicRemote().sendText(message);
//			session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
		} catch (IOException e) {
			log.error("发送消息出错:{}", e.getMessage());
			System.out.println("发送消息出错:{}"+e.getMessage());
			e.printStackTrace();
		}
	}

	
	public static void BroadCastInfo(String message) throws IOException {
		for (Session session : SessionSet) {
			if(session.isOpen()){
				SendMessage(session, message);
			}
		}
	}

	
	public static void SendMessage(String sessionId,String message) throws IOException {
		Session session = null;
		for (Session s : SessionSet) {
			if(s.getId().equals(sessionId)){
				session = s;
				break;
			}
		}
		if(session!=null){
			SendMessage(session, message);
		}
		else{
			log.warn("没有找到你指定ID的会话:{}",sessionId);
			System.out.println("没有找到你指定ID的会话:"+sessionId);
		}
	}
	
}

三、React+Umi+Antd 实现前端 WebSocket 通讯

js 需要安装 socket.io

命令: npm  socket.io 或者 yarn add socket.io

  componentDidMount() { 
 let ws = new WebSocket("ws://localhost:12935/20191108_V1.0_xdnx/websocket");
    if (typeof (WebSocket) == "undefined") {
      console.log("遗憾:您的浏览器不支持WebSocket");
    } else {
      console.log("恭喜:您的浏览器支持WebSocket");
      ws.onopen = (evt)=> {
        console.log("Connection open ...");
        ws.send("管理平台");
        ws.send("新增人数");
      };

      ws.onmessage = (evt)=> {
        console.log( "Received Message: " + evt.data);
        // alert(evt.data)
        //this.state.messageData 为接受数据的变量
        let messageData=this.state.messageData;
        this.setState({
          messageData:evt.data
        })
        // ws.close();
      };
      ws.onclose = (evt)=> {
        // alert(evt.data)
        console.log("Connection closed.");
        // ws.close();
      };
      ws.onerror = (evt)=> {
        console.log("error")
      };
      window.onbeforeunload = (event)=> {
        console.log("关闭WebSocket连接!");
        ws.send("关闭页面");
        event.close();
      }
  
  }
}

render html

这里用的antd的统计数值控件

          <div  style={{
            background: '#ececec',
            padding: '10px',
            width:'20%',
            float:'left',marginTop:'20px'
          }}>
            <Row gutter={16}>
              <Col span={12}>
                <Card>
                  <Statistic
                    title="管理平台当前在线人数"
                    value={this.state.messageData}
                    precision={0}
                    valueStyle={{ color: '#3f8600' }}
                    suffix="人"
                  />
                </Card>
              </Col>

            </Row>
          </div>

四、实现结果

五、关于部署

如果使用Tomcat部署war包下面这段可以注释掉,由Tomcat管理,页面路径需要改成和服务器一致的,不用加Http开头

eg: ws://localhost:12935/20191108_V1.0_xdnx/websocket

//package com.kero99.socket;
//
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.stereotype.Component;
//import org.springframework.web.socket.server.standard.ServerEndpointExporter;
//
//
//@Configuration  
//@Component
//public class WebSocketConfig {  
//	
//	@Bean
//    public ServerEndpointExporter serverEndpointExporter() {  
//        return new ServerEndpointExporter();  
//    }  
//  
//} 

 

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服