Spring Boot中使用WebSocket 【第一部分】

2023-11-09

简介

所谓WebSocket, 类似于Socket,它的作用是可以让Web应用中的客户端和服务端建立全双工通信。在基于Spring的应用中使用WebSocket一般可以有以下三种方式:

  • 使用Java提供的@ServerEndpoint注解实现
  • 使用Spring提供的低层级WebSocket API实现
  • 使用STOMP消息实现

下面,我将对这三种实现方式做一个简单介绍,此外有关WebSocket性质的更多介绍可以参考以下这篇文章:WebSocket探秘

注:本篇文章的完整源码可以参考:https://github.com/zifangsky/WebSocketDemo

使用Java提供的@ServerEndpoint注解实现

(1)使用@ServerEndpoint注解监听一个WebSocket请求路径:

这里监听了客户端的连接端口/reverse,并定义了如何处理客户端发来的消息

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package cn.zifangsky.samplewebsocket.websocket;

 

import javax.websocket.OnMessage;

import javax.websocket.Session;

import javax.websocket.server.ServerEndpoint;

import java.io.IOException;

 

/**

* ReverseWebSocketEndpoint

*

* @author zifangsky

* @date 2018/9/30

* @since 1.0.0

*/

@ServerEndpoint("/reverse")

public class ReverseWebSocketEndpoint {

 

    @OnMessage

    public void handleMessage(Session session, String message) throws IOException {

        session.getBasicRemote().sendText("Reversed: " + new StringBuilder(message).reverse());

    }

 

}

 

(2)WebSocket相关配置:

 

 

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

package cn.zifangsky.samplewebsocket.config;

 

import cn.zifangsky.samplewebsocket.websocket.ReverseWebSocketEndpoint;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.config.annotation.EnableWebSocket;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

 

/**

* WebSocket相关配置

*

* @author zifangsky

* @date 2018/9/30

* @since 1.0.0

*/

@Configuration

@EnableWebSocket

public class WebSocketConfig{

 

    @Bean

    public ReverseWebSocketEndpoint reverseWebSocketEndpoint() {

        return new ReverseWebSocketEndpoint();

    }

 

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

 

}

 

(3)示例页面:

 

 

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

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

<html xmlns:th="http://www.thymeleaf.org">

<head>

    <meta content="text/html;charset=UTF-8"/>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

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

    <title>WebSocket Examples: Reverse</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script th:src="@{/layui/layui.js}"></script>

    <link th:href="@{/layui/css/layui.css}" rel="stylesheet">

    <style type="text/css">

        #connect-container {

            margin: 0 auto;

            width: 400px;

        }

 

        #connect-container div {

            padding: 5px;

            margin: 0 7px 10px 0;

        }

 

        .layui-btn {

            display: inline-block;

        }

    </style>

    <script type="text/javascript">

        var ws = null;

 

        $(function () {

            var target = $("#target");

            if (window.location.protocol === 'http:') {

                target.val('ws://' + window.location.host + target.val());

            } else {

                target.val('wss://' + window.location.host + target.val());

            }

        });

 

        function setConnected(connected) {

            var connect = $("#connect");

            var disconnect = $("#disconnect");

            var reverse = $("#reverse");

 

            if (connected) {

                connect.addClass("layui-btn-disabled");

                disconnect.removeClass("layui-btn-disabled");

                reverse.removeClass("layui-btn-disabled");

            } else {

                connect.removeClass("layui-btn-disabled");

                disconnect.addClass("layui-btn-disabled");

                reverse.addClass("layui-btn-disabled");

            }

 

            connect.attr("disabled", connected);

            disconnect.attr("disabled", !connected);

            reverse.attr("disabled", !connected);

        }

 

        //连接

        function connect() {

            var target = $("#target").val();

 

            ws = new WebSocket(target);

            ws.onopen = function () {

                setConnected(true);

                log('Info: WebSocket connection opened.');

            };

            ws.onmessage = function (event) {

                log('Received: ' + event.data);

            };

            ws.onclose = function () {

                setConnected(false);

                log('Info: WebSocket connection closed.');

            };

        }

 

        //断开连接

        function disconnect() {

            if (ws != null) {

                ws.close();

                ws = null;

            }

            setConnected(false);

        }

 

        //文字反转

        function reverse() {

            if (ws != null) {

                var message = $("#message").val();

                log('Sent: ' + message);

                ws.send(message);

            } else {

                alert('WebSocket connection not established, please connect.');

            }

        }

 

        //日志输出

        function log(message) {

            console.debug(message);

        }

    </script>

