之前讲过用java写长轮询,长轮询的好处就是客户端能够及时的获取到服务端的变更。但是本质上还是客户端去捞数据。
现在有一种更好的后端向前端推数据的方式,那就是websocket。本文就通过实例,展示下如何写websocket程序。
-
准备
需要引入两个websocket的maven坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java-WebSocket</artifactId> <version>1.4.0</version> </dependency>
-
客户端
基础类
@Slf4j public class BaseWebsocketClient extends WebSocketClient { //客户端标识 private String clientName; //客户端连接状态 private boolean isConnect = false; public BaseWebsocketClient(URI serverUri, Map<String, String> httpHeaders, String clientName) { super(serverUri, new Draft_6455(), httpHeaders, 0); this.clientName = clientName; } @Override public void onOpen(ServerHandshake serverHandshake) { log.info("------ {} onOpen ------{}", clientName); } @Override public void onMessage(String s) { log.info("------ {} onMessage ------{}", s); } /***检测到连接关闭之后,会更新连接状态以及尝试重新连接***/ @Override public void onClose(int i, String s, boolean b) { log.info("------ {} onClose ------{}", clientName, b); setConnectState(false); // recontact(); } /***检测到错误,更新连接状态***/ @Override public void onError(Exception e) { log.info("------ {} onError ------{}", clientName, e); setConnectState(false); } public void setConnectState(boolean isConnect) { this.isConnect = isConnect; } public boolean getConnectState(){ return this.isConnect; } }
-
实现类
@Slf4j public class DeviceWebsocketClient extends BaseWebsocketClient{ private static final String ACS_CTRL_RESULT = "deviceWebsocketClient"; private static final String SUBSCRIBE = "subscribe"; /*这个订阅格式是实现约定好的,可以具体情况具体分析*/ private String sendStr = "{\n" + " \"method\": \"subscribe\",\n" + " \"params\": \"device\"\n" + "}"; public DeviceWebsocketClient(URI serverUri, Map<String, String> httpHeaders) { super(serverUri, httpHeaders, ACS_CTRL_RESULT); } @Override public void onOpen(ServerHandshake serverHandshake) { log.info("------ {} onOpen ------", ACS_CTRL_RESULT); this.send(sendStr); setConnectState(true); } @Override public void onMessage(String msg) { log.info("-------- receive acs ctrl result: " + msg + "--------"); System.out.println("receive server message " + msg); } }
-
客户端的bean
@Service @Slf4j @Order(value=100) public class DeviceWebsocketClientService { @PostConstruct public void start() { try { log.info("start to receive device data"); URI uri = new URI("ws://localhost:8080/websocket/10086"); Map<String, String> httpHeaders = new HashMap<>(4); httpHeaders.put("Origin", "http://" + uri.getHost()); DeviceWebsocketClient deviceWebsocketClient = new DeviceWebsocketClient(uri, httpHeaders); deviceWebsocketClient.connect(); }catch (Exception e){ log.error("start to receive device data failed", e); } } }
-
服务端
特别注意,如果使用的是SpringBoot内嵌tomcat一定要加这段代码
如果是打成war包独立部署,则可以不要
@Configuration @ConditionalOnWebApplication public class WebSocketConfig { @Bean public ServerEndpointExporter endpointExporter() { return new ServerEndpointExporter (); } }
@Component @Slf4j @ServerEndpoint("/websocket/{userId}") @Order(1) public class WebSocketServer { //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>(); // 用来存在线连接数 private static final Map<String, Session> sessionPool = new HashMap<String, Session>(); /** * 链接成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam(value = "userId") String userId) { try { this.session = session; webSockets.add(this); sessionPool.put(userId, session); log.info("websocket消息: 有新的连接,总数为:" + webSockets.size()); } catch (Exception e) { } } /** * 收到客户端消息后调用的方法 */ @OnMessage public void onMessage(String message) { log.info("websocket消息: 收到客户端消息:" + message); } /** * 此为单点消息 */ public void sendOneMessage(String userId, String message) { Session session = sessionPool.get(userId); if (session != null && session.isOpen()) { try { log.info("websocket消: 单点消息:" + message); session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } public void start() { log.info("websocket service startup"); try { for (int i=0;i<10;i++) { sendOneMessage("10086", "message " + i); } }catch (Exception e){ log.error("start to receive device data failed", e); } } }
-
测试代码
@RestController @Slf4j @SpringBootApplication public class MaskController { @Autowired private WebSocketServer webSocketServer; @RequestMapping("/trigger") public void triggerWebSocket(HttpServletRequest request, HttpServletResponse response) { webSocketServer.start(); }