本章节将介绍 Spring Boot 集成 WebSocket 的两种主要方式:原生注解与Spring封装。
在线WebSocket测试工具
🤖 Spring Boot 2.x 实践案例(代码仓库)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
@Configuration
public class WebSocketConfiguration {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {
/**
* 线程安全的无序的集合
*/
private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();
/**
* 存储在线连接数
*/
private static final Map<String, Session> SESSION_POOL = new HashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
SESSIONS.add(session);
SESSION_POOL.put(userId, session);
log.info("【WebSocket消息】有新的连接,总数为:" + SESSIONS.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session) {
try {
SESSIONS.remove(session);
log.info("【WebSocket消息】连接断开,总数为:" + SESSIONS.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnMessage
public void onMessage(String message) {
log.info("【WebSocket消息】收到客户端消息:" + message);
}
/**
* 此为广播消息
*
* @param message 消息
*/
public void sendAllMessage(String message) {
log.info("【WebSocket消息】广播消息:" + message);
for (Session session : SESSIONS) {
try {
if (session.isOpen()) {
session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 此为单点消息
*
* @param userId 用户编号
* @param message 消息
*/
public void sendOneMessage(String userId, String message) {
Session session = SESSION_POOL.get(userId);
if (session != null && session.isOpen()) {
try {
synchronized (session) {
log.info("【WebSocket消息】单点消息:" + message);
session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 此为单点消息(多人)
*
* @param userIds 用户编号列表
* @param message 消息
*/
public void sendMoreMessage(String[] userIds, String message) {
for (String userId : userIds) {
Session session = SESSION_POOL.get(userId);
if (session != null && session.isOpen()) {
try {
log.info("【WebSocket消息】单点消息:" + message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
处理器作用类似于
@RequestMapping注解,用于处理某一个路径的WebSocket连接,自定义处理器需要实现WebSocketHandler接口。
public interface WebSocket {
/**
* 会话开始回调
*
* @param session 会话
*/
void handleOpen(WebSocketSession session);
/**
* 会话结束回调
*
* @param session 会话
*/
void handleClose(WebSocketSession session);
/**
* 处理消息
*
* @param session 会话
* @param message 接收的消息
*/
void handleMessage(WebSocketSession session, String message);
/**
* 发送消息
*
* @param session 当前会话
* @param message 要发送的消息
* @throws IOException 发送io异常
*/
void sendMessage(WebSocketSession session, String message) throws IOException;
/**
* 发送消息
*
* @param userId 用户id
* @param message 要发送的消息
* @throws IOException 发送io异常
*/
void sendMessage(String userId, TextMessage message) throws IOException;
/**
* 发送消息
*
* @param userId 用户id
* @param message 要发送的消息
* @throws IOException 发送io异常
*/
void sendMessage(String userId, String message) throws IOException;
/**
* 发送消息
*
* @param session 当前会话
* @param message 要发送的消息
* @throws IOException 发送io异常
*/
void sendMessage(WebSocketSession session, TextMessage message) throws IOException;
/**
* 广播
*
* @param message 字符串消息
* @throws IOException 异常
*/
void broadCast(String message) throws IOException;
/**
* 广播
*
* @param message 文本消息
* @throws IOException 异常
*/
void broadCast(TextMessage message) throws IOException;
/**
* 处理会话异常
*
* @param session 会话
* @param error 异常
*/
void handleError(WebSocketSession session, Throwable error);
/**
* 获得所有的 websocket 会话
*
* @return 所有 websocket 会话
*/
Set<WebSocketSession> getSessions();
/**
* 得到当前连接数
*
* @return 连接数
*/
int getConnectionCount();
}
@Slf4j
public class WebSocketImpl implements WebSocket {
/**
* 在线连接数(线程安全)
*/
private final AtomicInteger connectionCount = new AtomicInteger(0);
/**
* 线程安全的无序集合(存储会话)
*/
private final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
@Override
public void handleOpen(WebSocketSession session) {
sessions.add(session);
int count = connectionCount.incrementAndGet();
log.info("a new connection opened,current online count:{}", count);
}
@Override
public void handleClose(WebSocketSession session) {
sessions.remove(session);
int count = connectionCount.decrementAndGet();
log.info("a new connection closed,current online count:{}", count);
}
@Override
public void handleMessage(WebSocketSession session, String message) {
// 只处理前端传来的文本消息,并且直接丢弃了客户端传来的消息
log.info("received a message:{}", message);
}
@Override
public void sendMessage(WebSocketSession session, String message) throws IOException {
this.sendMessage(session, new TextMessage(message));
}
@Override
public void sendMessage(String userId, TextMessage message) throws IOException {
Optional<WebSocketSession> userSession = sessions.stream().filter(session -> {
if (!session.isOpen()) {
return false;
}
Map<String, Object> attributes = session.getAttributes();
if (!attributes.containsKey("uid") {
return false;
}
String uid = (String) attributes.get("uid");
return uid.equals(userId);
}).findFirst();
if (userSession.isPresent()) {
userSession.get().sendMessage(message);
}
}
@Override
public void sendMessage(String userId, String message) throws IOException {
this.sendMessage(userId, new TextMessage(message));
}
@Override
public void sendMessage(WebSocketSession session, TextMessage message) throws IOException {
session.sendMessage(message);
}
@Override
public void broadCast(String message) throws IOException {
for (WebSocketSession session : sessions) {
if (!session.isOpen()) {
continue;
}
this.sendMessage(session, message);
}
}
@Override
public void broadCast(TextMessage message) throws IOException {
for (WebSocketSession session : sessions) {
if (!session.isOpen()) {
continue;
}
session.sendMessage(message);
}
}
@Override
public void handleError(WebSocketSession session, Throwable error) {
log.error("websocket error:{},session id:{}", error.getMessage(), session.getId());
log.error("", error);
}
@Override
public Set<WebSocketSession> getSessions() {
return sessions;
}
@Override
public int getConnectionCount() {
return connectionCount.get();
}
}
public class DefaultWebSocketHandler implements WebSocketHandler {
@Autowired
private WebSocket webSocket;
/**
* 建立连接
*
* @param session Session
*/
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
webSocket.handleOpen(session);
}
/**
* 接收消息
*
* @param session Session
* @param message 消息
*/
@Override
public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage<?> message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
webSocket.handleMessage(session, textMessage.getPayload());
}
}
/**
* 发生错误
*
* @param session Session
* @param exception 异常
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
webSocket.handleError(session, exception);
}
/**
* 关闭连接
*
* @param session Session
* @param closeStatus 关闭状态
*/
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) {
webSocket.handleClose(session);
}
/**
* 是否支持发送部分消息
*
* @return false
*/
@Override
public boolean supportsPartialMessages() {
return false;
}
}
自定义处理器需要实现
HandshakeInterceptor接口
public class WebSocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(@NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response, @NonNull WebSocketHandler wsHandler, @NonNull Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
// 模拟用户(通常利用JWT令牌解析用户信息)
String userId = servletServerHttpRequest.getServletRequest().getParameter("uid");
// TODO 判断用户是否存在
attributes.put("uid", userId);
return true;
}
return false;
}
@Override
public void afterHandshake(@NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response, @NonNull WebSocketHandler wsHandler, Exception exception) {
}
}
WebSocket 无法使用 header 传递参数,因此这里使用 url params 携带参数。
将自定义处理器、拦截器以及WebSocket操作类依次注入到IOC容器中。
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
@Bean
public DefaultWebSocketHandler defaultWebSocketHandler() {
return new DefaultWebSocketHandler();
}
@Bean
public WebSocket webSocket() {
return new WebSocketImpl();
}
@Bean
public WebSocketInterceptor webSocketInterceptor() {
return new WebSocketInterceptor();
}
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
registry.addHandler(defaultWebSocketHandler(), "ws/message")
.addInterceptors(webSocketInterceptor())
.setAllowedOrigins("*");
}
}




我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W
前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon
转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev
我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d
我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li
三分钟集成Tap防沉迷SDK(Unity版)一、SDK介绍基于国家对上线所有游戏必须增加防沉迷功能的政策下,TapTap推出防沉迷SDK,供游戏开发者进行接入;允许未成年用户在周五、六、日以及法定节假日晚上8:00-9:00进行游戏,防沉谜时间段进入游戏会弹窗进行提示!开发环境要求:Unity2019.4或更高版本iOS10或更高版本Android5.0(APIlevel21)或更高版本🔗Unity集成Demo参考链接🔗UnityTapSDK功能体验APK下载链接二、集成前准备1.创建应用进入开发者后台,按照提示开始创建应用;2.开通服务在使用TDS实名认证和防沉迷服务之前,需要在上面创建的应
IntrductionLibwebsocketsisasimple-to-use,MIT-license,pureClibraryprovidingclientandserverforhttp/1,http/2,websockets,MQTTandotherprotocolsinasecurity-minded,lightweight,configurable,scalableandflexibleway.It’seasytobuildandcross-buildviacmakeandissuitablefortasksfromembeddedRTOSthroughmasscloudservi
目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一