import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author HFL
* @date 2022/5/16 14:49
* 配置类
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
/**
* @author HFL
* @date 2022/5/16 15:17
* Websocket应用实现:
* 1.建立连接,连接放入连接池
* 2.关闭连接,连接移出连接池
* 3.接收客户端发送的消息,并做出相应处理
* 4.注入业务层的service
* [注意:Spring管理的Bean是单例模式的,而WebSocket不是单例,注入时需要处理一下]
* 5.异常处理,连接移除连接池
*/
@Slf4j
@Component
@ServerEndpoint("/endPoint/{screen}")
public class WebSocketServer {
/**
* 建立连接成功调用 (Session + 场景)
*/
@OnOpen
public void onOpen(Session session,@PathParam("screen") String screen) throws IOException {
log.info("[onOpen][session({}) 接入, [screen: {}]", session, screen);
WebSocketServerPool.addDataConnect(session, screen);
//WebSocketServerPool.sendMessage(session, configurationScreenService.queryAllJsonById(screen));
}
/**
* 关闭连接时调用
* @param session 连接
*/
@OnClose
public void onClose(Session session, CloseReason closeReason) {
log.info("[onClose][session({}) 连接关闭。关闭原因是({})}]", session, closeReason);
WebSocketServerPool.removeConnect(session);
}
/**
* 错误时调用
* @param session 连接
* @param throwable 异常
*/
@OnError
public void onError(Session session, Throwable throwable) {
log.info("[onClose][session({}) 发生异常]", session, throwable);
WebSocketServerPool.removeConnect(session);
}
/**
* 收到客户端信息后,根据接收到的信息进行处理
* @param session 连接
* @param message 数据消息
*/
@OnMessage
public void onMessage(Session session, String message) {
log.info("[onOpen][session({}) 接收到一条消息({})]", session, message);
// TODO: 2022/5/18 对于客户端发送的指令信息,解析后进行对应的逻辑处理
}
}
import javax.websocket.Session;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author HFL
* @date 2022/5/16 9:39
* Websocket连接池、对连接池内连接操作 和数据推送方法
*/
public class WebSocketServerPool {
/**
* WebSocket连接池
*/
private static ConcurrentMap<Session, String> dataConnect = new ConcurrentHashMap<>();
/**
* 将websocket连接,放入连接池
* @param session websocket连接
* @param screen 场景ID
*/
public static void addDataConnect(Session session, String screen){
dataConnect.put(session, screen);
Iterator<Map.Entry<Session, String>> iterator = dataConnect.entrySet().iterator();
synchronized (iterator){
//移除失效连接
while(iterator.hasNext()){
Map.Entry<Session, String> entry = iterator.next();
Session sessionNew = entry.getKey();
Map<String, Object> userProperties = sessionNew.getUserProperties();
if(null != userProperties && null != userProperties.get("ReadyState") && "0" != String.valueOf(userProperties.get("ReadyState"))){
iterator.remove();
}
}
}
}
/**
* 将websocket连接从连接池中移除
* @param session websocket连接
*/
public static void removeConnect(Session session){
Iterator<Map.Entry<Session, String>> iterator = dataConnect.entrySet().iterator();
synchronized (iterator){
//主动移除连接
while (iterator.hasNext()){
if(session.equals(iterator.next().getKey())){
iterator.remove();
}
}
}
}
/**
* 获取连接池中所有连接
* @return 连接池所有数据
*/
public static ConcurrentMap<Session, String> getDataConnect(){
return dataConnect;
}
/**
* Websocket消息推送
* @param session 连接
* @param message 消息主体
* @throws IOException I/O异常
*/
public static void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
}
import com.ljgk.ems.maitreya.user.annotation.EnableLoginArgResolver;
import com.ljgk.ems.maitreya.validator.config.EnableFormValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author HFL
* @date 2022/5/13 8:40
* 仅做测试,只需要@SpringBootApplication注释即可,
* 其他注释,用于其他的功能,例如:
* @EnableScheduling用于扫描开启定时任务即@Scheduled注解的方法
*/
@SpringBootApplication
@EnableDiscoveryClient
@Configuration
@EnableFeignClients(value = {"com.ljgk.ems.maitreya"})
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@Slf4j
@EnableLoginArgResolver
@EnableFormValidator
@EnableScheduling
public class ServerApplication {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(ConfigurationServerApplication.class, args);
}
}
服务地址:ws://172.18.42.29:14785/endPoint/1
服务启动报错:

2022-06-09 10:31:27.616:[ERROR] [main:18941] [org.springframework.boot.SpringApplication.reportFailure:826] --> Application run failed
java.lang.IllegalStateException: Failed to register @ServerEndpoint class: class com.ljgk.ems.maitreya.configuration.websocket.WebSocketServer$$EnhancerBySpringCGLIB$$8a624780
at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:159)
at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoints(ServerEndpointExporter.java:134)
at org.springframework.web.socket.server.standard.ServerEndpointExporter.afterSingletonsInstantiated(ServerEndpointExporter.java:112)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:896)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.ljgk.ems.maitreya.ConfigurationServerApplication.main(ConfigurationServerApplication.java:36)
Caused by: javax.websocket.DeploymentException: UT003027: Class class com.ljgk.ems.maitreya.configuration.websocket.WebSocketServer$$EnhancerBySpringCGLIB$$8a624780 was not annotated with @ClientEndpoint or @ServerEndpoint
at io.undertow.websockets.jsr.ServerWebSocketContainer.addEndpointInternal(ServerWebSocketContainer.java:735)
at io.undertow.websockets.jsr.ServerWebSocketContainer.addEndpoint(ServerWebSocketContainer.java:628)
at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.java:156)
... 12 common frames omitted
原因:
WebSocketServer类被代理了,我出现这个问题的原因是做了整个项目的接口拦截,然后做了接口的日志切面处理,导致这个类被代理了,而@ServerEndpoint注解在处理WebSocketServer时,取到了代理的类,无法处理导致的异常。
解决办法:
将WebSocketServer类从日志处理的切面中排除掉即可。
加入业务处理时报错:

java.lang.NullPointerException: null
at com.ljgk.ems.maitreya.configuration.websocket.WebSocketServer.onOpen(WebSocketServer.java:47)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at io.undertow.websockets.jsr.annotated.BoundMethod.invoke(BoundMethod.java:87)
at io.undertow.websockets.jsr.annotated.AnnotatedEndpoint$3.run(AnnotatedEndpoint.java:158)
at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:170)
at io.undertow.websockets.jsr.ServerWebSocketContainer$1.call(ServerWebSocketContainer.java:167)
原因:
通过注解@Resource自动注入的ConfigurationScreenService的Bean为空,即Spring容器中,ConfigurationScreenService没有注入进去,因为Spring管理的Bean的作用域和WebSocket的作用域不同,Spring管理的Bean都是单例,WebSocket不是。
解决办法:
将截图中通过@Resource注解注入ConfigurationScreenService的方式换成下面方式:
/**
* @author HFL
* @date 2022/5/16 15:17
* Websocket应用实现:
* 1.建立连接,连接放入连接池
* 2.关闭连接,连接移出连接池
* 3.接收客户端发送的消息,并做出相应处理
* 4.注入业务层的service
* [注意:Spring管理的Bean是单例模式的,而WebSocket不是单例,注入时需要处理一下]
* 5.异常处理,连接移除连接池
*/
@Slf4j
@Component
@ServerEndpoint("/endPoint/{screen}")
public class WebSocketServer {
private static ConfigurationScreenService configurationScreenService;
@Resource
public void setConfigurationScreenService(ConfigurationScreenService configurationScreenService){
WebSocketServer.configurationScreenService = configurationScreenService;
}
/**
* 建立连接成功调用 (Session + 场景ID)
*/
@OnOpen
public void onOpen(Session session,@PathParam("screen") String screen) throws IOException {
log.info("[onOpen][session({}) 接入, [screen: {}]", session, screen);
WebSocketServerPool.addDataConnect(session, screen);
WebSocketServerPool.sendMessage(session, configurationScreenService.queryAllJsonById(screen));
}
...
}
定时任务,轮询连接池中的连接,并取到对于的场景、绑定的设备,即可查询最新数据,最后推送至客户端。(最简单实现)
import cn.hutool.json.JSONUtil;
import com.ljgk.ems.maitreya.configuration.entity.Content;
import com.ljgk.ems.maitreya.configuration.service.ConfigurationScreenDataService;
import com.ljgk.ems.maitreya.configuration.vm.ScreenDataJsonVm;
import com.ljgk.ems.maitreya.configuration.websocket.WebSocketServerPool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.websocket.Session;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
/**
* @author HFL
* @date 2022/6/88:35
*/
@Slf4j
@Component
public class ScreenPushScheduledTask {
@Resource
private ConfigurationScreenDataService configurationScreenDataService;
/**
* 5秒一次
* 定时场景数据推送
*/
@Scheduled(cron = "0/5 * * * * ? ")
public void executeScreenDataPush(){
try {
ConcurrentMap<Session, String> dataConnect = WebSocketServerPool.getDataConnect();
//查询待推送场景
dataConnect.keySet().forEach(session -> {
try {
String screen = dataConnect.get(session);
//查询需要的场景对应的元件需要的值,并按规则组装成JSON
List<ScreenDataJsonVm> screenDataJsonVms = configurationScreenDataService.queryDataJson(screen);
WebSocketServerPool.sendMessage(session, JSONUtil.toJsonStr(screenDataJsonVms));
} catch (IOException e) {
log.error("WebSocket SendMessage Error, Session:[{}], Exception : [{}]", session, e.getMessage());
}
});
}catch (Exception e){
log.error("WebSocket Scheduler Executor Error : [{}]", e.getMessage());
}
}
}
监听Binlog日志,将MySql中变化数据取出,推送至客户端。
RocketMq实现,将变化数据写入队列,WS服务端消费队列中数据时,推送至客户端。
1. WebSocket原理及技术简介
2. Java实现WebSocket服务端
3. Java Websocket——服务器端
4.【WebSocket】SpringBoot整合WebSocket 注入Bean的方式
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_