Websocket的理论与实践应用

webSocket基本概念

WebSocket是一种网络通信协议,它在单个TCP连接上进行全双工通信。它允许服务器主动向客户端推送信息,客户端也能实时接收服务器的响应。WebSocket通信过程中,只需要一次握手,就可以在客户端和服务器之间建立持久性的连接,并进行双向数据传输。

三、WebSocket工作原理

WebSocket的工作原理基于HTTP协议进行握手,建立TCP连接后,双方通过帧(Frame)进行数据通信。以下是WebSocket建立连接和通信的简要步骤:

  1. 握手阶段:客户端通过发送一个带有特定请求头的HTTP请求来启动WebSocket连接。请求头中包含了WebSocket协议的版本号、子协议名称、是否携带凭证等信息。服务器收到请求后,如果同意建立连接,会返回一个状态码为101 Switching Protocols的响应,表示协议切换成功,WebSocket连接建立。
  2. 数据传输阶段:连接建立后,客户端和服务器之间就可以通过WebSocket帧进行双向数据传输。WebSocket帧包含了操作码(opcode)、负载数据(payload data)和掩码(mask)等信息。操作码用于标识帧的类型,如文本帧、二进制帧、控制帧等;负载数据是实际传输的数据;掩码用于保护客户端发送的数据,防止缓存污染。
    四、WebSocket应用场景
    WebSocket因其全双工通信和实时性强的特点,被广泛应用于以下场景:
  3. 实时聊天应用:WebSocket可以实现实时在线聊天功能,如在线聊天室、即时通讯软件等。
  4. 在线游戏:WebSocket可以实现游戏数据的实时传输和同步,提升游戏体验。
  5. 实时数据监控:WebSocket可以用于实时数据监控系统的后端推送,如股票价格、天气信息、交通状况等。
  6. 协同办公:WebSocket可以实现多人在线协同办公功能,如在线文档编辑、实时会议等。
  7. 物联网(IoT):WebSocket可以用于物联网设备的远程监控和控制,实现设备数据的实时传输和响应。
    五、WebSocket实现方法
    WebSocket的实现通常依赖于客户端和服务器端的编程语言和库。以下是一些常见的实现方法:
  8. 客户端实现:浏览器端可以通过JavaScript的WebSocket API来实现WebSocket连接和数据传输。WebSocket API提供了创建连接、发送数据、接收数据等方法。
  9. 服务器端实现:服务器端可以使用多种编程语言和框架来实现WebSocket服务器。常见的WebSocket服务器实现包括Node.js的socket.io库、Java的Netty框架、Python的websockets库等。这些库和框架提供了WebSocket协议的解析、连接管理、数据传输等功能。
    六、总结
    WebSocket作为一种网络通信协议,具有实时性强、全双工通信的特点,被广泛应用于实时聊天、在线游戏、实时数据监控、协同办公和物联网等场景。开发者可以通过JavaScript的WebSocket API和服务器端的编程语言和库来实现WebSocket连接和数据传输。掌握WebSocket技术将有助于提高Web应用的实时性和交互性。

具体方法实践如下

在此之前我们首先要了解下,http的三次握手四次挥手,长连接与短连接;

1.引入依赖包:

java 复制代码
  <dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
			 <version>1.5.3</version>
        </dependency>

