package com.ruoyi.sim.service.impl;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.sim.config.SimConfig;
import com.ruoyi.sim.config.SimDebugConfig;
import com.ruoyi.sim.domain.Seat;
import com.ruoyi.sim.domain.vo.SimSocketParamVo;
import com.ruoyi.sim.domain.vo.SocketWrapCacheVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static com.ruoyi.sim.constant.CommConst.SOCKET_TIME_OUT;
/**
* 1.所有Socket常开。
* todo: connectedTimeMillis 时间。
*
* 创建Socket重试,用failed开始方法去操作。
*/
@Service
public class SocketService {
private static final Logger l = LoggerFactory.getLogger(SocketService.class);
/**
* 硬件孙总北京办公室,测试远程地址
*
* 孙总办公室域名
* nas.yichiot.com
*/
public static final String IP_TEST = "221.218.215.73";
public static final int PORT_TEST = 8899;
private static final int LOCAL_PORT = 9000;
private static final int INIT_SIZE = 32;
/**
* 6 hours
* 1000L * 60 * 60 * 6
* 1000L * 60 * 5
*/
private static final long TIMEOUT_LIMIT = 1000L * 60 * 60 * 6;
public static final int SOCKET_CONNECT_RETRY_COUNT_LIMIT = 4;
/**
* key: ip:port
* value:
*/
private static HashMap cachedMap = new HashMap<>(INIT_SIZE);
/**
* 每个Socket都有。
* 重试次数。
* default 0.
*/
private static HashMap failedMap = new HashMap<>(INIT_SIZE);
@Autowired
private SimConfig config;
@Autowired
private SeatService seatService;
/**
* @param sspv
* @return true:socket ok!
*/
public boolean isOk(final SimSocketParamVo sspv) {
final String key = sspv.toKey();
if (cachedMap.containsKey(key) && cachedMap.get(key) != null) {
Socket s = cachedMap.get(key).getSocket();
if (s != null) {
return (s.isConnected() && s.isBound() && !s.isClosed());
}
}
return false;
}
public boolean isNotOk(final SimSocketParamVo sspv) {
return !isOk(sspv);
}
/**
* 暂时不考虑超时周期。
* todo:
*
* @param ssv
* @return
*/
public boolean isTimeout(SimSocketParamVo ssv) {
if (cachedMap.containsKey(ssv.toKey())) {
Long cached = cachedMap.get(ssv.toKey()).getOkTimeMillis();
if (cached == null || cached == 0L) {
return true;
}
return System.currentTimeMillis() - cached <= TIMEOUT_LIMIT;
}
return true;
}
/**
* @param sspv
* @param force 是否强制使用新连接的Socket
* @return
*/
public AjaxResult openOne(final SimSocketParamVo sspv, final boolean force) {
// check.
if (!config.isCommGlobal()) {
l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
return AjaxResult.error("模拟器通信被禁用!");
}
//
try {
if (isNotOk(sspv) || force) {
final String key = sspv.toKey();
l.info("openSocket cachedSocket is not ok!try new socket ip = {}:{}!forceNew = {}", sspv.getIp(), sspv.getPort(), force);
closeOne(sspv, false);
// 不指定本地端口实现。
Socket s = new Socket(sspv.getIp(), sspv.getPort());
// todo: LOCAL_PORT
// Socket s = new Socket(sspv.getIp(), sspv.getPort(), InetAddress.getLocalHost(), SimDebugConfig.TCP_LOCAL_PORT);
s.setSoTimeout(SOCKET_TIME_OUT);
SocketWrapCacheVo value = new SocketWrapCacheVo(sspv.getIp(), sspv.getPort(), s, System.currentTimeMillis());
// 新建Socket需要挂起 2s后再发指令。
value.setPreviousSendSleep(2000L);
cachedMap.put(key, value);
// socket failed count reset.
failedReset0(sspv);
} else {
l.info("openSocket cachedSocket cache ok!cached socket ip = {}:{}!forceNew = {}", sspv.getIp(), sspv.getPort(), force);
}
Seat seat = seatService.uniqueByRs485IpAndPort(sspv.getIp(), sspv.getPort());
seat.setSeatRs485SocketState(Seat.SocketState.ONLINE);
seatService.updateSeat(seat);
return AjaxResult.success("Socket[" + sspv.getIp() + ":" + sspv.getPort() + "],创建成功!");
} catch (IOException e) {
l.error("IOException = {}", sspv);
if (failedIsReachedMax(sspv, SOCKET_CONNECT_RETRY_COUNT_LIMIT)) {
return AjaxResult.error("Socket[" + sspv.getIp() + ":" + sspv.getPort() + "],重试[" + failedGet(sspv) + "]次,创建失败!");
} else {
failedPlus1(sspv);
AjaxResult ar = openOne(sspv, force);
if (ar.isSuccess()) {
return ar;
} else {
return AjaxResult.error("Socket[" + sspv.getIp() + ":" + sspv.getPort() + "],进行重试,创建失败!");
}
}
}
}
public AjaxResult tryOpenOne(final SimSocketParamVo sspv) {
if (!config.isCommGlobal()) {
l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
return AjaxResult.error("模拟器通信被禁用!");
}
return openOne(sspv, false);
}
/**
* todo:部分返回Aj结果。
*
* @return
*/
public AjaxResult tryOpenAll() {
if (!config.isCommGlobal()) {
l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
return AjaxResult.error("模拟器通信被禁用!");
}
List allSeat = seatService.listAllEnable();
for (Seat s : allSeat) {
AjaxResult ar = openOne(s.toSimSocketParamVo(), false);
l.debug("AjaxResult = {}", ar);
}
return AjaxResult.success("所有Socket,创建成功!");
}
public AjaxResult closeOne(final SimSocketParamVo sspv, boolean failedReset) {
if (!config.isCommGlobal()) {
l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
return AjaxResult.error("模拟器通信被禁用!");
}
final String key = sspv.toKey();
try {
if (cachedMap.containsKey(key)) {
Socket s = cachedMap.get(key).getSocket();
s.getInputStream().close();
s.getOutputStream().close();
s.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (failedReset) {
// failed count reset.
failedReset0(sspv);
}
cachedMap.remove(key);
return AjaxResult.success("关闭Socket成功!");
}
}
public AjaxResult closeAll() {
if (!config.isCommGlobal()) {
l.warn("isCommGlobal == {} [模拟器通信被禁用!]", config.isCommGlobal());
return AjaxResult.error("模拟器通信被禁用!");
}
final String msgOk = "关闭所有Socket成功!";
List allSeat = seatService.listAllEnable();
for (Seat s : allSeat) {
closeOne(new SimSocketParamVo(s.getSeatRs485Ip(), s.getSeatRs485Port()), true);
}
return AjaxResult.success(msgOk);
}
/**
* @param seat
* @return todo:null
*/
public SocketWrapCacheVo get(final Seat seat) {
if (seat == null) {
throw new IllegalArgumentException("seat为空。");
}
return get(new SimSocketParamVo(seat.getSeatRs485Ip(), seat.getSeatRs485Port()));
}
/**
* @param sspv
* @return
*/
public SocketWrapCacheVo get(final SimSocketParamVo sspv) {
if (isNotOk(sspv)) {
AjaxResult ar = openOne(sspv, true);
if (ar.isError()) {
// todo: isError
}
}
return cachedMap.get(sspv.toKey());
}
/**
* 初始化。todo:
*/
public void clear(final SimSocketParamVo sspv) {
}
private static final int SOCKET_FAILED_COUNT_0 = 0;
private static final int SOCKET_FAILED_COUNT_ADD_1 = 1;
/**
* @param sspv
* @param limit include limit
* @return
*/
public boolean failedIsReachedMax(final SimSocketParamVo sspv, final int limit) {
final String key = sspv.toKey();
return (failedMap.containsKey(key) && failedMap.get(key).get() >= limit);
}
public int failedPlus1(final SimSocketParamVo sspv) {
final String key = sspv.toKey();
if (!failedMap.containsKey(key)) {
failedMap.put(key, new AtomicInteger(SOCKET_FAILED_COUNT_ADD_1));
} else {
failedMap.get(key).addAndGet(SOCKET_FAILED_COUNT_ADD_1);
}
return failedMap.get(key).get();
}
public int failedGet(final SimSocketParamVo sspv) {
final String key = sspv.toKey();
if (failedMap.containsKey(key)) {
return failedMap.get(key).get();
} else {
return SOCKET_FAILED_COUNT_0;
}
}
public void failedReset0(final SimSocketParamVo sspv) {
final String key = sspv.toKey();
if (failedMap.containsKey(key)) {
failedMap.get(key).set(SOCKET_FAILED_COUNT_0);
} else {
l.debug("not containsKey SimSocketParamVo sspv:" + sspv);
}
}
}