天天看點

WebSocket實際應用

websocket安全性應用

1、WebSocket簡介

随着網際網路的發展,傳統的HTTP協定已經很難滿足Web應用日益複雜的需求了,如帶有即時通信、實時資料、訂閱推送的功能的應用。近年來,随着HTML5的誕生,WebSocket協定被提出,它實作了浏覽器與伺服器的全雙工通信,擴充了浏覽器與伺服器的通信功能,使伺服器端也能主動向用戶端的發送資料,使B/S模式具備了C/S模式的實時通信能力。

WebSocket的工作流程是這樣的:浏覽器通過JavaScript向服務端發出建立WebSocket連接配接的請求,在WebSocket連接配接建立成功後,用戶端和服務端就可以通過TCP連接配接傳輸資料。因為WebSocket連接配接本質上是TCP連接配接,不需要每次傳輸都帶上重複的頭部資料,是以它的資料傳輸量比輪詢和Comet技術小了很多。

2、WebSocket示例

2.1 建立javaWeb測試demo,idea:new-project-spring Initializr,依賴選中thymeleaf/test/websocket/web即可

WebSocket實際應用

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.nicecloud</groupId>
    <artifactId>springboot_websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_websocket</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

application.yml:

# thymeleaf start
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.servlet.content-type=text/html
# 開發時關閉緩存,不然沒法看到實時頁面
spring.thymeleaf.cache=false
# thymeleaf end
           

client.html:

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用戶端首頁</title>
    <!--這裡必須是雙标簽-->
    <script th:src="@{/js/jquery.min.js}"/>
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
使用者 ID: <input type="text" id="user" placeholder="請輸入您的使用者ID"/>
<input type="button" value="連接配接" onclick="connect()"/><br/>
發送内容: <input type="text" id="writeMsg" placeholder="請填寫要發送的内容"/>
<input type="button" value="發送" onclick="sendMsg()"/>

<script type="text/javascript">

    //放在這裡是擷取不到值的,應該放在function裡面
    //var user = $("#user").val();
    var ws = null;//不能在send()裡再建立ws,因為ws建立連接配接時已存在
    //建立連接配接
    function connect(){

        var userId = $("#user").val();
        if(userId != null){
            if('WebSocket' in window){
                ws = new WebSocket("ws://localhost:8080/websocket/"+userId);
            }else if('MozWebSocket' in window){
                ws = new MozWebSocket("ws://localhost:8080/websocket/"+userId);
            }else{
                alert("該浏覽器不支援websocket");
            }


            ws.onmessage = function(evt){
                alert("伺服器群發消息:" + evt.data);
            }


            ws.onclose = function(evt){
                alert("連接配接中斷");
            }


            ws.onopen = function(evt){
                alert("目前連接配接使用者ID為" + userId + ",連接配接成功!");
            }

        }else {//userId為空
            alert("請輸入您的userId");

        }

    }


    //發送消息
    // function sendMsg() {
    //     var message = $("#writeMsg").val();
    //     if(message != null){
    //         $.ajax({
    //                 method:'get',
    //                 url:'/sendMessage',
    //                 data:{
    //                     msg:message
    //                 },
    //                 success:function (data) {
    //                     console.log(data);
    //                 }
    //             }
    //
    //         )
    //     }else{
    //         alert("請填寫要發送的内容");
    //     }
    // }
</script>
</body>
</html>
           

server.html:

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>伺服器端發送資訊的頁面</title>
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
目前線上人數總計<div id="sum" th:text="${count}"></div>
向用戶端發送消息<input type="text" id="message"/><br/>
<input type="button" value="全體發送" onclick="sendAll()"/>


<script type="text/javascript">
    function sendAll() {
        var message = $("#message").val();
        if(message != null){
            $.ajax({
                    method:'get',
                    url:'/sendMessageToAll',
                    data:{
                        msg:message
                    },
                    success:function (data) {
                        console.log(data);
                    }
                }

            )
        }else{
            alert("請填寫要發送的内容");
        }
    }
</script>

</body>
</html>
           
WebSocketConfig:
           
package com.nicecloud.springboot_websocket.websocket;

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

/**
 * websocket配置類
 */
@Configuration
public class WebSocketConfig {
    /**
     * ServerEndpointExporter對象會自動注冊使用了@ServerEndpoint注解的的websocket endpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
           

WebSocketServer:

package com.nicecloud.springboot_websocket.websocket;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * websocket伺服器端點
 * @ServerEndpoint 類級别注解,将目前類定義為一個websocket服務端節點,value表示通路路徑
 */
@ServerEndpoint(value = "/websocket/{userId}")
@Component
public class WebSocketServer {
    private static Logger logger = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 與用戶端的連接配接會話,伺服器端通過它向用戶端發送消息
     */
    private Session session;