2.编写创建Websocket链接工具

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.device.manage.zfsw.model.WebsocketAccessKey;
import com.zfsw.spzx.device.manage.zfsw.utils.ProxyHttpUtils;
import com.zfsw.spzx.proxy.device.manage.enums.ManufacturerEnum;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.enums.ReadyState;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class CreateWebSocketConnectorUtil {
    @Resource
    private ProxyHttpUtils proxyHttpUtils;
    @Resource
    private WebSocketHandler webSocketHandler;
    public static final Map<String, Boolean> connectPool = new ConcurrentHashMap<>();
    private static final Map<String, Thread> socketConnectPool = new ConcurrentHashMap<>();
    private static final Map<String, Thread> socketListenerPool = new ConcurrentHashMap<>();

    public void create(ServerInfo serverInfo) {
        try {
            ManufacturerEnum manufacturerEnum = ManufacturerEnum.findByCodeOrDesc(serverInfo.getType());
            if (manufacturerEnum != null && manufacturerEnum.getNeedWsConnect()) {
                connectPool.put(serverInfo.getType(), false);
                WebsocketAccessKey key = proxyHttpUtils.sendRequest(ProxyApiEnum.WEBSOCKET_KEY_GET, "{}", serverInfo, WebsocketAccessKey.class);
                WebSocketHandlerClient webSocketHandlerClient = new WebSocketHandlerClient(serverInfo, key.getKey(),
                        StrConstant.WS_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort() + proxyHttpUtils.getVersionUrl(ProxyApiEnum.WS_URL, serverInfo),
                        webSocketHandler);
                webSocketHandlerClient.setConnectionLostTimeout(360);
                webSocketHandlerClient.connect();
                if (Objects.nonNull(socketConnectPool.get(serverInfo.getType()))) {
                    socketConnectPool.get(serverInfo.getType()).interrupt();
                    socketConnectPool.remove(serverInfo.getType());
                }
                WebSocketSessionClientManager.add(serverInfo.getType(), webSocketHandlerClient);
                this.addClientListener(serverInfo);
                connectPool.put(serverInfo.getType(), true);
            } else {
                log.info("{}服务不需要websocket连接", serverInfo.getType());
            }
        } catch (Exception e) {
            if (Objects.isNull(socketConnectPool.get(serverInfo.getType()))) {
                socketConnectPool.put(serverInfo.getType(), new Thread(() -> {
                    while (!connectPool.get(serverInfo.getType())) {
                        log.info("websocket开始重连:" + serverInfo.getName());
                        this.create(serverInfo);
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException ex) {
                            log.info("执行终止,连接成功后正常终止");
                        }
                    }
                }));
                socketConnectPool.get(serverInfo.getType()).start();
            }
        }
    }

    public void addClientListener(ServerInfo serverInfo) {
        if (Objects.isNull(socketListenerPool.get(serverInfo.getType()))) {
            socketListenerPool.put(serverInfo.getType(), new Thread(() -> {
                while (true) {
                    if (!connectPool.get(serverInfo.getType())) {
                        WebSocketHandlerClient client = WebSocketSessionClientManager.CLIENT_POOL.get(serverInfo.getType());
                        if (Objects.nonNull(client)) {
                            if (client.getConnection().getReadyState().equals(ReadyState.CLOSED)) {
                                log.info("websocket重连");
                                this.create(serverInfo);
                            }
                        }
                    }
                    try {
                        Thread.sleep(5000);
                        log.debug("websocket{}:{}监听中..............", serverInfo.getIp(), serverInfo.getPort());
                    } catch (InterruptedException e) {
                        log.error("线程终止");
                    }
                }
            }));
            socketListenerPool.get(serverInfo.getType()).start();
        }

    }

}

3.编写代理http工具

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.utils;

import cn.gookit.core.Assert;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.enums.ServiceType;
import com.zfsw.spzx.journal.enums.ApiStatus;
import com.zfsw.spzx.journal.model.system.SystemLogAddDto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;


@Component
@Slf4j
public class ProxyHttpUtils {
    @Resource
    RedisUtils redisUtils;

