WebSocket实例

  之前讲过用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();
    }

 

posted on 2022-08-12 11:34  MaXianZhe  阅读(330)  评论(0编辑  收藏  举报

导航