</head>

<body>

    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being

        enabled. Please enable

        Javascript and reload this page!</h2></noscript>

    <div>

        <div id="connect-container" class="layui-elem-field">

            <legend>Reverse</legend>

            <div>

                <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/reverse"/>

            </div>

            <div>

                <button id="connect" class="layui-btn layui-btn-normal" οnclick="connect();">Connect</button>

                <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="disconnect();">Disconnect

                </button>

 

            </div>

            <div>

                <textarea id="message" class="layui-textarea" placeholder="请输入需要反转的内容" style="width: 350px"></textarea>

            </div>

            <div>

                <button id="reverse" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="reverse();">Reverse message

                </button>

            </div>

        </div>

    </div>

</body>

</html>

启动项目后访问页面,效果如下:

使用Spring提供的低层级WebSocket API实现

Spring 4.0为WebSocket通信提供了支持,包括:

  • 发送和接收消息的低层级API;

  • 发送和接收消息的高级API;

  • 用来发送消息的模板;

  • 支持SockJS,用来解决浏览器端、服务器以及代理不支持WebSocket的问题。

使用Spring提供的低层级API实现WebSocket,主要需要以下几个步骤:

(1)添加一个WebSocketHandler:

定义一个继承了AbstractWebSocketHandler类的消息处理类,然后自定义对”建立连接“、”接收/发送消息“、”异常情况“等情况进行处理

 

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

package cn.zifangsky.samplewebsocket.websocket;

 

import cn.zifangsky.samplewebsocket.service.EchoService;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.handler.TextWebSocketHandler;

 

import javax.annotation.Resource;

import java.text.MessageFormat;

 

/**

* 通过继承 {@link org.springframework.web.socket.handler.AbstractWebSocketHandler} 的示例

*

* @author zifangsky

* @date 2018/10/9

* @since 1.0.0

*/

public class EchoWebSocketHandler extends TextWebSocketHandler{

    private final Logger logger = LoggerFactory.getLogger(getClass());

 

    @Resource(name = "echoServiceImpl")

    private EchoService echoService;

 

    @Override

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        logger.debug("Opened new session in instance " + this);

    }

 

    @Override

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        //组装返回的Echo信息

        String echoMessage = this.echoService.echo(message.getPayload());

        logger.debug(MessageFormat.format("Echo message \"{0}\"", echoMessage));

 

        session.sendMessage(new TextMessage(echoMessage));

    }

 

    @Override

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

        session.close(CloseStatus.SERVER_ERROR);

        logger.debug("Info: WebSocket connection closed.");

    }

}

 

(2)WebSocket相关配置:

 

 

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

package cn.zifangsky.samplewebsocket.config;

 

import cn.zifangsky.samplewebsocket.websocket.EchoWebSocketHandler;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.config.annotation.EnableWebSocket;

import org.springframework.web.socket.config.annotation.WebSocketConfigurer;

import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

 

/**

* WebSocket相关配置

*

* @author zifangsky

* @date 2018/9/30

* @since 1.0.0

*/

@Configuration

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer{

 

    @Override

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

        registry.addHandler(echoWebSocketHandler(), "/echoMessage");

        registry.addHandler(echoWebSocketHandler(), "/echoMessage_SockJS").withSockJS();

    }

 

    /**

     * 通过继承 {@link org.springframework.web.socket.handler.AbstractWebSocketHandler} 的示例

     */

    @Bean

    public WebSocketHandler echoWebSocketHandler(){

        return new EchoWebSocketHandler();

    }

 

}

从上面代码可以看出,这里除了配置了基本的WebSocket(也就是/echoMessage这个连接地址),还使用SockJS配置了浏览器不支持WebSocket技术时的替代方案(也就是/echoMessage_SockJS这个连接地址)。

(3)两个示例页面:

i)echo.html:

 

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

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

<html xmlns:th="http://www.thymeleaf.org">

