# SSE--1.1.入门案例01

# SSE通讯

sse-01

# 工程结构

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zs
        │           └── sse
        │               ├── MyApplication.java
        │               └── SseController.java
        └── resources
            └── static
                └── index.html

# 前端核心实现

//创建SSE对象连接
const evtSource = new EventSource(openurl) ;

//监听连接事件
evtSource.onmessage = (event) => {
    ui.addElementToUI(`接收到消息: ${event.data}`)
};
evtSource.onopen = (event) => {
    console.log('建立连接...')
};
evtSource.onerror = (event) => {
    console.error("发生错误:", event) ;
};

//关闭连接
ui.uiData.evtSource.close() ;

# SSE案例核心代码

//开启SSE
ui.doms.openSSE.addEventListener('click', function () {
    if (ui.uiData.evtSource) {
        return
    }
    let openurl = `/sse/events/${Date.now()}`
    const evtSource = new EventSource(openurl) ;
    ui.addElementToUI(`发起连接:${evtSource.url}`)
    ui.uiData.evtSource = evtSource;
    evtSource.onmessage = (event) => {
        ui.addElementToUI(`接收到消息: ${event.data}`)
    };
    evtSource.onopen = (event) => {
        console.log('建立连接...')
    };
    evtSource.onerror = (event) => {
        console.error("发生错误:", event) ;
    };
});

//关闭SSE
ui.doms.closeSSE.addEventListener('click', function() {
    if (!ui.uiData.evtSource) {
        return
    }
    ui.addElementToUI(`关闭连接: ${ui.uiData.evtSource.url}`)
    ui.uiData.evtSource.close() ;
    ui.uiData.evtSource = void 0;
})

# 完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <!-- 指定字符集 -->
    <meta charset="UTF-8">
    <!-- 使用Edge最新的浏览器的渲染方式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- viewport视口:网页可以根据设置的宽度自动进行适配,在浏览器的内部虚拟一个容器,容器的宽度与设备的宽度相同。
    width: 默认宽度与设备的宽度相同
    initial-scale: 初始的缩放比,为1:1 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>SSE</title>
</head>
<body>
<button class="open-sse-button" type="button">开启SSE连接</button>
<button class="close-sse-button"type="button">关闭SSE连接</button>
<button class="send-sse-button"type="button">发送数据</button>
<hr style="margin: 2px; padding: 0px 0px;"/>
<ul id="list"></ul>
</body>
<script>
    class UIData {
        constructor() {
            this.evtSource = void 0;
        }
    }
    class UI {
        constructor() {
            this.uiData = new UIData();
            this.doms = {
                openSSE: document.querySelector('.open-sse-button'),
                closeSSE: document.querySelector('.close-sse-button'),
                sendSSE: document.querySelector('.send-sse-button'),
                ulList: document.querySelector('#list')
            };
            this.listenEvent();
        }

        // 监听各种事件
        listenEvent() {

        }


        addElementToUI(text) {
            var html = this.doms.ulList.innerHTML;
            html += `
            <li>
                ${text}
            </li>
            `
            this.doms.ulList.innerHTML = html;
        }
    }
    var ui = new UI();

    //开启SSE
    ui.doms.openSSE.addEventListener('click', function () {
        if (ui.uiData.evtSource) {
            return
        }
        let openurl = `/sse/events/${Date.now()}`
        const evtSource = new EventSource(openurl) ;
        ui.addElementToUI(`发起连接:${evtSource.url}`)
        ui.uiData.evtSource = evtSource;
        evtSource.onmessage = (event) => {
            ui.addElementToUI(`接收到消息: ${event.data}`)
        };
        evtSource.onopen = (event) => {
            console.log('建立连接...')
        };
        evtSource.onerror = (event) => {
            console.error("发生错误:", event) ;
        };
    });
    //关闭SSE
    ui.doms.closeSSE.addEventListener('click', function() {
        if (!ui.uiData.evtSource) {
            return
        }
        ui.addElementToUI(`关闭连接: ${ui.uiData.evtSource.url}`)
        ui.uiData.evtSource.close() ;
        ui.uiData.evtSource = void 0;
    })
    //发送数据
    ui.doms.sendSSE.addEventListener('click', function() {
        if (!ui.uiData.evtSource) {
            return
        }
        let url = ui.uiData.evtSource.url;
        url = url.replace('/events', '/sender') ;
        fetch(url, {
            method: 'GET', // or 'POST', 'PUT', etc.
            headers: {
                'Content-Type': 'application/json',
            },
        })
            .then(response => {
                if (response.ok) {
                    //return response.json(); // 解析响应体为JSON
                    return response.text(); // 解析响应体为文本
                } else {
                    throw new Error('Network response was not ok');
                }
            })
            .then(data => console.log(data))
            .catch((error) => console.error('Error:', error)); // promise is rejected with an error
    })
</script>
</html>

# 后端核心实现

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

