|
@@ -0,0 +1,212 @@
|
|
|
|
+package com.nokia.sms.service;
|
|
|
|
+
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.io.InputStream;
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
+import java.net.InetAddress;
|
|
|
|
+import java.net.InetSocketAddress;
|
|
|
|
+import java.net.Socket;
|
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
|
+import java.util.Arrays;
|
|
|
|
+import java.util.Date;
|
|
|
|
+import java.util.concurrent.Future;
|
|
|
|
+
|
|
|
|
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
+
|
|
|
|
+import com.nokia.sms.config.BlkConfig;
|
|
|
|
+import com.nokia.sms.entity.BindRespStatus;
|
|
|
|
+import com.nokia.sms.entity.DelBlkBody;
|
|
|
|
+import com.nokia.sms.entity.DelBlkRespStatus;
|
|
|
|
+import com.nokia.sms.exception.ConnectFailedException;
|
|
|
|
+import com.nokia.sms.exception.ParseException;
|
|
|
|
+import com.nokia.sms.message.ClientMessageUtil;
|
|
|
|
+
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+
|
|
|
|
+@Slf4j
|
|
|
|
+@Service
|
|
|
|
+public class SocketClientService {
|
|
|
|
+
|
|
|
|
+ private final ThreadPoolTaskScheduler socketScheduler;
|
|
|
|
+
|
|
|
|
+ volatile Socket socket;
|
|
|
|
+ volatile InputStream inputStream;
|
|
|
|
+ volatile OutputStream outputStream;
|
|
|
|
+ volatile boolean connected;
|
|
|
|
+ // 0 - 4294967295 0xFFFFFFFF
|
|
|
|
+ // 在进程间共享
|
|
|
|
+ volatile long sequenceNumber;
|
|
|
|
+ private static final long MAX_SEQUENCE_NUMBER = 10L;
|
|
|
|
+ volatile Future<?> heartbeatFuture;
|
|
|
|
+
|
|
|
|
+ volatile String systemId = BlkConfig.getConfig("blkServer.systemId");
|
|
|
|
+ volatile String password = BlkConfig.getConfig("blkServer.password");
|
|
|
|
+ volatile String host = BlkConfig.getConfig("blkServer.ipAddress");
|
|
|
|
+ volatile int port = Integer.parseInt(BlkConfig.getConfig("blkServer.port"));
|
|
|
|
+ volatile int connectTimeout = Integer.parseInt(BlkConfig.getConfig("blkServer.connectTimeout"));
|
|
|
|
+ volatile long heartbeatDelay = Long.parseLong(BlkConfig.getConfig("blkServer.heartbeatDelay"));
|
|
|
|
+
|
|
|
|
+ public SocketClientService(ThreadPoolTaskScheduler socketScheduler) throws ConnectFailedException {
|
|
|
|
+ this.socketScheduler = socketScheduler;
|
|
|
|
+ connect();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * 完成删除黑名单业务
|
|
|
|
+ */
|
|
|
|
+ public boolean delBlk(String phoneNumber) {
|
|
|
|
+ log.debug("准备删除号码 {} 的短信黑名单记录...", phoneNumber);
|
|
|
|
+ Future<Boolean> future = socketScheduler.submit(() -> {
|
|
|
|
+ if (!connected) {
|
|
|
|
+ connect();
|
|
|
|
+ }
|
|
|
|
+ DelBlkBody delBlkBody = new DelBlkBody(phoneNumber);
|
|
|
|
+ // 发送消息
|
|
|
|
+ long sequenceNumberSend = getNextSequence();
|
|
|
|
+ byte[] smitDelBlk = ClientMessageUtil.getSmitDelBlkMessage(delBlkBody, sequenceNumberSend);
|
|
|
|
+ outputStream.write(smitDelBlk);
|
|
|
|
+ outputStream.flush();
|
|
|
|
+ log.debug("已发送解除黑名单消息 SMIT_DELBLK, 序列号: {}", sequenceNumberSend);
|
|
|
|
+ // 接收回复消息
|
|
|
|
+ byte[] resp = new byte[13];
|
|
|
|
+ inputStream.read(resp);
|
|
|
|
+ log.debug("收到服务器回复... {}", Arrays.toString(resp));
|
|
|
|
+ DelBlkRespStatus status = ClientMessageUtil.parseSmitDelBlkRespMessage(resp);
|
|
|
|
+ boolean b = (status == DelBlkRespStatus.SUCCESS);
|
|
|
|
+ if (b) {
|
|
|
|
+ // 删除成功, 序列号增加
|
|
|
|
+ sequenceNumber = getNextSequence();
|
|
|
|
+ }
|
|
|
|
+ return b;
|
|
|
|
+ });
|
|
|
|
+ try {
|
|
|
|
+ return future.get();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.error("删除黑名单出错...");
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ // 关闭连接,释放资源
|
|
|
|
+ close();
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * 连接到服务器
|
|
|
|
+ */
|
|
|
|
+ public void connect() throws ConnectFailedException {
|
|
|
|
+ try {
|
|
|
|
+ // 启动socket连接
|
|
|
|
+ socket = new Socket();
|
|
|
|
+ InetAddress address = InetAddress.getByName(host);
|
|
|
|
+ socket.connect(new InetSocketAddress(address, port), connectTimeout);
|
|
|
|
+ // 绑定输入输出
|
|
|
|
+ inputStream = socket.getInputStream();
|
|
|
|
+ outputStream = socket.getOutputStream();
|
|
|
|
+ // 向客户端发送绑定请求
|
|
|
|
+ byte[] smitBind = ClientMessageUtil.getSmitBindMessage(systemId, password);
|
|
|
|
+ outputStream.write(smitBind);
|
|
|
|
+ outputStream.flush();
|
|
|
|
+ log.debug("已发送绑定请求消息 SMIT_BIND");
|
|
|
|
+ // 接收回复消息
|
|
|
|
+ byte[] resp = new byte[128];
|
|
|
|
+ int count = inputStream.read(resp);
|
|
|
|
+ // 解析消息
|
|
|
|
+ BindRespStatus status = ClientMessageUtil.parseSmitBindRespMessage(resp);
|
|
|
|
+ log.debug("收到服务器回复...长度 {} 字节, 状态: {}({})", count, status, status.status);
|
|
|
|
+ if (BindRespStatus.SUCCESS.equals(status)) {
|
|
|
|
+ connected = true;
|
|
|
|
+ log.info("已成功与服务端建立连接,开始心跳检测");
|
|
|
|
+ // 开启心跳检测
|
|
|
|
+ heartbeatFuture = socketScheduler.scheduleAtFixedRate(() -> {
|
|
|
|
+ heartbeat();
|
|
|
|
+ }, new Date(System.currentTimeMillis() + heartbeatDelay), heartbeatDelay);
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+ log.error("服务器连接失败,错误码: {}", status);
|
|
|
|
+ // 连接失败释放资源
|
|
|
|
+ close();
|
|
|
|
+ throw new ConnectFailedException("连接失败,状态码 : " + status);
|
|
|
|
+ }
|
|
|
|
+ } catch (IOException | NoSuchAlgorithmException | ParseException e) {
|
|
|
|
+ log.error("服务器连接失败");
|
|
|
|
+ // 连接失败释放资源
|
|
|
|
+ close();
|
|
|
|
+ throw new ConnectFailedException(
|
|
|
|
+ String.format("连接服务器失败, 原因: %s - %s", e.getClass().getName(), e.getMessage()));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * 心跳检测
|
|
|
|
+ */
|
|
|
|
+ public void heartbeat() {
|
|
|
|
+ try {
|
|
|
|
+ // 发送心跳消息
|
|
|
|
+ long sequenceNumberSend = getNextSequence();
|
|
|
|
+ byte[] smitActiveTest = ClientMessageUtil.getSmitActiveTestMessage(sequenceNumberSend);
|
|
|
|
+ outputStream.write(smitActiveTest);
|
|
|
|
+ outputStream.flush();
|
|
|
|
+ log.debug("已发送心跳消息, 序列号: {}", sequenceNumberSend);
|
|
|
|
+ // 读取心跳消息的返回
|
|
|
|
+ byte[] buffer = new byte[48];
|
|
|
|
+ inputStream.read(buffer);
|
|
|
|
+ long sequenceNumberReceive = ClientMessageUtil.parseSmitActiveTestRespMessage(buffer);
|
|
|
|
+ log.debug("接收到服务器返回的心跳消息, 序列号: {}", sequenceNumberReceive);
|
|
|
|
+ if (sequenceNumberReceive == sequenceNumberSend) {
|
|
|
|
+ sequenceNumber = getNextSequence();
|
|
|
|
+ } else {
|
|
|
|
+ log.error("心跳乱序,将关闭连接...发送 {} 接收 {} ", sequenceNumberSend, sequenceNumberReceive);
|
|
|
|
+ close();
|
|
|
|
+ }
|
|
|
|
+ } catch (IOException | ParseException e) {
|
|
|
|
+ log.error("心跳出错,将关闭连接... {}({})", e.getClass().getName(), e.getMessage());
|
|
|
|
+ close();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * 遇到问题关闭连接
|
|
|
|
+ */
|
|
|
|
+ public void close() {
|
|
|
|
+ // 停止心跳检测
|
|
|
|
+ heartbeatFuture.cancel(true);
|
|
|
|
+ // 复位sequenceNumber
|
|
|
|
+ sequenceNumber = 0;
|
|
|
|
+ // 复位连接指示
|
|
|
|
+ connected = false;
|
|
|
|
+ // 释放资源
|
|
|
|
+ if (inputStream != null) {
|
|
|
|
+ try {
|
|
|
|
+ inputStream.close();
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ inputStream = null;
|
|
|
|
+ }
|
|
|
|
+ if (outputStream != null) {
|
|
|
|
+ try {
|
|
|
|
+ outputStream.close();
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ outputStream = null;
|
|
|
|
+ }
|
|
|
|
+ if (socket != null) {
|
|
|
|
+ try {
|
|
|
|
+ socket.close();
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ socket = null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private long getNextSequence() {
|
|
|
|
+ if (sequenceNumber < MAX_SEQUENCE_NUMBER) {
|
|
|
|
+ return sequenceNumber + 1L;
|
|
|
|
+ } else {
|
|
|
|
+ return 0L;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|