<head>

    <meta content="text/html;charset=UTF-8"/>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

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

    <title>WebSocket Examples: Reverse</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script th:src="@{/layui/layui.js}"></script>

    <link th:href="@{/layui/css/layui.css}" rel="stylesheet">

    <style type="text/css">

        #connect-container {

            margin: 0 auto;

            width: 400px;

        }

 

        #connect-container div {

            padding: 5px;

            margin: 0 7px 10px 0;

        }

 

        .layui-btn {

            display: inline-block;

        }

    </style>

    <script type="text/javascript">

        var ws = null;

 

        $(function () {

            var target = $("#target");

            if (window.location.protocol === 'http:') {

                target.val('ws://' + window.location.host + target.val());

            } else {

                target.val('wss://' + window.location.host + target.val());

            }

        });

 

        function setConnected(connected) {

            var connect = $("#connect");

            var disconnect = $("#disconnect");

            var echo = $("#echo");

 

            if (connected) {

                connect.addClass("layui-btn-disabled");

                disconnect.removeClass("layui-btn-disabled");

                echo.removeClass("layui-btn-disabled");

            } else {

                connect.removeClass("layui-btn-disabled");

                disconnect.addClass("layui-btn-disabled");

                echo.addClass("layui-btn-disabled");

            }

 

            connect.attr("disabled", connected);

            disconnect.attr("disabled", !connected);

            echo.attr("disabled", !connected);

        }

 

        //连接

        function connect() {

            var target = $("#target").val();

 

            ws = new WebSocket(target);

            ws.onopen = function () {

                setConnected(true);

                log('Info: WebSocket connection opened.');

            };

            ws.onmessage = function (event) {

                log('Received: ' + event.data);

            };

            ws.onclose = function () {

                setConnected(false);

                log('Info: WebSocket connection closed.');

            };

        }

 

        //断开连接

        function disconnect() {

            if (ws != null) {

                ws.close();

                ws = null;

            }

            setConnected(false);

        }

 

        //Echo

        function echo() {

            if (ws != null) {

                var message = $("#message").val();

                log('Sent: ' + message);

                ws.send(message);

            } else {

                alert('WebSocket connection not established, please connect.');

            }

        }

 

        //日志输出

        function log(message) {

            console.debug(message);

        }

    </script>

</head>

<body>

    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being

        enabled. Please enable

        Javascript and reload this page!</h2></noscript>

    <div>

        <div id="connect-container" class="layui-elem-field">

            <legend>Echo</legend>

            <div>

                <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/echoMessage"/>

            </div>

            <div>

                <button id="connect" class="layui-btn layui-btn-normal" οnclick="connect();">Connect</button>

                <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="disconnect();">Disconnect

                </button>

 

            </div>

            <div>

                <textarea id="message" class="layui-textarea" placeholder="请输入请求的内容" style="width: 350px"></textarea>

            </div>

            <div>

                <button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="echo();">Echo message

                </button>

            </div>

        </div>

    </div>

</body>

</html>

ii)echo_sockjs.html:

 

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

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

<html xmlns:th="http://www.thymeleaf.org">