# SSE案例核心代码

@GetMapping(path = "/events/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter createConnect(@PathVariable("id") String id) throws IOException {
    SseEmitter emitter = new SseEmitter(0L);
    // 每一个客户端保存到Map中
    sse.put(id, emitter);
    // 当发生错误的回调
    emitter.onError(ex -> {
        System.err.printf("userId: %s, error: %s%n", id, ex.getMessage());
        sse.remove(id);
    });
    // 异步请求完成后的回调
    emitter.onCompletion(() -> {
        sse.remove(id);
        System.out.printf("%s, 请求完成...");
    });
    // 异步请求超时回调
    emitter.onTimeout(() -> {
        System.err.println("超时...");
    });
    return emitter;
}

# 完整代码

package com.zs.sse;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/sse")
public class SseController {

    // 该集合用来管理所有客户端的连接
    private final Map<String, SseEmitter> sse = new ConcurrentHashMap<>();

    // 创建连接接口,同时指定了消息类型为text/event-stream
    @GetMapping(path = "/events/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter createConnect(@PathVariable("id") String id) throws IOException {
        SseEmitter emitter = new SseEmitter(0L);
        // 每一个客户端保存到Map中
        sse.put(id, emitter);
        // 当发生错误的回调
        emitter.onError(ex -> {
            System.err.printf("userId: %s, error: %s%n", id, ex.getMessage());
            sse.remove(id);
        });
        // 异步请求完成后的回调
        emitter.onCompletion(() -> {
            sse.remove(id);
            System.out.printf("%s, 请求完成...");
        });
        // 异步请求超时回调
        emitter.onTimeout(() -> {
            System.err.println("超时...");
        });
        return emitter;
    }

    // 该接口用来进行消息的发送
    // 由客户端发起请求,然后根据id获取相应的SseEmitter进行消息的发送
    @GetMapping("/sender/{id}")
    public String sender(@PathVariable("id") String id) throws Exception {
        SseEmitter emitter = this.sse.get(id);
        if (emitter != null) {
            try {
                emitter.send("随机消息 - " + new Random().nextInt(10000000));
            } catch (Exception e) {
                System.err.printf("%s%n, e.getMessage()");
            }

            String[] strArr = {
                    "我", "懒", "得", "写", "你", "谷", "搜", "到", "处", "皆", "只", "因", "你",
                    "太", "美", "浅", "唱", "动", "人", "说", "不", "出", "我", "试", "着", "多",
                    "看", "你", "一", "眼", "却", "发", "现", "我", "已", "沉", "溺", "于", "你",
                    "的", "镜", "头", "里", "只", "因", "你", "太", "美", "所", "以", "我", "多",
                    "看", "了", "一", "眼", "只", "因", "我", "太", "傻", "所", "以", "我", "放",
                    "不", "开", "你", "的", "手", "只", "因", "你", "太", "美", "所", "以", "我",
                    "做", "了", "个", "梦", "梦", "见", "你", "在", "微", "笑", "我", "在", "注",
                    "视", "只", "因", "你", "太", "美", "所", "以", "我", "放", "了", "你", "的",
                    "手", "所", "以", "我", "会", "微", "笑", "因", "为", "你", "太", "美", "end"
            };
            for (int i = 0; i < strArr.length; i++) {
                emitter.send(strArr[i]);
                Thread.sleep(500);
            }
        }
        return "success";
    }
}

# 消息监听

**注意:*默认是“message”事件,因为它可以捕获没有 event 字段的事件, * 以及具有特定类型 event:message 的事件。 它不会触发任何其他类型的事件。

sse-02

# 前端核心

// 监听SSE消息
ui.doms.listenSSE.addEventListener('click', function () {
    if (!ui.uiData.evtSource) {
        return
    }
    // 监听指定事件类型消息
    ui.uiData.evtSource.addEventListener("chat", ui.addElementToUIFunc);
})
// 关闭监听
ui.doms.closeListen.addEventListener('click', (event) => {
    if (!ui.uiData.evtSource) {
        return
    }
    // 监听指定事件类型消息
    ui.uiData.evtSource.removeEventListener("chat", ui.addElementToUIFunc);
})

# 前端完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <!-- 指定字符集 -->
    <meta charset="UTF-8">
    <!-- 使用Edge最新的浏览器的渲染方式 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- viewport视口:网页可以根据设置的宽度自动进行适配,在浏览器的内部虚拟一个容器,容器的宽度与设备的宽度相同。
    width: 默认宽度与设备的宽度相同
    initial-scale: 初始的缩放比,为1:1 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>SSE</title>
