使用Socket构建TCP层,依据自定义协议的服务端

需求

  1. 在TCP层构建服务,接收数据。
  2. 需要先通过socket构建客户端和服务端的长链接,然后等客户端发送数据。
  3. 如果客户端超过5分钟没有发送数据,服务端主动关闭连接。
  4. 数据发送时按字节逆序发送。
  5. 自定义协议,有指定的开头和结尾,长度恒定。
  6. 将数据存入数据库。

设计

  1. 基于springboot,需要启动类。
  2. 启动socketServer服务接收请求。
  3. 接收请求需要协议解码和序列化,统一在接口中。
  4. 数据存入数据库。

步骤

  1. 设计启动类,包括数据存入部分,数据协议校验和数据反序列化部分。
@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 serverSocket = new ServerSocket(port);
            log.debug("TCPServer started. Listening on port " + port);

            while (true) {
                // 接受客户端连接请求,并创建 Socket 对象
                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);
        }
    }

	//4字节数据转int
    private int getInt(byte[] bytes) {
        return ByteBuffer.wrap(bytes).getInt();
    }

	//4字节数据转float
    private float getFloat(byte[] bytes) {
        return ByteBuffer.wrap(bytes).getFloat();
    }
}
  1. 解析规则接口,负责协议解码,就是将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();
    }

}
  1. 接收数据体
@Data
public class TcpServerDTO {
    
    /** 编号 */
    private Integer num;

	......

}
  1. 请求处理线程
@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));
    }
}
  1. 储存命令接口
public interface SaveDTOHandler {

    void save(TcpServerDTO dto);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

田秋浩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值