<head>

    <meta content="text/html;charset=UTF-8"/>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

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

    <title>WebSocket Examples: Reverse</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>

    <script th:src="@{/layui/layui.js}"></script>

    <link th:href="@{/layui/css/layui.css}" rel="stylesheet">

    <style type="text/css">

        #connect-container {

            margin: 0 auto;

            width: 400px;

        }

 

        #connect-container div {

            padding: 5px;

            margin: 0 7px 10px 0;

        }

 

        .layui-btn {

            display: inline-block;

        }

    </style>

    <script type="text/javascript">

        var ws = null;

 

        $(function () {

            var target = $("#target");

            if (window.location.protocol === 'http:') {

                target.val('http://' + window.location.host + target.val());

            } else {

                target.val('https://' + window.location.host + target.val());

            }

        });

 

        function setConnected(connected) {

            var connect = $("#connect");

            var disconnect = $("#disconnect");

            var echo = $("#echo");

 

            if (connected) {

                connect.addClass("layui-btn-disabled");

                disconnect.removeClass("layui-btn-disabled");

                echo.removeClass("layui-btn-disabled");

            } else {

                connect.removeClass("layui-btn-disabled");

                disconnect.addClass("layui-btn-disabled");

                echo.addClass("layui-btn-disabled");

            }

 

            connect.attr("disabled", connected);

            disconnect.attr("disabled", !connected);

            echo.attr("disabled", !connected);

        }

 

        //连接

        function connect() {

            var target = $("#target").val();

 

            ws = new SockJS(target);

            ws.onopen = function () {

                setConnected(true);

                log('Info: WebSocket connection opened.');

            };

            ws.onmessage = function (event) {

                log('Received: ' + event.data);

            };

            ws.onclose = function () {

                setConnected(false);

                log('Info: WebSocket connection closed.');

            };

        }

 

        //断开连接

        function disconnect() {

            if (ws != null) {

                ws.close();

                ws = null;

            }

            setConnected(false);

        }

 

        //Echo

        function echo() {

            if (ws != null) {

                var message = $("#message").val();

                log('Sent: ' + message);

                ws.send(message);

            } else {

                alert('WebSocket connection not established, please connect.');

            }

        }

 

        //日志输出

        function log(message) {

            console.debug(message);

        }

    </script>

</head>

<body>

    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being

        enabled. Please enable

        Javascript and reload this page!</h2></noscript>

    <div>

        <div id="connect-container" class="layui-elem-field">

            <legend>Echo With SockJS</legend>

            <div>

                <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/echoMessage_SockJS"/>

            </div>

            <div>

                <button id="connect" class="layui-btn layui-btn-normal" οnclick="connect();">Connect</button>

                <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="disconnect();">Disconnect

                </button>

 

            </div>

            <div>

                <textarea id="message" class="layui-textarea" placeholder="请输入请求的内容" style="width: 350px"></textarea>

            </div>

            <div>

                <button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="echo();">Echo message

                </button>

            </div>

        </div>

    </div>

</body>

</html>

具体效果省略,可自行运行源码查看。

使用STOMP消息实现

所谓STOMP(Simple Text Oriented Messaging Protocol),就是在WebSocket基础之上提供了一个基于帧的线路格式(frame-based wire format)层。它对发送简单文本消息定义了一套规范格式(STOMP消息基于Text,当然也支持传输二进制数据),目前很多服务端消息队列都已经支持STOMP,比如:RabbitMQ、 ActiveMQ等。

(1)WebSocket相关配置:

 

 

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

package cn.zifangsky.stompwebsocket.config;

 

import cn.zifangsky.stompwebsocket.interceptor.websocket.MyChannelInterceptor;

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

import org.springframework.context.annotation.Configuration;

import org.springframework.messaging.simp.config.ChannelRegistration;

import org.springframework.messaging.simp.config.MessageBrokerRegistry;

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

 

/**

* WebSocket相关配置

*

* @author zifangsky

* @date 2018/9/30

* @since 1.0.0

*/

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{

    @Autowired

    private MyChannelInterceptor myChannelInterceptor;

 

    @Override

    public void registerStompEndpoints(StompEndpointRegistry registry) {

        registry.addEndpoint("/stomp-websocket").withSockJS();

    }

 

    @Override

    public void configureMessageBroker(MessageBrokerRegistry registry) {

        //客户端需要把消息发送到/message/xxx地址

        registry.setApplicationDestinationPrefixes("/message");

        //服务端广播消息的路径前缀,客户端需要相应订阅/topic/yyy这个地址的消息

        registry.enableSimpleBroker("/topic");

    }

 

    @Override

    public void configureClientInboundChannel(ChannelRegistration registration) {

        registration.interceptors(myChannelInterceptor);

    }

 

}

 

从上面代码可以看出,这里设置了好几个地址,简单解释如下:

  • 首先注册了一个名为/stomp-websocket的端点,也就是STOMP客户端连接的地址。

  • 此外,定义了服务端处理WebSocket消息的前缀是/message,这个地址用于客户端向服务端发送消息(比如客户端向/message/hello这个地址发送消息,那么服务端通过@MessageMapping(“/hello”)这个注解来接收并处理消息)

  • 最后,定义了一个简单消息代理,也就是服务端广播消息的路径前缀(比如客户端监听/topic/greeting这个地址,那么服务端就可以通过@SendTo(“/topic/greeting”)这个注解向客户端发送STOMP消息)。

