Java如何通过SSE实现消息推送详解

这篇文章主要介绍了Java如何通过SSE实现消息推送的相关资料,SSE是一种服务器向客户端推送数据的技术,基于HTTP协议,利用长连接特性,它适用于单向数据流场景,如股票价格更新、新闻实时推送等

1.什么是SSE?

SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于 HTTP 协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

2.SSE技术的基本原理

客户端向服务器发送一个GET请求,带有指定的header,表示可以接收事件流类型,并禁用任何的事件缓存。
服务器返回一个响应,带有指定的header,表示事件的媒体类型和编码,以及使用分块传输编码(chunked)来流式传输动态生成的内容。
服务器在有数据更新时,向客户端发送一个或多个名称:值字段组成的事件,由单个换行符分隔。事件之间由两个换行符分隔。服务器可以发送事件数据、事件类型、事件ID和重试时间等字段。
客户端使用EventSource接口来创建一个对象,打开连接,并订阅onopen、onmessage和onerror等事件处理程序来处理连接状态和接收消息。
客户端可以使用GET查询参数来传递数据给服务器,也可以使用close方法来关闭连接。

3.SSE和Socket的区别

SSE(Server-Sent Events)和 WebSocket 都是实现服务器向客户端实时推送数据的技术,但它们在某些方面还是有一定的区别。

技术实现SSE 基于 HTTP 协议,利用了其长连接特性,通过浏览器向服务器发送一个 HTTP 请求,建立一条持久化的连接。而 WebSocket 则是通过特殊的升级协议(HTTP/1.1 Upgrade 或者 HTTP/2)建立新的 TCP 连接,与传统 HTTP 连接不同。

数据格式SSE 可以传输文本和二进制格式的数据,但只支持单向数据流,即只能由服务器向客户端推送数据。WebSocket 支持双向数据流,客户端和服务器可以互相发送消息,并且没有消息大小限制。

连接状态SSE 的连接状态仅有三种==:已连接、连接中、已断开==。连接状态是由浏览器自动维护的,客户端无法手动关闭或重新打开连接。而 WebSocket 连接的状态更灵活,可以手动打开、关闭、重连等。

兼容性SSE 是标准的 Web API,可以在大部分现代浏览器和移动设备上使用。但如果需要兼容老版本的浏览器(如 IE6/7/8),则需要使用 polyfill 库进行兼容。而 WebSocket 在一些老版本 Android 手机上可能存在兼容性问题,需要使用一些特殊的 API 进行处理。

安全性SSE 的实现比较简单,都是基于 HTTP 协议的,与普通的 Web 应用没有太大差异,因此风险相对较低。WebSocket 则需要通过额外的安全措施(如 SSL/TLS 加密)来确保数据传输的安全性,避免被窃听和篡改,否则可能会带来安全隐患。

总体来说,SSE 和 WebSocket 都有各自的优缺点,适用于不同的场景和需求。如果只需要服务器向客户端单向推送数据,并且应用在前端的浏览器环境中,则 SSE 是一个更加轻量级、易于实现和维护的选择。而如果需要双向传输数据、支持自定义协议、或者在更加复杂的网络环境中应用,则 WebSocket 可能更加适合。

SSE适用于场景SSE适用场景是指服务器向客户端实时推送数据的场景,例如:

股票价格更新:服务器可以根据股市的变化,实时地将股票价格推送给客户端,让客户端能够及时了解股票的走势和行情。
新闻实时推送:服务器可以根据新闻的更新,实时地将新闻内容或标题推送给客户端,让客户端能够及时了解最新的新闻动态和信息。
在线聊天:服务器可以根据用户的发送,实时地将聊天消息推送给客户端,让客户端能够及时收到和回复消息。
实时监控:服务器可以根据设备的状态,实时地将监控数据或报警信息推送给客户端,让客户端能够及时了解设备的运行情况和异常情况。

SSE适用场景的特点是:

数据更新频繁:服务器需要不断地将最新的数据推送给客户端,保持数据的实时性和准确性。
低延迟:服务器需要尽快地将数据推送给客户端,避免数据的延迟和过期。
单向通信:服务器只需要向客户端推送数据,而不需要接收客户端的数据。

4.编写SSE服务,来进行创建链接和发送消息

Service:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

package com.zillion.aggregate.app.controller;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.stereotype.Service;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

@Slf4j

@Service

public class SSEService {

