我的公众号:MarkerHub,Java 网站:https://markerhub.com
更多精选文章请点击:Java 笔记大全.md
小 Hub 领读:
websocket 是双工通信协定,STOMP 是繁难文本协定,让传输内容更加简略,socktJS 是为了解决浏览器不反对 websocket 协定切换到其余协定解决问题。
如果大家看了这篇文章不懂的话,我能够本人写篇文章介绍给大家,带 git 地址的。
- 西格玛的博客
- http://lrwinx.github.io/
本篇文章以 websocket 的原理和落地为外围,来叙述 websocket 的应用,以及相干利用场景。
[](#http 与 websocket “http 与 websocket”)http 与 websocket
如咱们所理解,http 连贯为一次申请一次响应 (request->response),必须为同步调用形式。
而 websocket 为一次连贯当前,会建设 tcp 连贯,后续客户端与服务器交互为全双工方式的交互方式,客户端能够发送音讯到服务端,服务端也可将音讯发送给客户端。
http,websocket
此图来源于 Websocket 协定的学习、调研和实现, 如有侵权问题,告知后,删除。
依据上图,咱们大抵能够理解到 http 与 websocket 之间的区别和不同。
[](# 为什么要应用 websocket “ 为什么要应用 websocket”) 为什么要应用 websocket
那么理解 http 与 websocket 之间的不同当前,咱们为什么要应用 websocket 呢?他的利用场景是什么呢?
我找到了一个比拟合乎 websocket 应用场景的形容
“The best fit for WebSocket is in web applications where the client and server need to exchange events at high frequency and with low latency.”
翻译: 在客户端与服务器端交互的 web 利用中,websocket 最适宜在高频率低提早的场景下,进行事件的替换和解决
此段来源于 spring websocket 的官网文档
理解以上常识后,我举出几个比拟常见的场景:
- 游戏中的数据传输
- 股票 K 线图数据
- 客服零碎
依据如上所述,各个系统都来应用 websocket 不是更好吗?
其实并不是,websocket 建设连贯之后,后边交互都由 tcp 协定进行交互,故开发的复杂度会较高。当然 websocket 通信,自身要思考的事件要比 HTTP 协定的通信思考的更多.
所以如果不是有特殊要求 (即 利用不是”高频率低提早”的要求), 须要优先思考 HTTP 协定是否能够满足。
比方新闻零碎,新闻的数据早晨 10 分钟 – 30 分钟,是能够承受的,那么就能够采纳 HTTP 的形式进行轮询 (polling) 操作调用 REST 接口。
当然有时咱们建设了 websocket 通信,并且心愿通过 HTTP 提供的 REST 接口推送给某客户端,此时须要思考 REST 接口承受数据传送给 websocket 中,进行广播式的通信形式。
至此,我曾经讲述了三种交互方式的应用场景:
- websocket 独立应用场景
- HTTP 独立应用场景
- HTTP 直达 websocket 应用场景
[](#websocket “websocket”)websocket
websocket 为一次 HTTP 握手后,后续通信为 tcp 协定的通信形式。
当然,和 HTTP 一样,websocket 也有一些约定的通信形式,http 通信形式为 http 结尾的形式, e.g. http://xxx.com/path ,websocket 通信形式则为 ws 结尾的形式, e.g. ws://xxx.com/path
SSL:
- HTTP: https://xxx.com/path
- WEBSOCKET: wss://xxx.com/path
websocket 通信
此图来源于 WebSocket 教程, 如有侵权问题,告知后,删除。
[](#SockJS “SockJS”)SockJS
正如咱们所知, websocket 协定尽管曾经被制订,过后还有很多版本的浏览器或浏览器厂商还没有反对的很好。
所以, SockJS, 能够了解为是 websocket 的一个备选计划。
那它如何规定备选计划的呢?
它大略反对这样几个计划:
- Websockets
- Streaming
- Polling
当然,开启并应用 SockJS 后,它会优先选用 websocket 协定作为传输协定,如果浏览器不反对 websocket 协定,则会在其余计划中,抉择一个较好的协定进行通信。
看一下目前浏览器的反对状况:
Supported transports, by browser
此图来源于 github: sockjs-client
所以,如果应用 SockJS 进行通信,它将在应用上保持一致,底层由它本人去抉择相应的协定。
能够认为 SockJS 是 websocket 通信层上的下层协定。
底层对于开发者来说是通明的。
[](#STOMP “STOMP”)STOMP
STOMP 中文为: 面向音讯的简略文本协定
websocket 定义了两种传输信息类型: 文本信息 和 二进制信息 (text and binary).
类型尽管被确定,然而他们的传输体是没有规定的。
当然你能够本人来写传输体,来规定传输内容。(当然,这样的复杂度是很高的)
所以, 须要用一种简略的文本传输类型来规定传输内容,它能够作为通信中的文本传输协定, 即交互中的高级协定来定义交互信息。
STOMP 自身能够反对流类型的网络传输协定: websocket 协定和 tcp 协定
它的格局为:
COMMAND
header1:value1
header2:value2
Body^@
SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*
^@
SEND
destination:/queue/trade
content-type:application/json
content-length:44
{"action":"BUY","ticker":"MMM","shares",44}^@
当然 STOMP 曾经利用于很多音讯代理中,作为一个传输协定的规定,如: RabbitMQ, ActiveMQ
咱们皆能够用 STOMP 和这类 MQ 进行音讯交互.
除了 STOMP 相干的代理外,实际上还提供了一个 stomp.js, 用于浏览器客户端应用 STOMP 音讯协定传输的 js 库。
让咱们很不便的应用 stomp.js 进行与 STOMP 协定相干的代理进行交互.
正如咱们所知,如果 websocket 内容传输信息应用 STOMP 来进行交互,websocket 也很好的于音讯代理器进行交互 (如: RabbitMQ, ActiveMQ)
这样就很好的提供了音讯代理的集成计划。
总结,应用 STOMP 的长处如下:
- 不须要自建一套自定义的音讯格局
- 现有 stomp.js 客户端 (浏览器中应用) 能够间接应用
- 能路由信息到指定音讯地点
- 能够间接应用成熟的 STOMP 代理进行播送 如: RabbitMQ, ActiveMQ
[](# 后端技术计划选型 “ 后端技术计划选型 ”) 后端技术计划选型
websocket 服务端选型: spring websocket
反对 SockJS, 开启 SockJS 后,可应答不同浏览器的通信反对
反对 STOMP 传输协定,可无缝对接 STOMP 协定下的音讯代理器 (如: RabbitMQ, ActiveMQ)
[](# 前端技术计划选型 “ 前端技术计划选型 ”) 前端技术计划选型
前端选型: stomp.js,sockjs.js
后端开启 SOMP 和 SockJS 反对后,前对应有对应的 js 库进行反对.
所以选用此两个库.
[](# 总结 “ 总结 ”) 总结
上述所用技术,是这样的逻辑:
- 开启 socktJS:
如果有浏览器不反对 websocket 协定,能够在其余两种协定中进行抉择,然而对于应用层来讲,应用起来是一样的。
这是为了反对浏览器不反对 websocket 协定的一种备选计划 - 应用 STOMP:
应用 STOMP 进行交互,前端能够应用 stomp.js 类库进行交互,音讯一 STOMP 协定格局进行传输,这样就规定了音讯传输格局。
音讯进入后端当前,能够将音讯与实现 STOMP 格局的代理器进行整合。
这是为了音讯对立治理,进行机器扩容时,可进行负载平衡部署 - 应用 spring websocket:
应用 spring websocket, 是因为他提供了 STOMP 的传输自协定的同时,还提供了 StockJS 的反对。
当然,除此之外,spring websocket 还提供了权限整合的性能,还有自带天生与 spring 家族等相干框架进行无缝整合。
[](# 利用背景 “ 利用背景 ”) 利用背景
2016 年,在公司与共事一起探讨和开发了公司外部的客服零碎,因为前端技能的有余,很多通信方面的问题,无奈亲自调试前端来解决问题。
因为公司技术架构体系以前后端拆散为主,故前端无奈帮助后端调试,后端无奈帮助前端调试
在加上 websocket 为公司刚启用的协定,理解的人不多,导致前后端调试问题重重。
一年后的明天,我打算将前端重温,本人来调试一下前后端,来挖掘一下之前联调的问题.
当然,前端,我只是思考 stomp.js 和 sockt.js 的应用。
[](# 代码阶段设计 “ 代码阶段设计 ”) 代码阶段设计
[](# 角色 “ 角色 ”) 角色
- 客服
- 客户
[](# 登录用户状态 “ 登录用户状态 ”) 登录用户状态
- 上线
- 下线
[](# 调配策略 “ 调配策略 ”) 调配策略
- 用户登陆后,应该依据用户角色进行调配
[](# 关系保留策略 “ 关系保留策略 ”) 关系保留策略
- 应该提供关系型保留策略: 思考内存式策略 (可用于测试),redis 式策略
备注: 优先应该思考实现 Inmemory 策略,用于测试,让关系保留策略与存储平台无关
[](# 通信层设计 “ 通信层设计 ”) 通信层设计
- 归类 topic 的播送设计 (通信形式: 1-n)
- 归类 queue 的单点设计 (通信形式: 1-1)
[](# 代码实现 “ 代码实现 ”) 代码实现
[](# 角色 -1 “ 角色 ”) 角色
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public enum Role {
CUSTOMER_SERVICE,
CUSTOMER;
public static boolean isCustomer(User user) {Collection<GrantedAuthority> authorities = user.getAuthorities();
SimpleGrantedAuthority customerGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + Role.CUSTOMER.name());
return authorities.contains(customerGrantedAuthority);
}
public static boolean isCustomerService(User user) {Collection<GrantedAuthority> authorities = user.getAuthorities();
SimpleGrantedAuthority customerServiceGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + Role.CUSTOMER_SERVICE.name());
return authorities.contains(customerServiceGrantedAuthority);
}
}
代码中 User 对象,为平安对象,即 spring 中 org.springframework.security.core.userdetails.User,为 UserDetails 的实现类。
User 对象中,保留了用户受权后的很多根底权限信息,和用户信息。
如下:
public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();}
办法 #isCustomer 和 #isCustomerService 用来判断用户以后是否是顾客或者是客服。
[](# 登录用户状态 -1 “ 登录用户状态 ”) 登录用户状态
public interface StatesManager {
enum StatesManagerEnum{
ON_LINE,
OFF_LINE
}
void changeState(User user , StatesManagerEnum statesManagerEnum);
StatesManagerEnum currentState(User user);
}
设计登录状态时,应存在登录状态治理相干的状态管理器,此管理器只负责更改用户状态和获取用户状态相干操作。
并不波及其余关联逻辑,这样的代码划分,更有助于面向接口编程的扩展性
[](# 调配策略 -1 “ 调配策略 ”) 调配策略
public interface DistributionUsers {void distribution(User user);
}
调配角色接口设计,只关注传入的用户,并不关注此用户是客服或者用户,具体须要如何去做,由具体的调配策略来决定。
[](# 关系保留策略 -1 “ 关系保留策略 ”) 关系保留策略
public interface RelationHandler {void saveRelation(User customerService,User customer);
List<User> listCustomers(User customerService);
void deleteRelation(User customerService,User customer);
void saveCustomerService(User customerService);
List<User> listCustomerService();
User getCustomerService(User customer);
boolean exist(User user);
User availableNextCustomerService();}
关系保留策略,亦是只关注关系保留相干,并不在乎于保留到哪个存储介质中。
实现类由 Inmemory 还是 redis 还是 mysql, 它并不专一。
然而,此处须要留神,对于这种关系保留策略,开发测试时,并不波及高可用,可将 Inmemory 先做进去用于测试。
开发性能同时,相干共事再来开发其余介质存储的策略,性能测试以及 UAT 相干测试时,应切换为此介质存储的策略再进行测试。
[](# 用户综合治理 “ 用户综合治理 ”) 用户综合治理
对于不同性能的实现策略,由各个性能本人来实现,在应用上,咱们仅仅依据接口编程即可。
所以,要将上述所有性能封装成一个工具类进行应用,这就是所谓的 设计模式: 门面模式
@Component
public class UserManagerFacade {
@Autowired
private DistributionUsers distributionUsers;
@Autowired
private StatesManager statesManager;
@Autowired
private RelationHandler relationHandler;
public void login(User user) {if (roleSemanticsMistiness(user)) {throw new SessionAuthenticationException("角色语义不清晰");
}
distributionUsers.distribution(user);
statesManager.changeState(user, StatesManager.StatesManagerEnum.ON_LINE);
}
private boolean roleSemanticsMistiness(User user) {Collection<GrantedAuthority> authorities = user.getAuthorities();
SimpleGrantedAuthority customerGrantedAuthority = new SimpleGrantedAuthority("ROLE_"+Role.CUSTOMER.name());
SimpleGrantedAuthority customerServiceGrantedAuthority = new SimpleGrantedAuthority("ROLE_"+Role.CUSTOMER_SERVICE.name());
if (authorities.contains(customerGrantedAuthority)
&& authorities.contains(customerServiceGrantedAuthority)) {return true;}
return false;
}
public void logout(User user){statesManager.changeState(user, StatesManager.StatesManagerEnum.OFF_LINE);
}
public User getCustomerService(User user){return relationHandler.getCustomerService(user);
}
public List<User> listCustomers(User user){return relationHandler.listCustomers(user);
}
public StatesManager.StatesManagerEnum getStates(User user){return statesManager.currentState(user);
}
}
UserManagerFacade 中注入三个相干的性能接口:
@Autowired
private DistributionUsers distributionUsers;
@Autowired
private StatesManager statesManager;
@Autowired
private RelationHandler relationHandler;
可提供:
- 登录 (#login)
- 登出 (#logout)
- 获取对应客服 (#getCustomerService)
- 获取对应用户列表 (#listCustomers)
- 以后用户登录状态 (#getStates)
这样的设计,可保障对于用户关系的治理都由 UserManagerFacade 来决定
其余外部的操作类,对于使用者来说,并不关怀,对开发来讲,不同性能的策略都是通明的。
[](# 通信层设计 - 登录,受权 “ 通信层设计 – 登录,受权 ”) 通信层设计 – 登录,受权
spring websocket 尽管并没有要求 connect 时,必须受权,因为连贯当前,会分发给客户端 websocket 的 session id,来辨别客户端的不同。
然而对于大多数利用来讲,登录受权当前,进行 websocket 连贯是最正当的,咱们能够进行权限的调配,和权限相干的治理。
我模仿例子中,应用的是 spring security 的 Inmemory 的相干配置:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("admin").password("admin").roles(Role.CUSTOMER_SERVICE.name());
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles(Role.CUSTOMER_SERVICE.name());
auth.inMemoryAuthentication().withUser("user").password("user").roles(Role.CUSTOMER.name());
auth.inMemoryAuthentication().withUser("user1").password("user").roles(Role.CUSTOMER.name());
auth.inMemoryAuthentication().withUser("user2").password("user").roles(Role.CUSTOMER.name());
auth.inMemoryAuthentication().withUser("user3").password("user").roles(Role.CUSTOMER.name());
}
@Override
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable()
.formLogin()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();}
}
绝对较为简单,创立 2 个客户,4 个普通用户。
当认证管理器认证后,会将认证后的非法认证平安对象 user(即 认证后的 token) 放入 STOMP 的 header 中.
此例中,认证治理认证之后,认证的 token 为 org.springframework.security.authentication.UsernamePasswordAuthenticationToken,
此 token 认证后,将放入 websocket 的 header 中。(即 后边谈判到的平安对象 java.security.Principal)
[](# 通信层设计 -websocket 配置 “ 通信层设计 – websocket 配置 ”) 通信层设计 – websocket 配置
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/portfolio").withSockJS();}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
}
此配置中,有几点需进行解说:
其中端点”portfolio”, 用于 socktJs 进行 websocket 连贯时应用,只用于建设连贯。
“/topic”,“/queue”, 则为 STOMP 的语义束缚,topic 语义为 1-n(播送机制),queue 语义为 1-1(单点机制)
“app”, 此为利用级别的映射起点前缀,这样说有些艰涩,一会看一下示例将会清晰很多。
[](# 通信层设计 - 创立连贯 “ 通信层设计 – 创立连贯 ”) 通信层设计 – 创立连贯
用于连贯 spring websocket 的端点为 portfolio,它可用于连贯,看一下具体实现:
<script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.min.js"></script>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
var socket = new SockJS("/portfolio");
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {showGreeting("登录用户:" + frame.headers["user-name"]);
});
这样便建设了连贯。后续的其余操作就能够通过 stompClient 句柄进行应用了。
[](# 通信层设计 -spring-websocket 音讯模型 “ 通信层设计 – spring websocket 音讯模型 ”) 通信层设计 – spring websocket 音讯模型
见模型图:
message-flow-simple-broker
此图起源 spring-websocket 官网文档
能够看出对于同肯定于指标都为:/topic/broadcast, 它的发送渠道为两种:/app/broadcast 和 / topic/broadcast
如果为 / topic/broadcast, 间接可将音讯体发送给定于指标(/topic/broadcast)。
如果是 / app/broadcast,它将音讯对应在 MessageHandler 办法中进行解决,解决后的后果发放到 broker channel 中,最初再讲音讯体发送给指标 (/topic/broadcast)
当然,这里边所说的 app 前缀就是方才咱们在 websocket 配置中的前缀.
看一个例子:
前端订阅:
stompClient.subscribe('/topic/broadcast', function(greeting){showGreeting(greeting.body);
});
后端服务:
@Controller
public class ChatWebSocket extends AbstractWebSocket{@MessageMapping("broadcast")
public String broadcast(@Payload @Validated Message message, Principal principal) {return "发送人:" + principal.getName() + "内容:" + message.toString();}
}
@Data
public class Message {@NotNull(message = "题目不能为空")
private String title;
private String content;
}
前端发送:
function sendBroadcast() {stompClient.send("/app/broadcast",{},JSON.stringify({'content':'message content'}));
}
这种发送将音讯发送给后端带有 @MessageMapping 注解的办法,而后组合完数据当前,在推送给订阅 / topic/broadcast 的前端
function sendBroadcast() {stompClient.send("/topic/broadcast",{},JSON.stringify({'content':'message content'}));
}
这种发送间接将音讯发送给订阅 / topic/broadcast 的前端,并不通过注解办法进行流转。
我置信上述这个了解曾经解释分明了 spring websocket 的音讯模型图
[](# 通信层设计 -MessageMapping “ 通信层设计 – @MessageMapping”) 通信层设计 – @MessageMapping
带有这个注解的 @Controller 下的办法,正是对应 websocket 中的直达数据的解决办法。
那么这个注解下的办法到底能够获取哪些数据,其中有什么原理呢?
办法阐明
我大略说一下:
Message,@Payload,@Header,@Headers,MessageHeaders,MessageHeaderAccessor, SimpMessageHeaderAccessor,StompHeaderAccessor
以上这些都是获取音讯头,音讯体,或整个音讯的根本对象模型。
@DestinationVariable
这个注解用于动静监听门路,很想 rest 中的 @PathVariable:
e.g.:
@MessageMapping("/queue/chat/{uid}")
public void chat(@Payload @Validated Message message, @DestinationVariable("uid") String uid, Principal principal) {String msg = "发送人:" + principal.getName() + "chat";
simpMessagingTemplate.convertAndSendToUser(uid,"/queue/chat",msg);
}
java.security.Principal
这个对象我须要重点说一下。
他则是 spring security 认证之后,产生的 Token 对象, 即本例中的 UsernamePasswordAuthenticationToken.
UsernamePasswordAuthenticationToken 类图
不难发现 UsernamePasswordAuthenticationToken 是 Principal 的一个实现.
能够将 Principal 间接转成受权后的 token, 进行操作:
UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) principal;
正如前边设计章节所说,整个用户设计都是对 org.springframework.security.core.userdetails.User 进行操作,那如何拿到 User 对象呢。
很简略, 如下:
UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) principal;
User user = (User) user.getPrincipal()
[](# 通信层设计 -1-1-amp-amp-1-n “ 通信层设计 – 1-1 && 1-n”) 通信层设计 – 1-1 && 1-n
1-n topic:
此形式,上述音讯模型章节曾经讲过,此处不再赘述
1-1 queue:
客服 – 用户沟通为 1-1 用户交互的案例
前端:
stompClient.subscribe('/user/queue/chat',function(greeting){showGreeting(greeting.body);
});
后端:
@MessageMapping("/queue/chat/{uid}")
public void chat(@Payload @Validated Message message, @DestinationVariable("uid") String uid, Principal principal) {String msg = "发送人:" + principal.getName() + "chat";
simpMessagingTemplate.convertAndSendToUser(uid,"/queue/chat",msg);
}
发送端:
function chat(uid) {stompClient.send("/app/queue/chat/"+uid,{},JSON.stringify({'title':'hello','content':'message content'}));
}
上述的转化,看上去没有 topic 那样 1-n 的播送要晦涩,因为代码中采纳约定的形式进行开发,当然这是由 spring 约定的。
约定转化的处理器为 UserDestinationMessageHandler。
大略的语义逻辑如下:
“An application can send messages targeting a specific user, and Spring’s STOMP support recognizes destinations prefixed with“/user/“for this purpose. For example, a client might subscribe to the destination“/user/queue/position-updates”. This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user session, e.g.“/queue/position-updates-user123”. This provides the convenience of subscribing to a generically named destination while at the same time ensuring no collisions with other users subscribing to the same destination so that each user can receive unique stock position updates.”
大抵的意思是说: 如果是客户端订阅了 / user/queue/position-updates, 将由 UserDestinationMessageHandler 转化为一个基于用户会话的订阅地址, 比方 / queue/position-updates-user123,而后能够进行通信。
例子中,咱们能够把 uid 当成用户的会话,因为用户 1-1 通信是通过 spring security 受权的,所以咱们能够把会话当做受权后的 token.
如登录用户 token 为: UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(“admin”,”user”);
且这个 token 是非法的,那么 / user/queue/chat 订阅则为 / queue/chat-admin
发送时,如果通过 / user/admin/queue/chat, 则不通过 @MessageMapping 间接进行推送。
如果通过 / app/queue/chat/admin, 则将音讯由 @MessageMapping 注解解决,最终发送给 / user/admin/queue/chat 起点
追踪代码 simpMessagingTemplate.convertAndSendToUser:
@Override
public void convertAndSendToUser(String user, String destination, Object payload, Map<String, Object> headers,
MessagePostProcessor postProcessor) throws MessagingException {Assert.notNull(user, "User must not be null");
user = StringUtils.replace(user, "/", "%2F");
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
}
阐明最初的门路仍然是 / user/admin/queue/chat 起点.
[](# 通信层设计 -SubscribeMapping “ 通信层设计 – @SubscribeMapping”) 通信层设计 – @SubscribeMapping
@SubscribeMapping 注解能够实现订阅即返回的性能。
这个很像 HTTP 的 request-response,但不同的是 HTTP 的申请和响应是同步的,每次申请必须失去响应。
而 @SubscribeMapping 则是异步的。意思是说:当订阅时,直到回应可响应时在进行解决。
[](# 通信层设计 - 异样解决 “ 通信层设计 – 异样解决 ”) 通信层设计 – 异样解决
@MessageMapping 是反对 jsr 303 校验的,它反对 @Validated 注解,可抛出谬误异样, 如下:
@MessageMapping("broadcast")
public String broadcast(@Payload @Validated Message message, Principal principal) {return "发送人:" + principal.getName() + "内容:" + message.toString();}
那异样如何解决呢
@MessageExceptionHandler, 它能够进行音讯层的异样解决
@MessageExceptionHandler
@SendToUser(value = "/queue/error",broadcast = false)
public String handleException(MethodArgumentNotValidException methodArgumentNotValidException) {BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
if (!bindingResult.hasErrors()) {return "未知谬误";}
List<FieldError> allErrors = bindingResult.getFieldErrors();
return "jsr 303 谬误:" + allErrors.iterator().next().getDefaultMessage();}
其中 @SendToUser,是指只将音讯发送给以后用户,当然,以后用户须要订阅 / user/queue/error 地址。
注解中 broadcast, 则表明音讯不进行多会话的流传 (有可能一个用户登录 3 个浏览器,有三个会话),如果此 broadcast=false,则只传给以后会话,不进行其余会话流传
本文从 websocket 的原理和协定,以及内容相干协定等不同维度进行了具体介绍。
最终以一个利用场景为例,从我的项目的结构设计,以及代码策略设计,设计模式等不同方面展现了 websocket 的通信性能在我的项目中的应用。
如何实现某一性能其实并不重要,重要的是得理解实践,深刻实践之后,再进行开发。
望此篇文章对你了解 websocket 有所帮忙。
举荐浏览
Java 笔记大全.md
太赞了,这个 Java 网站,什么我的项目都有!https://markerhub.com
这个 B 站的 UP 主,讲的 java 真不错!