实现对客户端的在线统计,及与客户端的交互和接受redis的消息
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* ApplicationContextRegister
*
* class_comment
*
* Create Time: 2019/12/30 16:40
*
* @author ong
* @version 0.0.1
* @since core-be 0.0.1
*/
@Component
@Lazy(false)//不延时代表查询出对象A的时候,会把B对象也查询出来放到A对象的引用中,A对象中的B对象是有值的。
public class ApplicationContextRegister implements ApplicationContextAware {
private static ApplicationContext APPLICATION_CONTEXT;
/**
* 设置spring上下文 * * @param applicationContext spring上下文 * @throws BeansException * author:huochengyan https://blog.csdn.net/u010919083
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
APPLICATION_CONTEXT = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
}
首先要注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。要注意,如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* WebSocketConfig
*
* class_comment
*
* Create Time: 2019/12/9 15:58
*
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
如上图所示,就知道,为什么在组件类加@ServerEndpoint的注解了!
使用@ServerEndpoint创立websocket endpoint
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSONObject;
import com.a.b.c.commons.util.ApplicationContextRegister;
import com.a.b.c.commons.util.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* GrendWebSocket
*
* class_comment
*
* Create Time: 2019/12/9 16:18
*
* @author ong
* @version 0.0.1
* @since core-be 0.0.1
*/
@ServerEndpoint(value = "/trend/curve")
@Component
public class GrendWebSocket {
/**
* The constant LOG.
*/
private static Logger LOG = LoggerFactory.getLogger(GrendWebSocket.class);
private RedisUtil redisUtil;
public static GrendWebSocket GrendWebSocket;
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
private static CopyOnWriteArraySet queryTypes= new CopyOnWriteArraySet();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//某个客户端连接请求的数据类型
private String queryType;
//是否开始接受数据
private String status;
/**
* 连接建立成功调用的方法
* */
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
this.queryType = session.getQueryString(); //
this.status = "wait";
queryTypes.add(this.queryType);
LOG.info("有新连接加入!当前在线人数为" + getOnlineCount());
try {
sendMessage("连接成功");
LOG.info("请求数据类型为:${}",session.getQueryString());
} catch (IOException e) {
LOG.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
//queryTypes.remove(this.queryType);
subOnlineCount(); //在线数减1
LOG.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
LOG.error("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
}
/**
* 群发自定义消息
* */
public static void sendInfo(String queryType,String message) throws IOException {
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized Set getQueryTypes() {
return queryTypes;
}
public static synchronized void addOnlineCount() {
GrendWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
GrendWebSocket.onlineCount--;
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
LOG.info("来自客户端的消息:" + message);
//群发消息
if(message != null && "request_history".equals(message) ){
if(redisUtil == null){
ApplicationContext act = ApplicationContextRegister.getApplicationContext();
redisUtil= act.getBean(RedisUtil.class);
}
List
通过redis的lrange获取存储的数据并输出,如:
List
public void sendMessage(String message) throws IOException {
synchronized (session) {
this.session.getBasicRemote().sendText(message);
}
}
getAsyncRemote()和getBasicRemote()确实是异步与同步的区别,大部分情况下,推荐使用getAsyncRemote()。由于getBasicRemote()的同步特性,并且它支持部分消息的发送即sendText(xxx,boolean isLast). isLast的值表示是否一次发送消息中的部分消息,对于如下情况:
由于同步特性,第二行的消息必须等待第一行的发送完成才能进行,而第一行的剩余部分消息要等第二行发送完才能继续发送,所以在第二行会抛出IllegalStateException异常。如果要使用getBasicRemote()同步发送消息,则避免尽量一次发送全部消息,使用部分消息来发送。
/**
* 群发自定义消息
* */
public static void sendInfo(String queryType,String message) throws IOException {
for (GrendWebSocket item : webSocketSet) {
if(item.queryType.equals(queryType) && "request".equals(item.status)){
try {
item.sendMessage(message);
} catch (IOException e) {
LOG.error("消息数据发送失败 websocker to brower!");
continue;
}
}
}
}
特别指出:
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
在普通的同步机制中,是通过对象加锁来实现多个线程对同一变量的安全访问的,该变量是多个线程共享的,系统并没有将这份资源复制多份,只是采用了安全机制来控制对这份资源的访问而已。
但是:
ThreadLocal类,意思是线程局部变量。作用是为每一个使用该变量的线程都提供一个该变量的副本,使每一个线程都能独立操作这个副本而不会与其他线程的副本冲突。
故ThreadLocal将需要并发访问的资源复制多份,每个线程拥有自己的资源副本,从而也就没有必要对该变量进行同步了。
线程安全的类,以Concurrent开头的集合类,都在java.util.concurrent包下,这种集合类采用更复杂的算法来保证永远不会锁住整个集合(并发写入时加锁,读取时不加锁),因此在并发写入时有较好的性能。最常用的是ConcurrentHashMap
ConcurrentHashMap在默认情况下最多支持16个线程并发写入,如果没有设置,则超过16个线程并发向该Map中写入数据时,可能会有一些线程需要等待,可以在创建ConcurrentHashMap实例时调用某个带参构造器显式指定。
来自Concurrent包的集合类CopyOnWriteArraySet的特点:
对所有操作使用内部CopyOnWriteArrayList的java.util.Set。 因此,它具有相同的基本属性:
友情链接:https://www.cnblogs.com/bianzy/p/5822426.html