    public String getVersionUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
        return proxyApiEnum.getUrl().replace("{version}", subSystem.getVersion());
    }

    public String getFullUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
        return getBaseUrl(subSystem) + getVersionUrl(proxyApiEnum, subSystem);
    }

    private String getBaseUrl(ServerInfo serverInfo) {
        log.info("请求服务信息:{}", JSONObject.toJSONString(serverInfo));
        return StrConstant.HTTP_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort();
    }

    public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
        Assert.throwWhenEmpty(deviceId, "设备id不能为空");
        ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
        return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
    }

    public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
        return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
    }

    public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
        return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
    }

    public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
        return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
    }

    private ResponseRes send(ProxyApiEnum proxyApiEnum, String param, String deviceId, ServerInfo subSystem) {
        ServerInfo serverInfo = StringUtils.isEmpty(deviceId) ? subSystem : redisUtils.getSubsystemByDeviceIdFromCache(deviceId);
        String fullUrl = getFullUrl(proxyApiEnum, serverInfo);
        log.info("{},请求参数,{}", fullUrl, param);
        HttpRequest request = this.createRequest(proxyApiEnum.getMethod(), fullUrl, param, serverInfo);
        ResponseRes res = new ResponseRes();
        res.setStartTime(System.currentTimeMillis());
        res.setHttpRequest(request);
        try {
            HttpResponse response = request.execute();
            res.setBody(response.body());
            Integer code = JSON.parseObject(response.body()).getInteger("code");
            boolean success = Objects.equals(code, 0);
            res.setIsSuccess(success);
            res.setErrorMes(success ? null : JSON.parseObject(response.body()).getString("cnDescribe"));
        } catch (Exception e) {
            res.setIsSuccess(false);
            res.setErrorMes(e.getCause() + StrConstant.COLON_SIGN + e.getMessage() + StrConstant.F_ONE);
        }
        log.info("{}响应结果,{}", fullUrl, res.getIsSuccess() ? res.getBody() : res.getErrorMes());
        if (StringUtils.isEmpty(res.body)) {
            ProxyResponse response = new ProxyResponse(500, res.getErrorMes());
            res.body = JSON.toJSONString(response);
        }
        String apiName = param.contains("PtzGotoPreset") ? "预置位转动" : proxyApiEnum.getDesc();
        this.setLog(res.startTime, res.httpRequest, param, res.body, apiName,
                res.errorMes, res.isSuccess);
        return res;
    }

    @Data
    private static class ResponseRes {
        private String body;
        private Boolean isSuccess = true;
        private String errorMes;
        private long startTime;
        private HttpRequest httpRequest;
    }

    @Data
    @AllArgsConstructor
    private static class ProxyResponse {
        private Integer code;
        private String data;
    }


    private HttpRequest createRequest(Method method, String url, String body, ServerInfo subSystem) {
        HttpRequest request = HttpUtil.createRequest(method, url);
        request.basicAuth(subSystem.getUserName(), subSystem.getPassword());
        request.header("Content-Type", "application/json");
        request.body(body);
        request.setReadTimeout(subSystem.getReadTimeout());
        request.setConnectionTimeout(subSystem.getConnectTimeout());
        return request;
    }


    @Resource
    RabbitTemplate rabbitTemplate;
    @Value("${spring.rabbitmq.producer.system-log-topic:system-log-topic}")
    private String topic;

    private void setLog(long startTime, HttpRequest request, String param, String body, String apiName, String errorMsg, Boolean isSuccess) {
        long endTime = System.currentTimeMillis();
        LocalDateTime now = LocalDateTime.now();
        SystemLogAddDto logRequest = new SystemLogAddDto();
        logRequest.setElapsedTime(endTime - startTime);
        logRequest.setMethod(request.getMethod().name());
        logRequest.setHeader(JSON.toJSONString(request.headers()));
        logRequest.setParam(param);
        //设置接口名称
        logRequest.setName(apiName);
        logRequest.setSourceSystem(ServiceType.MS_DEVOPS_DATA_WEB_SERVICE);
        logRequest.setTargetSystem(ServiceType.MS_PROXY_DEVICE_MANAGE_SERVICE);
        logRequest.setUrl(request.getUrl());
        logRequest.setCreateTime(now);
        logRequest.setStatus(isSuccess ? ApiStatus.SUCCESS : ApiStatus.FAIL);
        logRequest.setResult(body == null ? "接口失败,接口返回null" : body);
        if (StringUtils.isNotEmpty(errorMsg)) {
            logRequest.setResult(logRequest.getResult() + StrConstant.COMMA + errorMsg);
        }
        try {
            rabbitTemplate.convertAndSend(topic, JSON.toJSONString(logRequest));
        } catch (Exception e) {
            log.warn("add log failed");
        }
    }
}

4.继承websocet的客户端

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import com.alibaba.fastjson2.JSONException;
import cn.gookit.core.Assert;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

@Slf4j
public class WebSocketHandlerClient extends WebSocketClient {

    private final String connectKey;
    private final ServerInfo serverInfo;
    private final WebSocketHandler webSocketHandler;


    public WebSocketHandlerClient(ServerInfo serverInfo, String connectKey, String uri, WebSocketHandler webSocketHandler) {
        super(URI.create(uri));
        this.serverInfo = serverInfo;
        this.connectKey = connectKey;
        this.webSocketHandler = webSocketHandler;
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        log.info("onOpen write1");
        send(connectKey);
    }