    private static final Map<String,SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    public SseEmitter crateSse(String uid) {

        SseEmitter sseEmitter = new SseEmitter(0L);

        sseEmitter.onCompletion(() -> {

            log.info("[{}]结束链接" , uid);

            sseEmitterMap.remove(uid);

        });

        sseEmitter.onTimeout(() -> {

            log.info("[{}]链接超时",uid);

        });

        sseEmitter.onError(throwable -> {

            try{

                log.info("[{}]链接异常,{}",uid,throwable.toString());

                sseEmitter.send(SseEmitter.event()

                        .id(uid)

                        .name("发生异常")

                        .data("发生异常请重试")

                        .reconnectTime(3000));

                sseEmitterMap.put(uid,sseEmitter);

            }catch (IOException e){

                e.printStackTrace();

            }

        });

        try{

            sseEmitter.send(SseEmitter.event().reconnectTime(5000));

        }catch (IOException e){

            e.printStackTrace();

        }

        sseEmitterMap.put(uid,sseEmitter);

        log.info("[{}]创建sse连接成功!",uid);

        return sseEmitter;

    }

    public boolean sendMessage(String uid,String messageId,String message){

        if(StringUtils.isEmpty(message)){

            log.info("[{}]参数异常,msg为空",uid);

            return false;

        }

        SseEmitter sseEmitter = sseEmitterMap.get(uid);

        if(sseEmitter == null){

            log.info("[{}]sse连接不存在",uid);

            return  false;

        }

        try{

            sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(60000).data(message));

            log.info("用户{},消息ID:{},推送成功:{}",uid,messageId,message);

            return true;

        }catch (IOException e){

            sseEmitterMap.remove(uid);

            log.info("用户{},消息ID:{},消息推送失败:{}",uid,messageId,message);

            sseEmitter.complete();

            return false;

        }

    }

    public void closeSse(String uid){

        if(sseEmitterMap.containsKey(uid)){

            SseEmitter sseEmitter = sseEmitterMap.get(uid);

            sseEmitter.complete();

            sseEmitterMap.remove(uid);

        }else {

            log.info("用户{}连接已关闭",uid);

        }

    }

}

Controller:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

package com.zillion.aggregate.app.controller;

import cn.hutool.core.util.IdUtil;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.*;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

@Controller

@RequestMapping("/aggregate/api/pay")

public class TestController {

    private static final Map<String,Boolean> SEND_MAP = new ConcurrentHashMap<>();

    @Autowired

    private SSEService sseService;

    @GetMapping("createSse")

    @CrossOrigin

    public SseEmitter createSse(String uid)

    {

        return sseService.crateSse(uid);

    }

    @GetMapping("/sendMsg")

    @ResponseBody

    @CrossOrigin

    public SseEmitter sendMsg(@RequestParam("uid") String uid) throws InterruptedException {

        SseEmitter sseEmitter = sseService.crateSse(uid);

        if (SEND_MAP.get(uid)==null ||  !SEND_MAP.get(uid)){

             new Thread(()->{

                 int i=0;

                 while (true){

                     try {

                         i++;

                         String message = "uid:"+uid+" number:"+i+" message:"+IdUtil.fastUUID().replace("-", "");

                         sseService.sendMessage(uid,"消息"+i,message);

                         SEND_MAP.put(uid,true);

                         Thread.sleep(1000);

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                         closeSse(uid);

                     }

                 }

             }).start();

         }

        return sseEmitter;

    }

    @GetMapping("closeSse")

    @CrossOrigin

    public void closeSse(String uid){

        sseService.closeSse(uid);

    }

}

5.前端实现消息监听

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

<!doctype html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>SSE消息推送监听</title>

</head>

<body>

    <div id="conMsg"></div>

<script>

    let uid = 1;

    let chat = document.getElementById("conMsg");

    if(window.EventSource){

        var eventSource = new EventSource(`http://localhost:9001/aggregate/aggregate/api/pay/sendMsg?interfaceId=CEDB297CECCC9DCBAD348204ACDD5BAD&uid=${uid}`);

        eventSource.onopen = ()=>{

            console.log("链接成功");

        }

        eventSource.onmessage = (ev)=>{

            if(ev.data){

                chat.innerHTML += ev.data+"<br>";

            }

        }

        eventSource.onerror = ()=>{

            console.log("sse链接失败")

        }

    }else{

        alert("当前浏览器不支持sse")

    }

</script>

</body>

</html>

复制讲解AI专家

总结 

到此这篇关于Java如何通过SSE实现消息推送的文章就介绍到这了,更多相关Java SSE消息推送内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

java通过SSE实现消息推送

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容