</head>
<body>
<button class="open-sse-button" type="button">开启SSE连接</button>
<button class="close-sse-button" type="button">关闭SSE连接</button>
<button class="send-sse-button" type="button">发送数据</button>
<button class="listen-sse-button" type="button">监听SSE</button>
<button class="close-listen-button" type="button">关闭监听</button>
<button class="send-listen-button" type="button">发送SSE监听数据</button>
<hr style="margin: 2px; padding: 0px 0px;"/>
<ul id="list"></ul>
</body>
<script>
    class UIData {
        constructor() {
            this.evtSource = void 0;
        }
    }

    class UI {
        constructor() {
            this.uiData = new UIData();
            this.doms = {
                openSSE: document.querySelector('.open-sse-button'),
                closeSSE: document.querySelector('.close-sse-button'),
                listenSSE: document.querySelector('.listen-sse-button'),
                closeListen: document.querySelector('.close-listen-button'),
                sendSSE: document.querySelector('.send-sse-button'),
                sendListen: document.querySelector('.send-listen-button'),
                ulList: document.querySelector('#list')
            };
            this.listenEvent();
        }

        // 监听各种事件
        listenEvent() {

        }


        addElementToUI(text) {
            var html = this.doms.ulList.innerHTML;
            html += `
            <li>
                ${text}
            </li>
            `
            this.doms.ulList.innerHTML = html;
        }
        addElementToUIFunc(event) {
            var html = ui.doms.ulList.innerHTML;
            html += `
            <li>
                ${event.data}
            </li>
            `
            ui.doms.ulList.innerHTML = html;
        }
    }

    var ui = new UI();

    //开启SSE
    ui.doms.openSSE.addEventListener('click', function () {
        if (ui.uiData.evtSource) {
            return
        }
        let openurl = `/sse/events/${Date.now()}`
        const evtSource = new EventSource(openurl);
        ui.addElementToUI(`发起连接:${evtSource.url}`)
        ui.uiData.evtSource = evtSource;
        evtSource.onmessage = (event) => {
            ui.addElementToUI(`接收到消息: ${event.data}`)
        };
        evtSource.onopen = (event) => {
            console.log('建立连接...')
        };
        evtSource.onerror = (event) => {
            console.error("发生错误:", event);
        };
    });
    //关闭SSE
    ui.doms.closeSSE.addEventListener('click', function () {
        if (!ui.uiData.evtSource) {
            return
        }
        ui.addElementToUI(`关闭连接: ${ui.uiData.evtSource.url}`)
        ui.uiData.evtSource.close();
        ui.uiData.evtSource = void 0;
    })
    //发送数据
    ui.doms.sendSSE.addEventListener('click', function () {
        if (!ui.uiData.evtSource) {
            return
        }
        let url = ui.uiData.evtSource.url;
        url = url.replace('/events', '/sender');
        fetch(url, {
            method: 'GET', // or 'POST', 'PUT', etc.
            headers: {
                'Content-Type': 'application/json',
            },
        })
            .then(response => {
                if (response.ok) {
                    //return response.json(); // 解析响应体为JSON
                    return response.text(); // 解析响应体为文本
                } else {
                    throw new Error('Network response was not ok');
                }
            })
            .then(data => console.log(data))
            .catch((error) => console.error('Error:', error)); // promise is rejected with an error
    })

    // 监听SSE消息
    ui.doms.listenSSE.addEventListener('click', function () {
        if (!ui.uiData.evtSource) {
            return
        }
        // 监听指定事件类型消息
        ui.uiData.evtSource.addEventListener("chat", ui.addElementToUIFunc);
    })
    // 关闭监听
    ui.doms.closeListen.addEventListener('click', (event) => {
        if (!ui.uiData.evtSource) {
            return
        }
        // 监听指定事件类型消息
        ui.uiData.evtSource.removeEventListener("chat", ui.addElementToUIFunc);
    })
    //发送数据
    ui.doms.sendListen.addEventListener('click', function () {
        if (!ui.uiData.evtSource) {
            return
        }
        let url = ui.uiData.evtSource.url;
        url = url.replace('/events', '/listen');
        fetch(url, {
            method: 'GET', // or 'POST', 'PUT', etc.
            headers: {
                'Content-Type': 'application/json',
            },
        })
            .then(response => {
                if (response.ok) {
                    //return response.json(); // 解析响应体为JSON
                    return response.text(); // 解析响应体为文本
                } else {
                    throw new Error('Network response was not ok');
                }
            })
            .then(data => console.log(data))
            .catch((error) => console.error('Error:', error)); // promise is rejected with an error
    })

</script>
</html>

# 后端核心

@GetMapping("/listen/{id}")
public String listen(@PathVariable("id") String id) throws Exception {
    SseEmitter emitter = this.sse.get(id) ;
    if (emitter != null) {
        SseEmitter.SseEventBuilder builder = SseEmitter.event() ;
        // 指定事件类型
        builder.name("chat") ;
        String msg = "随机消息 - " + new Random().nextInt(10000000);
        builder.data(msg) ;
        try {
            emitter.send(builder) ;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return "success" ;
}
更新时间: 2024年1月30日星期二下午2点36分