React+SpringBoot通過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;
/**
 * 開啓WebSocket支持
 * @author ygc
 */
@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;

/**
 * WebSocket服務器端推送消息示例Controller
 * 
 * @author ygc
 *
 */
@RestController
@RequestMapping("/scoket")
public class WebSocketController {
//	@Autowired
//	private RedisOperator redisOperator;
	@RequestMapping(value="/sendAll", method=RequestMethod.GET)
	/**
	 * 羣發消息內容
	 * @param message
	 * @return
	 */
	String sendAllMessage(@RequestParam(required=true) String message){
		try {
			WebSocketServer.BroadCastInfo(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "ok";
	}
	@RequestMapping(value="/sendOne", method=RequestMethod.GET)
	/**
	 * 指定會話ID發消息
	 * @param message 消息內容
	 * @param id 連接會話ID
	 * @return
	 */
	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;

/**
 * WebSocket服務端
 * @author ygc
 */
@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>();
	

	/**
	 * 連接建立成功調用的方法
	 * @throws IOException 
	 */
	@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()));
	}
	
	/**
	 * 連接關閉調用的方法
	 * @throws IOException 
	 */
	@OnClose
	public void onClose(Session session) throws IOException {
		int personCount = OnlineCount.decrementAndGet();
		System.out.println("有連接關閉,當前連接數爲:"+personCount);
		log.info("有連接關閉,當前連接數爲:{}", personCount);
		SessionSet.remove(session);	
	}

	/**
	 * 收到客戶端消息後調用的方法
	 * 
	 * @param message
	 *            客戶端發送過來的消息
	 * @throws IOException 
	 */
	@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));
		}		
	}

	/**
	 * 出現錯誤
	 * @param session
	 * @param error
	 */
	@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();
	}

	/**
	 * 發送消息,實踐表明,每次瀏覽器刷新,session會發生變化。
	 * @param session
	 * @param message
	 */
	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();
		}
	}

	/**
	 * 羣發消息
	 * @param message
	 * @throws IOException
	 */
	public static void BroadCastInfo(String message) throws IOException {
		for (Session session : SessionSet) {
			if(session.isOpen()){
				SendMessage(session, message);
			}
		}
	}

	/**
	 * 指定Session發送消息
	 * @param sessionId
	 * @param message
	 * @throws IOException
	 */
	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>

四、實現結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章