需求
- 在TCP层构建服务,接收数据。
- 需要先通过socket构建客户端和服务端的长链接,然后等客户端发送数据。
- 如果客户端超过5分钟没有发送数据,服务端主动关闭连接。
- 数据发送时按字节逆序发送。
- 自定义协议,有指定的开头和结尾,长度恒定。
- 将数据存入数据库。
设计
- 基于springboot,需要启动类。
- 启动socketServer服务接收请求。
- 接收请求需要协议解码和序列化,统一在接口中。
- 数据存入数据库。
步骤
- 设计启动类,包括数据存入部分,数据协议校验和数据反序列化部分。
@Service
@Slf4j
public class TcpServerService {
private ExecutorService executor = Executors.newFixedThreadPool(2);
private List<AnalysisRule> rules = new ArrayList<>(10);
@Resource
private Mapper mapper;
@Value("${tcpServer.port}")
private int port;
@PostConstruct
public void initServer() {
initRules();
startServer();
}
private void initRules() {
rules.add((info, dto) -> {
if (!Arrays.equals(info, new byte[] {0x11, 0x22, 0x33, 0x44})) {
throw new SystemException("verify head fail!");
}
});
rules.add((info, dto) -> dto.setNum(getInt(info)));
.......
rules.add((info, dto) -> {
if (!Arrays.equals(info, new byte[] {0x55, 0x66, 0x77, (byte) 0x88})) {
throw new SystemException("verify tail fail!");
}
});
}
private void startServer() {
try {
ServerSocket serverSocket = new ServerSocket(port);
log.debug("TCPServer started. Listening on port " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
log.debug("Client connected: " + clientSocket.getInetAddress().getHostAddress());
executor.submit(new ClientHandler(clientSocket, rules, dto -> {
mapper.insert(dto);
}));
Thread.sleep(100L);
}
} catch (IOException | InterruptedException e) {
log.debug("tcp Server 接收数据异常,",e);
}
}
private int getInt(byte[] bytes) {
return ByteBuffer.wrap(bytes).getInt();
}
private float getFloat(byte[] bytes) {
return ByteBuffer.wrap(bytes).getFloat();
}
}
- 解析规则接口,负责协议解码,就是将4字节数据顺序翻转。
public interface AnalysisRule {
default int getLength() {
return 4;
}
void handler(byte[] info, TcpServerDTO dto);
default int execute(int readIndex, byte[] bytes, TcpServerDTO dto) {
byte[] curBytes = Arrays.copyOfRange(bytes, readIndex, readIndex + getLength());
byte info1 = curBytes[0];
byte info2 = curBytes[1];
byte info3 = curBytes[2];
byte info4 = curBytes[3];
byte[] info = new byte[4];
info[0] = info4;
info[1] = info3;
info[2] = info2;
info[3] = info1;
handler(info, dto);
return readIndex + getLength();
}
}
- 接收数据体
@Data
public class TcpServerDTO {
private Integer num;
......
}
- 请求处理线程
@Slf4j
public class ClientHandler implements Runnable{
private Socket clientSocket;
private List<AnalysisRule> rules;
private SaveDTOHandler handler;
public ClientHandler(Socket clientSocket, List<AnalysisRule> rules, SaveDTOHandler handler) {
this.clientSocket = clientSocket;
this.rules = rules;
this.handler = handler;
}
@Override
public void run() {
try {
InputStream inputStream = clientSocket.getInputStream();
int enterTime = 0;
while (true) {
enterTime++;
if (enterTime > 10) {
log.debug("leave read loop!");
break;
}
if (inputStream.available() >= 128) {
byte[] buffer = new byte[128];
int count = inputStream.read(buffer);
if (count > 0) {
analysis(buffer);
}
enterTime = 0;
} else {
log.debug("sleep 30s, enterTime:" + enterTime);
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
clientSocket.close();
log.debug("Client disconnected: " + clientSocket.getInetAddress().getHostAddress());
} catch (Exception e) {
log.debug("tcp Server 接收数据异常,",e);
}
}
private void analysis(byte[] buffer) {
TcpServerDTO dto = new TcpServerDTO();
int readIndex = 0;
try {
for (AnalysisRule curRule : rules) {
if (curRule != null) {
readIndex = curRule.execute(readIndex, buffer, dto);
}
}
} catch (Exception e) {
log.error("解析失败!", e);
return;
} finally {
printPackageInfo(buffer);
}
log.info("生成的DTO为:" + dto);
handler.save(dto);
}
private void printPackageInfo(byte[] buffer) {
String[] strArray = new String[buffer.length];
for (int i = 0; i < buffer.length; i++) {
byte cur = buffer[i];
String hexString = Integer.toHexString(cur & 0xff);
if (hexString.length() < 2) {
hexString = "0" + hexString;
}
strArray[i] = hexString.toUpperCase(Locale.ROOT);
}
log.info("packages info : " + Arrays.toString(strArray));
}
}
- 储存命令接口
public interface SaveDTOHandler {
void save(TcpServerDTO dto);
}