需要注意的是,上面代码中还添加了一个名为MyChannelInterceptor的拦截器,目的是为了在客户端断开连接后打印一下日志。相关代码如下:

 

 

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

package cn.zifangsky.stompwebsocket.interceptor.websocket;

 

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.messaging.Message;

import org.springframework.messaging.MessageChannel;

import org.springframework.messaging.simp.stomp.StompCommand;

import org.springframework.messaging.simp.stomp.StompHeaderAccessor;

import org.springframework.messaging.support.ChannelInterceptor;

import org.springframework.stereotype.Component;

 

import java.security.Principal;

import java.text.MessageFormat;

 

/**

* 自定义{@link org.springframework.messaging.support.ChannelInterceptor},实现断开连接的处理

*

* @author zifangsky

* @date 2018/10/10

* @since 1.0.0

*/

@Component

public class MyChannelInterceptor implements ChannelInterceptor{

    private final Logger logger = LoggerFactory.getLogger(getClass());

 

    @Override

    public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {

        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

        StompCommand command = accessor.getCommand();

 

        //用户已经断开连接

        if(StompCommand.DISCONNECT.equals(command)){

            String user = "";

            Principal principal = accessor.getUser();

            if(principal != null && StringUtils.isNoneBlank(principal.getName())){

                user = principal.getName();

            }else{

                user = accessor.getSessionId();

            }

 

            logger.debug(MessageFormat.format("用户{0}的WebSocket连接已经断开", user));

        }

    }

 

}

 

(2)使用@MessageMapping和@SendTo注解处理消息:

@MessageMapping注解用于监听指定路径的客户端消息,而@SendTo注解则用于将服务端的消息发送给监听了该路径的客户端。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

package cn.zifangsky.stompwebsocket.controller;

 

import cn.zifangsky.stompwebsocket.model.websocket.Greeting;

import cn.zifangsky.stompwebsocket.model.websocket.HelloMessage;

import org.springframework.messaging.handler.annotation.MessageMapping;

import org.springframework.messaging.handler.annotation.SendTo;

import org.springframework.stereotype.Controller;

 

/**

* Greeting

* @author zifangsky

* @date 2018/9/30

* @since 1.0.0

*/

@Controller

public class GreetingController {

 

    @MessageMapping("/hello")

    @SendTo("/topic/greeting")

    public HelloMessage greeting(Greeting greeting) {

        return new HelloMessage("Hello," + greeting.getName() + "!");

    }

}

 

(3)示例页面:

 

 

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

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

<html xmlns:th="http://www.thymeleaf.org">

<head>

    <meta content="text/html;charset=UTF-8"/>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

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

    <title>WebSocket Examples: Reverse</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

    <script th:src="@{/layui/layui.js}"></script>

    <link th:href="@{/layui/css/layui.css}" rel="stylesheet">

    <style type="text/css">

        #connect-container {

            margin: 0 auto;

            width: 400px;

        }

 

        #connect-container div {

            padding: 5px;

            margin: 0 7px 10px 0;

        }

 

        .layui-btn {

            display: inline-block;

        }

    </style>

    <script type="text/javascript">

        var stompClient = null;

 

        $(function () {

            var target = $("#target");

            if (window.location.protocol === 'http:') {

                target.val('http://' + window.location.host + target.val());

            } else {

                target.val('https://' + window.location.host + target.val());

            }

        });

 

        function setConnected(connected) {

            var connect = $("#connect");

            var disconnect = $("#disconnect");

            var echo = $("#echo");

 

            if (connected) {

                connect.addClass("layui-btn-disabled");

                disconnect.removeClass("layui-btn-disabled");

                echo.removeClass("layui-btn-disabled");

            } else {

                connect.removeClass("layui-btn-disabled");

                disconnect.addClass("layui-btn-disabled");

                echo.addClass("layui-btn-disabled");

            }

 

            connect.attr("disabled", connected);

            disconnect.attr("disabled", !connected);

            echo.attr("disabled", !connected);

        }

 

        //连接

        function connect() {

            var target = $("#target").val();

 

            var ws = new SockJS(target);

            stompClient = Stomp.over(ws);

 

            stompClient.connect({}, function () {

                setConnected(true);

                log('Info: STOMP connection opened.');

 

                //订阅服务端的/topic/greeting地址

                stompClient.subscribe("/topic/greeting", function (greeting) {

                    log('Received: ' + JSON.parse(greeting.body).content);

                })

            },function () {

                //断开处理

                setConnected(false);

                log('Info: STOMP connection closed.');

            });

        }

 

        //断开连接

        function disconnect() {

            if (stompClient != null) {

                stompClient.disconnect();

                stompClient = null;

            }

            setConnected(false);

            log('Info: STOMP connection closed.');

        }

 

        //向服务端发送姓名

        function sendName() {

            if (stompClient != null) {

                var username = $("#username").val();

                log('Sent: ' + username);

                stompClient.send("/message/hello", {}, JSON.stringify({'name': username}));

            } else {

                alert('STOMP connection not established, please connect.');

            }

        }

 

        //日志输出

        function log(message) {

            console.debug(message);

        }

    </script>