    /**
     * 使用concurrent包的線程安全set,用來存放每個用戶端對應的WebSocketServer對象
     */
    private static CopyOnWriteArraySet<WebSocketServer> websocketSet = new CopyOnWriteArraySet();

    /**
     * 靜态變量記錄線上連接配接數,應該把它設計成線程安全的
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 新的連接配接建立成功後調用此方法,此方法嚴禁加入死循環或線程堵塞,會導緻其他事件監聽失效
     *
     * @param session
     * @param userId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        // session逾時時間,逾時後無法再發送消息,服務端幾秒之後會觸發onClose時間
        session.setMaxIdleTimeout(60 * 1000 * 30);
        // 擷取目前session
        this.session = session;
        // 将目前session加入到set中
        websocketSet.add(this);
        // 線上使用者連接配接數+1
        addOnlineCount();
        logger.info("目前使用者連接配接數onlineCount = {}", getOnlineCount());
    }

    /**
     * 伺服器端收到用戶端消息時調用此方法發
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        logger.info("目前發送人sessionId = {}, 發送内容為:{}", session.getId(), message);
    }

    /**
     * 斷開連接配接時調用此方法,此demo重新整理頁面即調用
     */
    @OnClose
    public void onClose() {
        // 删除目前使用者session
        websocketSet.remove(this);
        // 線上使用者連接配接數-1
        subOnlineCount();
    }

    /**
     * 出現錯誤時調用此方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        logger.info("發生錯誤:{}, sessionId = {}", error.getMessage(), session.getId());
        error.printStackTrace();
    }

    /**
     * 用戶端單發消息
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
            logger.info("用戶端sessionId = {},消息内容為:{}", this.session.getId(), message);
        } catch (IOException e) {
            logger.error("用戶端發消息異常,異常資訊為:{}", e.getMessage());
        }
    }

    /**
     * 伺服器群發消息給用戶端
     *
     * @param message
     */
    public void sendMessageToAll(String message) {
        for (WebSocketServer webSocketServer : websocketSet) {
            try {
                webSocketServer.session.getBasicRemote().sendText(message);
                logger.info("服務端群發消息給用戶端==>sessionId = {},消息内容為:{}", webSocketServer.session.getId(), message);
            } catch (IOException e) {
                logger.error("服務端群發消息異常,異常資訊為:{}", e.getMessage());
            }
        }
    }

    /**
     * 擷取目前使用者連接配接數
     *
     * @return
     */
    public static synchronized Integer getOnlineCount() {
        return WebSocketServer.onlineCount.get();
    }

    /**
     * 目前線上使用者連接配接數+1
     */
    private static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount.addAndGet(1);
    }

    /**
     * 目前線上使用者連接配接數-1
     */
    private static synchronized void subOnlineCount() {
        if (WebSocketServer.onlineCount.get() > 0) {
            WebSocketServer.onlineCount.addAndGet(-1);
        }
    }

}
           

WebSocketController:

package com.nicecloud.springboot_websocket.controller;

import com.nicecloud.springboot_websocket.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;

@Controller
public class WebSocketController {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通路伺服器端的統計資訊頁面
     *
     * @param model
     * @return
     */
    @RequestMapping("/server")
    public String onlineCount(Model model) {
        int count = WebSocketServer.getOnlineCount();
        // 把線上數放入model中,前端直接擷取
        model.addAttribute("count", count);
        // 服務端發送資訊的頁面
        return "server";
    }

    /**
     * 通路用戶端首頁
     *
     * @return
     */
    @RequestMapping("client")
    public String index() {
        // 跳轉到用戶端首頁
        return "client";
    }

    /**
     * 服務端群發消息
     *
     * @param message
     * @return
     */
    @RequestMapping("/sendMessageToAll")
    public String sendMessageToAll(@RequestParam(value = "msg") String message) {
        webSocketServer.sendMessageToAll(message);
        //如果該方法傳回void,那麼運作時會抛出org.thymeleaf.exceptions.TemplateInputException: Error resolving template
        return "server";
    }

    /**
     * 用戶端發送消息
     *
     * @param message
     * @return
     */
//    @RequestMapping("/sendMessage")
//    public String sendMessage(@RequestParam(value = "msg") String message) {
//        webSocketServer.sendMessage(message);
//        //如果該方法傳回void,那麼運作時會抛出org.thymeleaf.exceptions.TemplateInputException: Error resolving template
//        return "server";
//    }
}
           

3、運作結果

啟動項目,打開兩個頁面,通路http://localhost:8080/client:

WebSocket實際應用
WebSocket實際應用

通路用戶端頁面:http://localhost:8080/server

WebSocket實際應用
WebSocket實際應用
WebSocket實際應用
WebSocket實際應用

ok~暫時就這樣子啦lala

繼續閱讀