美文网首页纵横研究院后端基础技术专题社区
WebSocket广播式和点对点的通信【原创】

WebSocket广播式和点对点的通信【原创】

作者: elijah777 | 来源:发表于2019-07-17 21:13 被阅读0次

本篇文章主要介绍websocket的两种通信,广播式和点对点的通信。

一、广播式通讯

类似广播一样,只要发出,订阅的人便可以接收到

前端发出消息,通过SockJS连接

代码示例
1、pom.xml引入jar
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-websocket</artifactId>
 </dependency>
2、WebSocketConfig.java 配置WebSocket
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
​
/**
 * @description: 配置WebSocket
 *          注释@EnableWebSocketMessageBroker开始使用STOMP协议来传输基于代理(message broker)的消息
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-12 16:39
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
​
 /**
 *  注册STOMP协议的节点(endpoint),并映射的对应的URL。
 *  注册一个STOMP的endpoint,并指定使用SickJS协议
 * @param registry
 */
 @Override
 public void registerStompEndpoints(StompEndpointRegistry registry) {
 registry.addEndpoint("/endpointSSH").withSockJS();
 registry.addEndpoint("/endpointChat").withSockJS();
 }
​
 /**
 *  配置消息代理(Message Broker)
 *  广播式应配置一个/topic 消息代理
 *  点对点配置 /queue
 * @param registry
 */
 @Override
 public void configureMessageBroker(MessageBrokerRegistry registry) {
 registry.enableSimpleBroker("/queue","/topic");
 }
​
}
3、两个发送和接收消息的实体 ElijahMessage.java ElijahResponse.java
import lombok.AllArgsConstructor;
import lombok.Getter;
​
/**
 * @description: 用于服务器想向浏览器发生消息
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-12 17:10
 */
@AllArgsConstructor
@Getter
public class ElijahResponse {
 private String responseMessage;
}
import lombok.Getter;
​
/**
 * @description: 用于接收服务器发送的消息
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-12 17:09
 */
@Getter
public class ElijahMessage {
 private String name;
}
4、WsController.java WebSocket 控制器
import com.ch7.domain.ElijahMessage;
import com.ch7.domain.ElijahResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
​
import java.security.Principal;
​
/**
 * @description: WebSocket 控制器
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-12 17:15
 */
@Controller
@Slf4j
public class WsController {
​
 /**
 * 通过SimpMessagingTemplate 向浏览器发生消息
 */
 @Autowired
 private SimpMessagingTemplate messagingTemplate;
​
 /**
 *  当浏览器向服务端发生请求时,通过@MessageMapping映射/welcome这个地址
 *  注解@MessageMapping使用方法与@RequestMapping相似
 * @param message
 * @return
 * @throws Exception
 */
 @MessageMapping("welcome")
 @SendTo("/topic/getResponse")
 public ElijahResponse say(ElijahMessage message) throws Exception {
 Thread.sleep(3000);
 return new ElijahResponse("Welcome, " + message.getName() + "!");
 }
​
 /**
 *  点对点聊天
 *
 * @param principal 包含当前用户的信息
 * @param msg
 */
 @MessageMapping("/chat")
 public void handleChar(Principal principal, String msg) {
​
 // 判断发生给谁
 if (principal.getName().equals("ssh")) {
 // 发生消息给用户  接收消息的用户、浏览器订阅地址和消息内容
 messagingTemplate.convertAndSendToUser("elijah",
 "/queue/notifications",
 principal.getName() + "-send: " + msg);
 } else {
 messagingTemplate.convertAndSendToUser("ssh",
 "/queue/notifications",
 principal.getName() + "-send: " + msg);
 }
 }
​
}
5、ws.html 发送消息和接收消息的页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <title>spring-boot-WebSocket-广播式</title>
 <link rel="stylesheet" type="text/css" value="">
</head>
<body onload="disconnect()">
<noscript>
 <h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2>
</noscript>
​
​
<div>
 <h3>WebSocket</h3>
</div>
​
<div>
 <div>
 <button id="connect" onclick="connect();">连接</button>
 <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
 </div>
​
 <div id="conversationDiv">
 <label>输入你的名字</label>
 <input type="text" id="name" />
 <button id="sendName" onclick="sendName();">发送</button>
 <p id="response"></p>
​
 </div>
</div>
​
​
​
​
​
<!--<script src="/static/js/sockjs.min.js" charset="utf-8"></script>-->
<!--<script src="/static/js/stomp.min.js" charset="utf-8"></script>-->
​
<script th:src="@{/static/js/jquery.min.js}"></script>
<script th:src="@{/static/js/sockjs.min.js}"></script>
<script th:src="@{/static/js/stomp.min.js}"></script>
<script type="application/javascript">
​
 var stompClient = null;
​
 function setConnected(connected) {
 console.log('Connected status: ' + connected);
 document.getElementById("connect").disabled = connected;
 document.getElementById("disconnect").disabled = !disconnect;
 document.getElementById("conversationDiv").style.visibility= (connected ? 'visible' : 'hidden');
​
 // $("#conversationDiv").style.visibility = (connected ? 'visible' : 'hidden');
 $('response').html();
 }