</head>

<body>

    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being

        enabled. Please enable

        Javascript and reload this page!</h2></noscript>

    <div>

        <div id="connect-container" class="layui-elem-field">

            <legend>STOMP Message With SockJS</legend>

            <div>

                <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/stomp-websocket"/>

            </div>

            <div>

                <button id="connect" class="layui-btn layui-btn-normal" οnclick="connect();">Connect</button>

                <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="disconnect();">Disconnect

                </button>

 

            </div>

            <div>

                <input id="username" type="text" class="layui-input" size="40" style="width: 350px" placeholder="请输入你的姓名" value=""/>

            </div>

            <div>

                <button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"

                        οnclick="sendName();">Say hello

                </button>

            </div>

        </div>

    </div>

</body>

</html>

启动项目后访问页面,效果如下:

参考:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Boot中使用WebSocket 【第一部分】 的相关文章

随机推荐

  • 为什么MVC不是设计模式?

    为什么MVC不是设计模式 mvc不是设计模式 而是设计模式的组合 更像是架构 mvc更像是观察者模式 策略模式 组合模式的组合 model表示应用的数据模型 view处理屏幕上展现给用户的内容 当一个model变化时 与它对应的模块发出通知
  • 微信朋友圈装x代码_微信聊天隐藏“表白”代码,好友都看懵了,太神奇了

    了解更多热门资讯 玩机技巧 数码评测 科普深扒 点击右上角关注我们 技能 微信聊天特效代码 难度系数 1颗星 适用系统 安卓 iOS 昨天 所长终于鼓起勇气约了自己喜欢了很久的女生 毕竟像我这种技术宅 约人怎么可能直说嘛 怪不好意思的 于是
  • 如何调试makefile变量

    六 七年前写过一篇 跟我一起写Makefile 直到今天 还有一些朋友问我一些Makefile的问题 老实说 我有一段时间没有用Makefile了 生疏了 回顾 这几年来大家问题我的问题 其实很多时候是makefile的调试问题 所以 就像
  • [原创]基于EDFlib/C++实现脑电数据EDF标准格式读写

    一 关于EDF European Data Format 格式的介绍 欧洲数据格式 EDF 是一种用于交换和存储多通道生物和物理信号的简单而灵活的格式 它是由几位在哥本哈根举行的1987年国际睡眠大会上首次会见的欧洲 医疗 工程师开发的 E
  • 关于openwrt的802.11w 管理帧保护使用

    目录 关于openwrt的802 11w 管理帧保护使用 802 11w技术说明 背景 技术概述 新的密钥管理方式 802 11w技术总结 openwrt中的应用 openwrt中界面上的提示 实际遇到的问题 802 11w应用总结 关于o
  • cocos creator小人碰撞墙壁效果

    1 给小人添加刚体组件 添加组件 gt 物理组件 gt collider gt Box 去掉Allow Sleep 必须去掉 否则只检测一次碰撞 Fixed Rotation勾选 禁止墙壁旋转 将gravity scale设置为0 去掉重力
  • Rose画状态图

    一 何谓状态图 1 状态图的概念 状态图由 状态 转换 事件 活动和动作5部分 组成 1 状态指的是对象在其生命周期中的一种状况 处于某个特定状态中的对象必然会满足某些条件 执行某些动作或者是等待某些事件 一个状态的生命周期是一个有限的时间
  • servlet服务器端和android端json交互

    今天尝试用servlet编写服务器端代码 返回json数据格式给服务器端 1 google提供的gson fromJson 和toJson方法 2 服务器端servlet url中使用的方法 知道怎么在web xml中配置 3 get 和p
  • 修改标准程序的GUI_STATUS

    用户提出需求 要求PO与PR可以增加按钮进行与OA系统的集成 最终效果如下 首先在类CL COMPOSITE SCREEN VIEW MM中的方法EXCLUDING FUNCTIONS中加入GUI STATUS的替代 创建一个空的程序Z M
  • 手机随机数字抽奖器_用Excel制作随机抽奖器,只需2步轻松搞定

    大家在一些节目中应该经常会看到抽奖环节 在海量的手机号码中随便抽出一个来 今天 小汪老师就来用Excel仿制一个简易的抽奖器 制作起来非常简单 大家今后如果做抽奖活动的话 也可以用上一用 效果演示 按住F9键不松 即可开始抽奖 松开后会随机
  • XMLHttpRequest: 网络错误 0x80070005, 拒绝访问 解决办法

    我使用的是vue element ui 开发后台管理系统 之前在ie中都可以正常执行操作 今天在编辑一个页面后报错 新建功能是可以正常提交数据的 但当我新建好了数据再点编辑进去修改内容后提交时就会报错 这个错仅在ie中出现 我使用的是ie1
  • oracle使用PLsql查询

    oracle查询当前用户名下所有表 TEST为用户名 用户名必须是大写 select from all tables where owner TEST 查看当前登录的用户的表 select table name from user tabl
  • latex中的对与错(对号√与叉号×)、空格

    转载 LaTeX 对号和错号 Xovee的博客 CSDN博客 latex对号错号 转载 LaTeX中的空格汇总 AXYZdong的博客 CSDN博客 latex空格符号怎么打出来 空格 对号 错号 代码 documentclass arti
  • c语言字符指针初始化赋值,C语言_指针变量的赋值与运算,很详细详解

    指针变量的赋值 指针变量同普通变量一样 使用之前不仅要定义说明 而且必须赋予具体的值 未经赋值的指针变量不能使用 否则将造成系统混乱 甚至死机 指针变量的赋值只能赋予地址 决不能赋予任何其它数据 否则将引起错误 在 语言中 变量的地址是由编
  • 基于K8S的CI&CD--安装部署zookeeper

    安装部署zookeeper 主机名 角色 IP node7 11 host com zk1 10 4 7 11 node7 12 host com zk2 10 4 7 12 node7 21 host com zk3 10 4 7 21
  • Unity场景导出GLTF格式的文件方法

    最近需要在Unity中解析GLTF文件 以及能够导出Unity的场景信息 经过查询 最后决定使用UnitGLTF这个开源库 1 首选下载代码 git clone https github com KhronosGroup UnityGLTF
  • java并发编程(7) 共享模型之工具 - 自定义线程池

    文章目录 前言 1 线程池 2 线程池自定义 步骤1 自定义拒绝策略接口 步骤2 自定义任务队列 1 任务队列参数定义 2 任务队列获取任务 3 任务队列添加任务 4 任务队列其他方法 5 任务队列全部代码 步骤3 自定义线程池 1 线程池
  • Qt多进程间通信方式——共享内存

    正文 Windows平台下进程间通信常用方式有管道 文件映射 Socket通信和共享内存等 这里详细介绍一下Qt的共享内存机制 Qt官方例子 Qt官方的例子主要是一个客户端加载图片后 将图片存储到共享内存中 另一个客户端从共享内存中获取图片
  • duboo使用zookeeper连接的单机及集群配置方式

    1 单机配置
  • Spring Boot中使用WebSocket 【第一部分】

    简介 所谓WebSocket 类似于Socket 它的作用是可以让Web应用中的客户端和服务端建立全双工通信 在基于Spring的应用中使用WebSocket一般可以有以下三种方式 使用Java提供的 ServerEndpoint注解实现