    @Override
    public void onMessage(String message) {
        log.debug("webSocket接收到消息{}", message);
        if (!message.startsWith("{")) {
            return;
        }
        try {
            webSocketHandler.handler(message);
        } catch (JSONException e) {
            Assert.throwException(e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        log.info("onClose:code:{},reason:{},remote:{}", code, reason, remote);
        CreateWebSocketConnectorUtil.connectPool.put(serverInfo.getType(), false);
    }

    @Override
    public void onError(Exception ex) {
        log.info("onError", ex);
    }

    @Override
    public void onWebsocketPing(WebSocket conn, Framedata f) {
        super.onWebsocketPing(conn, f);
    }
}

5.编写websocket处理器

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.service.DeviceSubsystemService;
import com.zfsw.spzx.device.manage.zfsw.service.entity.DeviceSubsystem;
import com.zfsw.spzx.device.manage.zfsw.utils.RedisUtils;
import com.zfsw.spzx.proxy.device.manage.model.ws.CataLogNotify;
import com.zfsw.spzx.proxy.device.manage.model.ws.WebSocketNotify;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Slf4j
@Component
public class WebSocketHandler {
    @Resource
    RabbitTemplate rabbitTemplate;
    @Resource
    DeviceSubsystemService deviceSubsystemService;
    @Resource
    RedisUtils redisUtils;
    @Value("${spring.rabbitmq.producer.proxy-device-manage}")
    private String producer;

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    @PreDestroy
    public void shutdown() {
        executorService.shutdown();
    }
    public void handler(String msg) {
        try {
            executorService.execute(() -> this.handlerDataSubsystem(msg));
            rabbitTemplate.convertAndSend(producer, msg);
        } catch (AmqpException e) {
            log.error("发送websocket消息失败!", e);
        }
    }

    void handlerDataSubsystem(String msg) {
        try {
            JSONObject object = JSON.parseObject(msg);
            String action = object.getString("action");
            if ("Catalog".equals(action)) {
                WebSocketNotify<List<CataLogNotify>> catalogNotify = JSON.parseObject(msg,
                        new TypeReference<WebSocketNotify<List<CataLogNotify>>>() {
                        }.getType());
                List<CataLogNotify> catalogNotifies = catalogNotify.getData();
                Set<String> pIds = catalogNotifies.stream().map(item -> item.getId().split(StrConstant.EMAIL_SIGN)[0]).collect(Collectors.toSet());
                List<DeviceSubsystem> deviceSubsystems = deviceSubsystemService.lambdaQuery().in(DeviceSubsystem::getId, pIds).list();
                Map<String, String> devSubMap = deviceSubsystems.stream().collect(Collectors.toMap(DeviceSubsystem::getId, DeviceSubsystem::getServerType));
                List<DeviceSubsystem> list = catalogNotifies.stream().map(item -> {
                    DeviceSubsystem deviceSubsystem = new DeviceSubsystem();
                    deviceSubsystem.setId(item.getId());
                    deviceSubsystem.setServerType(devSubMap.get(item.getId().split(StrConstant.EMAIL_SIGN)[0]));
                    return deviceSubsystem;
                }).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(list)) {
                    deviceSubsystemService.saveOrUpdateBatch(list);
                    redisUtils.setDeviceSubSystemToCache(list);
                }
            }
        } catch (Exception e) {
            log.error(e.getCause() + StrConstant.COLON_SIGN + e.getMessage());
        }

    }


}

6.代理JSON工具编写

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.utils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import cn.gookit.core.Assert;
import cn.gookit.core.util.ModelMapperUtil;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.model.ProxyApiRes;

import java.util.List;


public class ProxyJsonUtils {
    public static <E> ProxyApiRes<E> parseObject(String result, Class<E> tClass) {
        ProxyApiRes<E> proxyApiRes = new ProxyApiRes<>();
        try {
            if (tClass == Boolean.class) {
                // 兼容底层接口问题,同一接口返回参数不一样
                ProxyApiRes<?> apiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<?>>() {
                });
                ModelMapperUtil.mapperTo(apiRes, proxyApiRes);
                proxyApiRes.setData((E) apiRes.getIsSuccess());
            } else {
                proxyApiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<E>>(tClass) {
                });
            }
        } catch (Exception e) {
            Assert.throwException(e.getCause() + "," + e.getMessage() + StrConstant.F_ONE);
        }
        Assert.throwWhen(!proxyApiRes.getIsSuccess(), proxyApiRes.getCnDescribe() + StrConstant.F_ONE);
        return proxyApiRes;
    }

    public static <E> ProxyApiRes<List<E>> parseList(String result, Class<E> tClass) {
        ProxyApiRes<List<E>> proxyApiRes = new ProxyApiRes<>();
        try {
            proxyApiRes = JSON.parseObject(result, new TypeReference<ProxyApiRes<List<E>>>(tClass) {
            });
        } catch (Exception e) {
            Assert.throwException(e.getCause() + "," + e.getMessage() + StrConstant.F_ONE);
        }
        Assert.throwWhen(!proxyApiRes.getIsSuccess(), proxyApiRes.getCnDescribe() + StrConstant.F_ONE);
        return proxyApiRes;
    }
}