​
 /**
 * 打开连接
 */
 function connect() {
 // 1、连接SockJS的endpoint
 var socket = new SockJS('/endpointSSH');
 // 2、使用STOMP 子协议的WebSocket客户端
 stompClient = Stomp.over(socket);
 // 3、连接websockst服务端
 stompClient.connect({}, function (frame) {
 setConnected(true);
 console.log('Connected: ' + frame);
 // 4、 通过stomp.subscribe订阅/topic/getResponse目标(destination)发生的消息,后端在@SendTo定义
 stompClient.subscribe('/topic/getResponse', function (respose) {
 showResponse(JSON.parse(respose.body).responseMessage);
 });
 });
 }
​
 /**
 * 关闭连接
 */
 function disconnect() {
 if (stompClient != null) {
 stompClient.disconnect();
 }
 setConnected(false);
 console.log("Disconnected");
 }
​
 function sendName() {
 var name = $('#name').val();
 // 5、通过 stompClient.send 向/welcome 目标发送消息 服务端在@MessageMapping中定义的
 stompClient.send("/welcome", {}, JSON.stringify({'name': name}));
 }
​
 function showResponse(message) {
 var response = $("#response");
 response.html(message);
 }
​
</script>
</body>
</html>
​
展示结果:
websocket-广播式.png

在之前学习过socket连接对象也可通过WebSocket(不通过SockJS)连接

var socket = new WebSocket(url);

https://www.jianshu.com/p/bd0667b270ca

目前的是通过sockjs来

1、连接SockJS的endpoint

2、使用STOMP 子协议的WebSocket客户端

3、连接websockst服务端

4、 通过stomp.subscribe订阅/topic/getResponse目标(destination)发生的消息,后端在@SendTo定义

5、通过 stompClient.send 向/welcome 目标发送消息 服务端在@MessageMapping中定义的

STOMP帧由命令,一个或多个头信息、一个空行及负载(文本或字节)所组成;

其中可用的COMMAND 包括:

CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT;

数据执行流程

CONNECT accept-version:1.1,1.0 heart-beat:10000,10000

连接成功的返回为:

<<< CONNECTED version:1.1 heart-beat:0,0

订阅目标(destination)/topic/getResponse:

SUBSCRIBE id:sub-0 destination:/topic/getResponse

向目标(destination)/welcome 发生消息的格式为:

SEND destination:/welcome content-length:17

{"name":"elijah"}

从目标(destination)/topic/getResponse接收的格式为:

<<< MESSAGE destination:/topic/getResponse content-type:application/json;charset=UTF-8 subscription:sub-0 message-id:5nd0pfjf-73 content-length:38

{"responseMessage":"Welcome, elijah!"}

二、点对点式通信:

点对点多用于聊天室,一对一的通信,这里是基础的登录(springsecurity)到聊天室然后进行两天

代码示例

相关登录配置
1、pom.xml 引入jar
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
 </dependency>
2、WebSecurityConfig.java 鉴权配置
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
​
/**
 * @description: 登录时的鉴权配置
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-15 18:31
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
​
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http
 .authorizeRequests()
 // /和login不拦截
 .antMatchers("/", "login").permitAll()
 .anyRequest().authenticated()
 .and()
 .formLogin()
 // 页面访问路径
 .loginPage("/login")
 // 登录成功转向/char
 .defaultSuccessUrl("/chat")
 .permitAll()
 .and()
 .logout()
 .permitAll();
​
 }
​
 /**
 * 内存中分配两个用户
 * @param auth
 * @throws Exception
 */
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 auth
 .inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
 .withUser("elijah")
 .password(new BCryptPasswordEncoder().encode("elijah"))
 .roles("USER")
 .and()
 .withUser("ssh")
 .password(new BCryptPasswordEncoder().encode("ssh"))
 .roles("USER");
 }
​
 /**
 * 静态资源不拦截
 * @param web
 * @throws Exception
 */
 @Override
 public void configure(WebSecurity web) throws Exception {
 web.ignoring().antMatchers("/resource/static/**");
 }
}
3、login.html 登录页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <title>spring-boot-login</title>
 <link rel="stylesheet" type="text/css" value="">
</head>
<body>
​
<div>
 <h3>登录</h3>
</div>
​
 <div th:if="${param.error}">
 无效的账号和密码
 </div>
 <div th:if="${param.logout}">
 你已注销
 </div>
​
<form th:action="@{/login}" method="post">
 <div>
 <label>
 账号:  <input type="text" name="username" />
 </label>
 </div>
 <div>
 <label>
 密码:  <input type="password" name="password" />
 </label>
 </div>
 <div>
 <input type="submit" value="登陆"/>
 </div>
</form>
</body>
</html>
聊天代码
1、相关配置

WebSocketConfig.java

WsController.java

方法在上面广播式代码里

两个页面也需要配置

