websocket协议是基于Tcp的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信–允许服务器主动发送信息给客户端。
websocket使用场景:
socketjs是什么:
学习资料:socketjs
stompjs是什么:
学习资料:stompjs
简单websocket游戏公告系统《一》主要内容:springboot框架搭建和maven依赖资料地址:spring提供的WebSocket教学
资料:webjars
使用Springboot构建基于STOMP的WebSocket广播式通信
Spring中的WebSocket架构
架构图:
图中各个组件介绍:
生产者型客户端(左上组件): 发送SEND命令到某个目的地址(destination)的客户端。
消费者型客户端(左下组件): 订阅某个目的地址(destination), 并接收此目的地址所推送过来的消息的客户端。
request channel: 一组用来接收生产者型客户端所推送过来的消息的线程池。
response channel: 一组用来推送消息给消费者型客户端的线程池。
broker: 消息队列管理者,也可以成为消息代理。它有自己的地址(例如“/topic”),客户端可以向其发送订阅指令,它会记录哪些订阅了这个目的地址(destination)。
应用目的地址(图中的”/app”): 发送到这类目的地址的消息在到达broker之前,会先路由到由应用写的某个方法。相当于对进入broker的消息进行一次拦截,目的是针对消息做一些业务处理。
非应用目的地址(图中的”/topic”,也是消息代理地址): 发送到这类目的地址的消息会直接转到broker。不会被应用拦截。
SimpAnnotatonMethod: 发送到应用目的地址的消息在到达broker之前, 先路由到的方法. 这部分代码是由应用控制的。
我们先建立服务器
首先是配置类代码:
package net.xdclass.websocketstudy.config;
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;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
注册端点,发布或者订阅消息的时候需要连接此端点
//withSockJS表示开始socketjs支持
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpoint-websocket").setAllowedOriginPatterns("*").withSockJS();
}
///配置消息代理中介
//enableSimpleBroker 服务端推送给客户端的路径前缀
//setApplicationDestinationPrefixes 客户端推送给服务端的路径前缀
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic","/chat");///启动SimpleBroker,使得订阅到此"topic"前缀的客户端可以收到公告
registry.setApplicationDestinationPrefixes("/app"); 在客户端推送给服务端也就是/app/v1/chat,这样就会被gameInfo方法来处理
}
}
ApplicationDestinationPrefixes就类似于simpAnnotationMethod
定义两个类,来分别表示客户端传入服务端的消息和服务端传入客户端的消息
package net.xdclass.websocketstudy.model;
import java.util.Date;
public class InMessage {
private String from;
private String to;
private String content;
private Date data;
public InMessage() {
}
public InMessage(String from, String to, String content, Date data) {
this.from = from;
this.to = to;
this.content = content;
this.data = data;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getData() {
return data;
}
public void setData(Date data) {
this.data = data;
}
}
```java
package net.xdclass.websocketstudy.model;
import java.util.Date;
public class OutMessage {
private String from;
private String content;
private Date time=new Date();
public OutMessage() {
}
public OutMessage(String content) {
this.content = content;
}
public OutMessage(String from, String content, Date time) {
this.from = from;
this.content = content;
this.time = time;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
}
接着就是controller方法
```java
package net.xdclass.websocketstudy.controller.v1;
import net.xdclass.websocketstudy.model.InMessage;
import net.xdclass.websocketstudy.model.OutMessage;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.messaging.handler.annotation.MessageMapping;
import javax.websocket.OnMessage;
@Controller
public class GameInfoController {
//MessageMapping也是一个路由,他和RequestMapping也是一样的路径映射,只不过它是针对websocket去使用的。
@MessageMapping("v1/chat")
/// SendTo就是发送到SimpleBroker,然后再由SimpleBroker转发到订阅到这个路径的客户。
@SendTo("/topic/game_chat")
public OutMessage gameInfo(InMessage message)
{
return new OutMessage(message.getContent());
}
}
然后我们看js代码
var stompClient = null;
/*
这个函数就是防止重复连接
*/
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#notice").html("");
}
function connect() {
var socket = new SockJS('/endpoint-websocket'); //连接上端点(基站)
stompClient = Stomp.over(socket); //用stom进行包装,规范协议
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/game_chat', function (result) {让这个客户端订阅到/topic/game_chat目的,然后只要有消息发送到该地址
console.info(result)///这个客户端就会接收到消息放入result中。
showContent(JSON.parse(result.body));
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {这个函数是管理员发送公告,也就是客户端向服务端发送东西所以是以/app开头的,然后经过服务端处理,最后又会发送到
/topic/game_chat中然后订阅该地址的客户端就能看到消息啦。
stompClient.send("/app/v1/chat", {}, JSON.stringify({'content': $("#content").val()}));
}
function showContent(body) {
$("#notice").append("" + body.content + " "+new Date(body.time).toLocaleString()+" ");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
简介:
讲解websocket推送方式:@SendTo注解和SimpMessagingTemplate的区别
笔记:
1、SendTo 不通用,固定发送给指定的订阅者
2、SimpMessagingTemplate 灵活,支持多种发送方式
我们可以将
SimpMessagingTemplate放在一个service包中。
package net.xdclass.websocketstudy.service;
import net.xdclass.websocketstudy.model.InMessage;
import net.xdclass.websocketstudy.model.OutMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
/*
简单消息模板,同来推送消息
*/
@Service
public class WebSocketService {
@Autowired
private SimpMessagingTemplate template;
public void sendTopicMessage(String dest,InMessage message)
{
for(int i=0;i<20;i++)
template.convertAndSend(dest,new OutMessage((message.getContent())+i));
}
}
然后再去调用
package net.xdclass.websocketstudy.controller.v2;
import net.xdclass.websocketstudy.model.InMessage;
import net.xdclass.websocketstudy.service.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
@Controller
public class V2GameInfoController {
@Autowired
private WebSocketService ws;
//MessageMapping也是一个路由,他和RequestMapping也是一样的路径映射,只不过它是针对websocket去使用的。
@MessageMapping("v2/chat")
/// SendTo就是发送到SimpleBroker,然后再由SimpleBroker转发到订阅到这个路径的客户。
//@SendTo("/topic/game_chat")
public void gameInfo(InMessage message){
ws.sendTopicMessage("/topic/game_rank",message);
}
}
简介:
SpringBoot里面websocekt监听器的使用,包含订阅、取消订阅,socekt连接和断开连接4类监听器的编写和使用
笔记:
注意点:
1、需要监听器类需要实现接口ApplicationListener T表示事件类型,下列几种都是对应的websocket事件类型
2、在监听器类上注解 @Component,spring会把改类纳入管理
websocket模块监听器类型:
SessionSubscribeEvent 订阅事件
SessionUnsubscribeEvent 取消订阅事件
SessionDisconnectEvent 断开连接事件
SessionDisconnectEvent 建立连接事件
例如:
package net.xdclass.websocketstudy.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
@Component
public class ConnectionEventListener implements ApplicationListener<SessionConnectEvent> {
@Override
public void onApplicationEvent(SessionConnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
System.out.println("[ConnectionEventListener监听器事件 类型]"+headerAccessor.getCommand().getMessageType());
}
}
package net.xdclass.websocketstudy.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
@Component
public class SubscribeEventListener implements ApplicationListener<SessionSubscribeEvent> {
@Override
public void onApplicationEvent(SessionSubscribeEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
System.out.println("[监听器事件 类型]"+headerAccessor.getCommand().getMessageType());
}
}
简介:
使用socketjs订阅API,进行点对点聊天;StompHeaderAccessor简单介绍
StompHeaderAccessor简单消息传递协议中处理消息头的基类,
通过这个类,可以获取消息类型(例如:发布订阅,建立连接断开连接),会话id等。
@Controller
public class V3GameInfoController {
@Autowired
private WebSocketService ws;
//MessageMapping也是一个路由,他和RequestMapping也是一样的路径映射,只不过它是针对websocket去使用的。
@MessageMapping("v3/single/chat")
/// SendTo就是发送到SimpleBroker,然后再由SimpleBroker转发到订阅到这个路径的客户。
//@SendTo("/topic/game_chat")
public void singleChat(InMessage message){
ws.sendChatMessage(message);
}
}
public void sendChatMessage(InMessage message)
{
template.convertAndSend("/chat/single/"+message.getTo(),new OutMessage(message.getFrom()+" 发送:"+message.getContent()));
}
这样就能实现点对点聊天。
简介:
websocket结合springboot的注解Scheduled实现定时推送,使用服务端定时推送注意事项;
开发简单监控JVM监控功能
笔记:
1、在controller的类方法上标注 @Scheduled(fixedRate = 3000) 表示这个方法会定时执行
fixedRate表示是多少毫秒 3000就3秒
2、需要在springboot启动类上@EnableScheduling
3、被注解@Scheduled标记的方法,是不能有参数,不然会报错
类似的我们可以实现监控java虚拟机内存大小:
@Controller
public class V4ServerInfoController {
@Autowired
private WebSocketService ws;
@MessageMapping("/v4/schedule/push")
@Scheduled(fixedRate=3000)三秒调用///加了这个注解的方法不能带任何参数否则会报错
public void sendServerInfo()
{
ws.sendServerInfo();
}
}
public void sendServerInfo()
{
int processors=Runtime.getRuntime().availableProcessors();
long freeMem = Runtime.getRuntime().freeMemory();
long maxMem = Runtime.getRuntime().maxMemory();
String message=String.format("服务器可用处理器: %s;虚拟机空闲内存大小: %s; 最大内存大小: %s",processors,freeMem,maxMem);
template.convertAndSend("/topic/server_info",new OutMessage(message));
}
简介:
展示简单股票行情推送的效果,及介绍阿里云API市场,httpClient工具类的使用
笔记:
访问地址:localhost:8080/v5/index.html
阿里云API市场: https://market.aliyun.com/data?spm=5176.8142029.388261.183.346bc16fAs3slP
HttpClientUtils下载: https://github.com/aliyun/api-gateway-demo-sign-java
这样我们就可以利用阿里云API的数据然后通过服务器发送到客户端。
简介:
拦截器介绍,springBoot结合websocket相关拦截器使用,握手拦截器的开发和使用
笔记:
1、编写一个类,实现一个接口HandshakeInterceptor;写完之后需要在websocket配置里面启用
.addInterceptors(new HttpHandShakeIntecepter())
2、实现两个方法beforeHandshake和afterHandshake,在里面可以获取resuest和response
package net.xdclass.websocketstudy.intecepter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpSession;
import java.util.Map;
/*
http握手拦截器,可以通过这个类的方法获取request,和response
*/
@Component
public class HttpHandShakeIntecepter implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("握手拦截器 beforeHandshake");
if(request instanceof ServletServerHttpRequest)
{
ServletServerHttpRequest servletRequest= (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession();
String id = session.getId();
System.out.println("握手拦截器 beforeHandshake sessionId="+id);
attributes.put("sessionId",id);在这里放入之后到后面的listener也可以拿到
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("握手拦截器 afterHandshake");
}
}
我们可以拿到HttpServletRequest,在这里面我们可以干很多事情,
之后到监视器还能拿到我们在拦截器里面设置的数据。
System.out.println("[ConnectionEventListener监听器事件 类型]"+headerAccessor.getSessionAttributes().get("sessionId"));
简介:
用户状态功能相关接口开发和登录API接口开发
package net.xdclass.websocketstudy.controller.v6;
import net.xdclass.websocketstudy.service.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@Controller
public class UserChatController {
@Autowired
private WebSocketService ws;
///模拟数据库用户的数据
public static Map<String,String>userMap=new HashMap<>();
static{
userMap.put("jack","123");
userMap.put("mary","456");
userMap.put("tom","789");
userMap.put("tim","000");
userMap.put("小D","666");
}
public static Map<String,User>onlineUser=new HashMap<>();
static{
onlineUser.put("123",new User("admin","888"));
}
@RequestMapping(value = "login",method= RequestMethod.POST)
public String userlogin(@RequestParam(value="username",required = true) String username, @RequestParam(value = "pwd",
required = true) String pwd, HttpSession session)
{
String password = userMap.get(username);
if(pwd!=null&&pwd.equals(password))
{
User user=new User(username,pwd);
String sessionId=session.getId();
onlineUser.put(sessionId,user);
return "redirect:/v6/chat.html";
}
return "redirect:/v6/error.html";
}
}
简介:
channel频道拦截器使用讲解,结合StompHeaderAccessor实现用户上线下线功能
笔记:
1、ChannelInterceptorAdapter 频道拦截器适配器,具体实现的接口是ChannelIntecepter
2、需要ChannelInterceptorAdapter子类重写override对应的方法,实现自己的逻辑,主要是
public void postSend(Message message, MessageChannel channel, boolean sent)
3、ChannelInterceptorAdapter子类需要在配置Websocket的配置里面加入
4、在配置类里面加入
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors( new SocketChannelIntecepter());
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.interceptors( new SocketChannelIntecepter());
}
资料:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/messaging/support/ChannelInterceptorAdapter.html
握手拦截器 beforeHandshake
握手拦截器 beforeHandshake sessionId=17DD2BFBD372D1461B0FC27E0A030D26
握手拦截器 afterHandshake
SocketChannelIntecepter->preSend
SocketChannelIntecepter->postSend
SocketChannelIntecepter ->sessionId=17DD2BFBD372D1461B0FC27E0A030D26
connect sessionId=17DD2BFBD372D1461B0FC27E0A030D26
SocketChannelIntecepter->afterSendCompletion
[ConnectionEventListener监听器事件 类型]CONNECT
[ConnectionEventListener监听器事件 类型]17DD2BFBD372D1461B0FC27E0A030D26
SocketChannelIntecepter->preSend
SocketChannelIntecepter->postSend
SocketChannelIntecepter->afterSendCompletion
SocketChannelIntecepter->preSend
SocketChannelIntecepter->postSend
SocketChannelIntecepter ->sessionId=17DD2BFBD372D1461B0FC27E0A030D26
SocketChannelIntecepter->afterSendCompletion
[监听器事件 类型]SUBSCRIBE
由此可见
SocketChannelIntecepter是一个从外到里再从里到外的过程,使用这个拦截器,我咋感觉更像监听器,只要连接或者不连接订阅或者不订阅的时候我们都能监听到,这样我们就能做出用户上线或者下线的操作,只要用户下线,意思就是断开连接,然后我们可以通过StompHeaderAccessor获取到session,然后通过getCommand方法,获取状态,判断状态,如果断开连接,我们就把用户数据根据sessionId移除掉就好了。
package net.xdclass.websocketstudy.intecepter;
import net.xdclass.websocketstudy.controller.v6.UserChatController;
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.messaging.support.ChannelInterceptorAdapter;
/*
频道拦截器,类似管道,可以获取消息的一些meta数据
*/
public class SocketChannelIntecepter extends ChannelInterceptorAdapter {
/*
在消息实际发送到频道之前调用
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
System.out.println("SocketChannelIntecepter->preSend");
return super.preSend(message, channel);
}
/*
发送消息调用后立即调用
*/
@Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
System.out.println("SocketChannelIntecepter->postSend");
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(message);
if(headerAccessor.getCommand()==null)return ;
String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
System.out.println("SocketChannelIntecepter ->sessionId="+sessionId);
switch (headerAccessor.getCommand())
{
case CONNECT:
connect(sessionId);
break;
case DISCONNECT:
disconnect(sessionId);
break;
case SUBSCRIBE:
break;
case UNSUBSCRIBE:
break;
}
}
void connect(String sessionId)
{
System.out.println("connect sessionId="+sessionId);
}
void disconnect(String sessionId)
{
System.out.println("disconnect sessionId="+sessionId);
///用户下线操作
UserChatController.onlineUser.remove(sessionId);
}
/*
在发送完成之后完成调用,不管是否有异常发生,一般用于资源释放
*/
@Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
System.out.println("SocketChannelIntecepter->afterSendCompletion");
super.afterSendCompletion(message, channel, sent, ex);
}
}
简介:使用schdule注解,推送在线用户接口开发
@Scheduled(fixedRate = 2000)
public void onlinUser()
{
ws.sendOnlineUser(onlineUser);
}
实时推送我们的onlineUser。
public void sendOnlineUser(Map<String, User> onlineUser) {
String msg="";
for(Map.Entry<String,User>entry:onlineUser.entrySet())
{
msg=msg.concat(entry.getValue().getUsername()+" || ");
}
template.convertAndSend("/topic/onlineuser",new OutMessage(msg));
}
然后是多人在线聊天。
@MessageMapping("v6/chat")
//SimpMessageHeaderAccessor headerAccessor
public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor)
{
String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
User user=onlineUser.get(sessionId);
message.setFrom(user.getUsername());
ws.sendTopicChat(message);
}
多人聊天很简单,只要通过sessionId找到发出信息的用户,然后展示在客户端上就行了。