3.编写代理http工具

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.utils;

import cn.gookit.core.Assert;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.enums.ProxyApiEnum;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import com.zfsw.spzx.enums.ServiceType;
import com.zfsw.spzx.journal.enums.ApiStatus;
import com.zfsw.spzx.journal.model.system.SystemLogAddDto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;


@Component
@Slf4j
public class ProxyHttpUtils {
    @Resource
    RedisUtils redisUtils;

    public String getVersionUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
        return proxyApiEnum.getUrl().replace("{version}", subSystem.getVersion());
    }

    public String getFullUrl(ProxyApiEnum proxyApiEnum, ServerInfo subSystem) {
        return getBaseUrl(subSystem) + getVersionUrl(proxyApiEnum, subSystem);
    }

    private String getBaseUrl(ServerInfo serverInfo) {
        log.info("请求服务信息:{}", JSONObject.toJSONString(serverInfo));
        return StrConstant.HTTP_PROTOCOL + serverInfo.getIp() + StrConstant.COLON_SIGN + serverInfo.getPort();
    }

    public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
        Assert.throwWhenEmpty(deviceId, "设备id不能为空");
        ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
        return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
    }

    public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, String deviceId, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, deviceId, null);
        return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
    }

    public <E> E sendRequest(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
        return ProxyJsonUtils.parseObject(responseRes.body, tClass).getData();
    }

    public <E> List<E> sendRequestForList(ProxyApiEnum proxyApiEnum, String param, ServerInfo subSystem, Class<E> tClass) {
        ResponseRes responseRes = this.send(proxyApiEnum, param, null, subSystem);
        return ProxyJsonUtils.parseList(responseRes.body, tClass).getData();
    }

    private ResponseRes send(ProxyApiEnum proxyApiEnum, String param, String deviceId, ServerInfo subSystem) {
        ServerInfo serverInfo = StringUtils.isEmpty(deviceId) ? subSystem : redisUtils.getSubsystemByDeviceIdFromCache(deviceId);
        String fullUrl = getFullUrl(proxyApiEnum, serverInfo);
        log.info("{},请求参数,{}", fullUrl, param);
        HttpRequest request = this.createRequest(proxyApiEnum.getMethod(), fullUrl, param, serverInfo);
        ResponseRes res = new ResponseRes();
        res.setStartTime(System.currentTimeMillis());
        res.setHttpRequest(request);
        try {
            HttpResponse response = request.execute();
            res.setBody(response.body());
            Integer code = JSON.parseObject(response.body()).getInteger("code");
            boolean success = Objects.equals(code, 0);
            res.setIsSuccess(success);
            res.setErrorMes(success ? null : JSON.parseObject(response.body()).getString("cnDescribe"));
        } catch (Exception e) {
            res.setIsSuccess(false);
            res.setErrorMes(e.getCause() + StrConstant.COLON_SIGN + e.getMessage() + StrConstant.F_ONE);
        }
        log.info("{}响应结果,{}", fullUrl, res.getIsSuccess() ? res.getBody() : res.getErrorMes());
        if (StringUtils.isEmpty(res.body)) {
            ProxyResponse response = new ProxyResponse(500, res.getErrorMes());
            res.body = JSON.toJSONString(response);
        }
        String apiName = param.contains("PtzGotoPreset") ? "预置位转动" : proxyApiEnum.getDesc();
        this.setLog(res.startTime, res.httpRequest, param, res.body, apiName,
                res.errorMes, res.isSuccess);
        return res;
    }

    @Data
    private static class ResponseRes {
        private String body;
        private Boolean isSuccess = true;
        private String errorMes;
        private long startTime;
        private HttpRequest httpRequest;
    }

    @Data
    @AllArgsConstructor
    private static class ProxyResponse {
        private Integer code;
        private String data;
    }


    private HttpRequest createRequest(Method method, String url, String body, ServerInfo subSystem) {
        HttpRequest request = HttpUtil.createRequest(method, url);
        request.basicAuth(subSystem.getUserName(), subSystem.getPassword());
        request.header("Content-Type", "application/json");
        request.body(body);
        request.setReadTimeout(subSystem.getReadTimeout());
        request.setConnectionTimeout(subSystem.getConnectTimeout());
        return request;
    }


    @Resource
    RabbitTemplate rabbitTemplate;
    @Value("${spring.rabbitmq.producer.system-log-topic:system-log-topic}")
    private String topic;

    private void setLog(long startTime, HttpRequest request, String param, String body, String apiName, String errorMsg, Boolean isSuccess) {
        long endTime = System.currentTimeMillis();
        LocalDateTime now = LocalDateTime.now();
        SystemLogAddDto logRequest = new SystemLogAddDto();
        logRequest.setElapsedTime(endTime - startTime);
        logRequest.setMethod(request.getMethod().name());
        logRequest.setHeader(JSON.toJSONString(request.headers()));
        logRequest.setParam(param);
        //设置接口名称
        logRequest.setName(apiName);
        logRequest.setSourceSystem(ServiceType.MS_DEVOPS_DATA_WEB_SERVICE);
        logRequest.setTargetSystem(ServiceType.MS_PROXY_DEVICE_MANAGE_SERVICE);
        logRequest.setUrl(request.getUrl());
        logRequest.setCreateTime(now);
        logRequest.setStatus(isSuccess ? ApiStatus.SUCCESS : ApiStatus.FAIL);
        logRequest.setResult(body == null ? "接口失败,接口返回null" : body);
        if (StringUtils.isNotEmpty(errorMsg)) {
            logRequest.setResult(logRequest.getResult() + StrConstant.COMMA + errorMsg);
        }
        try {
            rabbitTemplate.convertAndSend(topic, JSON.toJSONString(logRequest));
        } catch (Exception e) {
            log.warn("add log failed");
        }
    }
}