2、char.html 聊天窗口
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <title>spring-boot-WebSocket-点对点式-home</title>
 <link rel="stylesheet" type="text/css" value="">
</head>
<body>
​

<p>聊天室</p>
​
<form id="elijahForm">
 <textarea rows="4" cols="60" name="text"></textarea>
 <input type="submit">
</form>
​
​
​
​
<script th:src="@{/static/js/jquery.min.js}"></script>
<script th:src="@{/static/js/sockjs.min.js}"></script>
<script th:src="@{/static/js/stomp.min.js}"></script>
<script type="application/javascript">
​
​
 $('#elijahForm').submit(function (e) {
 e.preventDefault();
 var text = $('#elijahForm').find('textarea[name="text"]').val();
 sendSpittle(text);
 });
​
 // 1、连接SockJS的endpoint
 var socket = new SockJS('/endpointChat');
 // 2、使用STOMP 子协议的WebSocket客户端
 stomp = Stomp.over(socket);
 // 3、连接websockst服务端 //默认的和STOMP端点连接
 stomp.connect("guest", "guest", function (frame) {
 // 4、 通过stomp.subscribe订阅/topic/getResponse目标(destination)发生的消息,后端在@SendTo定义
 stomp.subscribe("/user/queue/notifications", function (message) {
 debugger;
 var content = message.body;
 var obj = JSON.parse(content);
 console.log("admin用户特定的消息1:" + obj.message)
 console.log("收到一条新消息:" + JSON.parse(respose.message).responseMessage)
 $('#output').append("<b>Received: " + message.body + "</b><br/>")
 });
 });
​
​
 function sendSpittle(text) {
 stomp.send("/chat", {}, text);
 }
​
 $('#stop').click(function () {
 socket.close();
 })
</script>
​
<div id="output"></div>
​
</body>
</html>
​
展示结果:
websocket-点对点式.png

三、其他说明:

1、基本概念:

STOMP:

STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议

如何理解 STOMP 与 WebSocket 的关系: 1) HTTP协议解决了 web 浏览器发起请求以及 web 服务器响应请求的细节,假设 HTTP 协议 并不存在,只能使用 TCP 套接字来 编写 web 应用,你可能认为这是一件疯狂的事情;

  1. 直接使用 WebSocket(SockJS) 就很类似于 使用 TCP 套接字来编写 web 应用,因为没有高层协议,就需要我们定义应用间所发送消息的语义,还需要确保连接的两端都能遵循这些语义;

  2. 同 HTTP 在 TCP 套接字上添加请求-响应模型层一样,STOMP 在 WebSocket 之上提供了一个基于帧的线路格式层,用来定义消息语义;

2、所遇到的坑:

使用springsecurity时在内容中设置密码没有处理会报错

There is no PasswordEncoder mapped for the id "null"

是高版本的security所导致

.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
 .withUser("elijah")
 .password(new BCryptPasswordEncoder().encode("elijah"))
 .roles("USER")

参考文档:

https://blog.csdn.net/jqsad/article/details/77745379

https://www.jianshu.com/p/bd0667b270ca

参考书籍汪云飞 SpringBoot实战

相关文章

  • WebSocket广播式和点对点的通信【原创】

    本篇文章主要介绍websocket的两种通信,广播式和点对点的通信。 一、广播式通讯 类似广播一样,只要发出,订阅...

  • 2018-08-20

    SpringBoot项目中使用WebSocket配置广播式通信 通过@MessageMapping映射/welco...

  • 【笔记】谢希仁—计网五版:chapter three 数据链路层

    数据链路层使用的信道有以下两种类型: ①点对点信道,一对一的点对点通信方式 ②广播信道,一对多的广播通信方式,复杂...

  • 链路层之多路访问

    链路层信道类型 点对点通信链路(Point-to-Point Protocol) 广播通信链路(Broad...

  • 计网之数据链路层

    数据链路层使用的信道主要有如下两种:点对点信道 一对一的点对点通信方式广播信道 一对多的广播通信方式 数据链路层的...

  • SpringBoot2.x集成WebSocket实现广播和点对点

     WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket 协议在2008年诞生,201...

  • 数据链路层

    数据链路层 数据链路层使用的信道主要以下两种类型: 点对点信道。这种信道使用的一对一的点对点通信方式 广播信道。 ...

  • 数据链路层(一)

    数据链路层使用的信道主要有以下两种类型: 点对点信道:这种信道使用一对一的点对点通信方式。 广播信道:这种信道使用...

  • 计算机网络第三章数据链路层

    数据链路层使用的信道主要是有以下两种类型: 点对点信道。这种信道使用一对一的点对点通信方式。 广播信道。这种信道使...

  • 网络 Conclusion

    1. TCP和UDP TCP/UDP详细分析TCP是点对点的通信,UDP是广播TCP控制了数据包的发送序列的产生,...

网友评论

    本文标题:WebSocket广播式和点对点的通信【原创】

    本文链接:https://www.haomeiwen.com/subject/puxhlctx.html