在项目的开发时,遇到实现服务器主动发送数据到前端页面的功能的需求。实现该功能不外乎使用轮询和websocket技术,但在考虑到实时性和资源损耗后,最后决定使用websocket。现在就记录一下用Java实现Websocket技术吧~
Java实现Websocket通常有两种方式:1、创建WebSocketServer
类,里面包含open、close、message、error等方法;2、利用Springboot提供的webSocketHandler
类,创建其子类并重写方法。我们项目虽然使用Springboot框架,不过仍采用了第一种方法实现。
创建WebSocket的简单实例操作流程
1)引入Websocket依赖
<!--websocket支持包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2)创建配置类WebSocketConfig
ServerEndpointExporter
是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig
配置类和@ServerEndpoint
注解实例。
如果使用内置的tomcat容器,那么必须用@Bean
注入ServerEndpointExporter
;
如果使用外置容器部署war包,那么不需要提供ServerEndpointExporter
,扫描服务器的行为将交由外置容器处理,需要将注入ServerEndpointExporter
的@Bean
代码注解掉。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 如果使用Springboot默认内置的tomcat容器,则必须注入ServerEndpoint的bean;
* 如果使用外置的web容器,则不需要提供ServerEndpointExporter,下面的注入可以注解掉
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
3)创建WebSocketServer
在websocket协议下,后端服务器相当于ws里面的客户端,需要用@ServerEndpoint
指定访问路径,并使用@Component
注入容器
@ServerEndpoint:当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类。被注解的类将被注册成为一个WebSocket端点。所有的配置项都在这个注解的属性中 ( 如:@ServerEndpoint("/ws") )
下面的栗子中@ServerEndpoint
指定访问路径中包含sid,这个是用于区分每个页面
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/ws/{sid}")
@Component
public class WebSocketServer {
private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//旧:concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。由于遍历set费时,改用map优化
//private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//新:使用map对象优化,便于根据sid来获取对应的WebSocket
private static ConcurrentHashMap<String,WebSocketServer> websocketMap = new ConcurrentHashMap<>();
//接收用户的sid,指定需要推送的用户
private String sid;
/**
* 连接成功后调用的方法
*/
@OnOpen
public void