4.继承websocet的客户端

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import com.alibaba.fastjson2.JSONException;
import cn.gookit.core.Assert;
import com.zfsw.spzx.device.manage.zfsw.model.ServerInfo;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;

@Slf4j
public class WebSocketHandlerClient extends WebSocketClient {

    private final String connectKey;
    private final ServerInfo serverInfo;
    private final WebSocketHandler webSocketHandler;


    public WebSocketHandlerClient(ServerInfo serverInfo, String connectKey, String uri, WebSocketHandler webSocketHandler) {
        super(URI.create(uri));
        this.serverInfo = serverInfo;
        this.connectKey = connectKey;
        this.webSocketHandler = webSocketHandler;
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        log.info("onOpen write1");
        send(connectKey);
    }


    @Override
    public void onMessage(String message) {
        log.debug("webSocket接收到消息{}", message);
        if (!message.startsWith("{")) {
            return;
        }
        try {
            webSocketHandler.handler(message);
        } catch (JSONException e) {
            Assert.throwException(e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        log.info("onClose:code:{},reason:{},remote:{}", code, reason, remote);
        CreateWebSocketConnectorUtil.connectPool.put(serverInfo.getType(), false);
    }

    @Override
    public void onError(Exception ex) {
        log.info("onError", ex);
    }

    @Override
    public void onWebsocketPing(WebSocket conn, Framedata f) {
        super.onWebsocketPing(conn, f);
    }
}

5.编写websocket处理器

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.TypeReference;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zfsw.spzx.device.manage.zfsw.constant.StrConstant;
import com.zfsw.spzx.device.manage.zfsw.service.DeviceSubsystemService;
import com.zfsw.spzx.device.manage.zfsw.service.entity.DeviceSubsystem;
import com.zfsw.spzx.device.manage.zfsw.utils.RedisUtils;
import com.zfsw.spzx.proxy.device.manage.model.ws.CataLogNotify;
import com.zfsw.spzx.proxy.device.manage.model.ws.WebSocketNotify;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Slf4j
@Component
public class WebSocketHandler {
    @Resource
    RabbitTemplate rabbitTemplate;
    @Resource
    DeviceSubsystemService deviceSubsystemService;
    @Resource
    RedisUtils redisUtils;
    @Value("${spring.rabbitmq.producer.proxy-device-manage}")
    private String producer;

    ExecutorService executorService = Executors.newFixedThreadPool(5);

    @PreDestroy
    public void shutdown() {
        executorService.shutdown();
    }
    public void handler(String msg) {
        try {
            executorService.execute(() -> this.handlerDataSubsystem(msg));
            rabbitTemplate.convertAndSend(producer, msg);
        } catch (AmqpException e) {
            log.error("发送websocket消息失败!", e);
        }
    }

    void handlerDataSubsystem(String msg) {
        try {
            JSONObject object = JSON.parseObject(msg);
            String action = object.getString("action");
            if ("Catalog".equals(action)) {
                WebSocketNotify<List<CataLogNotify>> catalogNotify = JSON.parseObject(msg,
                        new TypeReference<WebSocketNotify<List<CataLogNotify>>>() {
                        }.getType());
                List<CataLogNotify> catalogNotifies = catalogNotify.getData();
                Set<String> pIds = catalogNotifies.stream().map(item -> item.getId().split(StrConstant.EMAIL_SIGN)[0]).collect(Collectors.toSet());
                List<DeviceSubsystem> deviceSubsystems = deviceSubsystemService.lambdaQuery().in(DeviceSubsystem::getId, pIds).list();
                Map<String, String> devSubMap = deviceSubsystems.stream().collect(Collectors.toMap(DeviceSubsystem::getId, DeviceSubsystem::getServerType));
                List<DeviceSubsystem> list = catalogNotifies.stream().map(item -> {
                    DeviceSubsystem deviceSubsystem = new DeviceSubsystem();
                    deviceSubsystem.setId(item.getId());
                    deviceSubsystem.setServerType(devSubMap.get(item.getId().split(StrConstant.EMAIL_SIGN)[0]));
                    return deviceSubsystem;
                }).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(list)) {
                    deviceSubsystemService.saveOrUpdateBatch(list);
                    redisUtils.setDeviceSubSystemToCache(list);
                }
            }
        } catch (Exception e) {
            log.error(e.getCause() + StrConstant.COLON_SIGN + e.getMessage());
        }

    }


}

7.websocket服务端的session管理器

java 复制代码
package com.zfsw.spzx.device.manage.zfsw.service.ws;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class WebSocketSessionClientManager {

    //保存现存信息
    public static ConcurrentHashMap<String, WebSocketHandlerClient> CLIENT_POOL = new ConcurrentHashMap<>();


    public static void add(String subSystemID, WebSocketHandlerClient webSocketHandlerClient) {
        // 添加 session
        CLIENT_POOL.put(subSystemID, webSocketHandlerClient);
    }

    public static WebSocketHandlerClient remove(String subSystem) {
        // 删除 session
        return CLIENT_POOL.remove(subSystem);
    }

    public static void removeAndClose(String subSystem) {
        WebSocketHandlerClient webSocketHandlerClient = remove(subSystem);
        if (webSocketHandlerClient != null) {
            // 关闭连接
            webSocketHandlerClient.close();
        }
    }

    public static WebSocketHandlerClient get(String subSystem) {
        // 获得 session
        return CLIENT_POOL.get(subSystem);
    }
}
相关推荐
安科士andxe7 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
YJlio9 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
CTRA王大大10 小时前
【网络】FRP实战之frpc全套配置 - fnos飞牛os内网穿透(全网最通俗易懂)
网络
testpassportcn10 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
通信大师11 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
Tony Bai12 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
消失的旧时光-194312 小时前
从 0 开始理解 RPC —— 后端工程师扫盲版
网络·网络协议·rpc
叫我龙翔13 小时前
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层结构
服务器·网络·c++·json
“αβ”14 小时前
网络层协议 -- ICMP协议
linux·服务器·网络·网络协议·icmp·traceroute·ping
袁小皮皮不皮15 小时前
数据通信18-网络管理与运维
运维·服务器·网络·网络协议·智能路由器