网络编程:使用UDP实现数据帧的接收

目录

1、需求

2、逻辑处理

3、代码实现

4、总结


1、需求

        使用java代码实现数据帧的接收需求,完成数据到数据库的存储。

2、逻辑处理

        由于udp传输不保证数据的有序性、可靠性,所以在做业务开发的时候,要程序员自己考虑需求完善udp的缺陷。因此我们定义几个字段保证数据的有序,对于可靠性未进行考虑。

3、代码实现

        监听端口55000,等待数据的发送:

@Service
public class UDPReceiverSuper {
    private static final int BUFFER_SIZE = 1044;
    private static final int HEAD_SIZE = 20;
    private static final int DATA_SIZE = 1024;
    private static final int MAX_BUFFER_SIZE = 1 * 1024 * 1024; // 缓冲器大小设置为1MB
    private static final double MAX_BUFFER_THRESHOLD = 0.8; // 缓冲区阈值
    private static final int MAX_BUFFER_INDEX = (int) (MAX_BUFFER_SIZE * MAX_BUFFER_THRESHOLD / DATA_SIZE); //缓冲区元素数量阈值

    //timestampToBufferMap存储的是:时间戳,TreeMap,TreeMap里面存储的是:当前包序号,接受数据的对象
    private Map<Long, ConcurrentHashMap<Long, DatagramPacket>> timestampToBufferMap = new HashMap();
    private long timeStamp;
    private boolean isClosed = false;// 使用阻塞队列作为缓冲区
    private long errorPackageSum = 0;
    private int frameNum;        //用于帧计数

    Thread udpReceiverThread;

    @Value("${GK.GKOriginalDataFilePath}")
    private String GKOriginalDataFilePath; // 管控原始数据文件存储路径

    @Value("${HP.storagePath}")
    private String storagePath;    //高性能数据接收路径
    @Autowired
    private INetworkConfigService networkConfigService;
    @Autowired
    private DealGkDataServiceSuperWithNewThread dealGkDataServiceSuperWithNewThread;
    @Autowired
    private DealGkDataServiceSuperWithThreadPoolAndBuffer dealGkDataServiceSuperWithThreadPoolAndBuffer;
    @Autowired
    private DealGkDataServiceSuperWithThreadPool dealGkDataServiceSuperWithThreadPool;
    @Autowired
    private SaveGKOriginalDataService saveGKOriginalDataService;
    @Autowired
    private SaveGKOriginalDataServiceWithBuffer saveGKOriginalDataServiceWithBuffer;



    public UDPReceiverSuper() {
    }

    public void start() {
        //创建父文件夹

        Path path = Paths.get(storagePath);
        if (Files.notExists(path)) {
            try {
                Files.createDirectories(path);
                System.out.println("Directories created successfully: " + storagePath);
            } catch (IOException e) {
                System.err.println("Failed to create directories: " + e.getMessage());
            }
        } else {
            System.out.println("Directories already exist: " + storagePath);
        }

        // 启动接收数据的线程
        if (udpReceiverThread == null) {
            udpReceiverThread = new Thread(new Receiver());
            udpReceiverThread.start();
        }
    }

    //数据帧头定义
    private class PackageHeader {
        public long id = 0;
        public long timestamp = 0;
        public long totalPackageNum = 0;
        public long currentPackageNum = 0;
        public long dataLength = 0;
    }


    // 接收数据的线程
    private class Receiver implements Runnable {
        @Override
        public void run() {
            NetworkConfig networkConfig = networkConfigService.selectNetworkConfigById(1L);
            String port = networkConfig.getPort();
            String ip = networkConfig.getIp();
            System.out.println("实际未绑定ip");
            System.out.println("ip: " + ip + "  port: " + port);
            try {
                DatagramSocket ds = new DatagramSocket(Integer.parseInt(port));
                if (ds != null) {
                    isClosed = false;
                }
                System.out.println("udpReceiver_ds: " + ds + "   等待接收数据......");


                while (true) {
                    if (isClosed) {
                        break;
                    }
                    byte[] receiveData = new byte[BUFFER_SIZE];   //接收数据缓存区,大小为1044
                    DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                    ds.receive(receivePacket);     //接收数据
                    byte[] data1 = receivePacket.getData();
                    frameNum++;
//                    System.out.println("当前帧数为: " + frameNum);   //todo 用于打印输出当前接收到的帧数

                    ByteBuffer byteBuffer1 = ByteBuffer.allocate(data1.length);
                    byteBuffer1.put(data1);
                    byteBuffer1.flip();   //flip操作是将:写模式切换到读模式,将‘limit’设置为当前的‘position’,将‘position’重置为0
//                    ByteBuffer byteBuffer1 = ByteBuffer.allocate(receiveData.length);
//                    byteBuffer1.put(receiveData);
//                    byteBuffer1.flip();   //flip操作是将:写模式切换到读模式,将‘limit’设置为当前的‘position’,将‘position’重置为0
                    /*两种情况:1、接收管控  2、接收高性能*/
                    byteBuffer1.order(ByteOrder.LITTLE_ENDIAN);  //转化为小端
                    int headerType = byteBuffer1.getInt();       //得到设备标识符
                    if (headerType == 1) {
                        /*解决方法一: 这个是采用多线程的方式进行写入数据到txt文件*/
                        saveGKOriginalDataService.saveGKOriginalData(receivePacket, GKOriginalDataFilePath);
                        /*解决方法二:直接处理管控的函数*/
//                        dealGkDataServiceSuperWithNewThread.dealGKApi(byteBuffer1);
                        /*解决方法三:在UDPReceiverSuperSuper类里面,并且要在NetworkConfigController中进行函数*/
                        /*解决方法四:使用线程池的方式解决,每接收一帧,就开始处理*/
//                        dealGkDataServiceSuperWithThreadPoolAndBuffer.dealGKApi(byteBuffer1);
                        /*解决方法五:直接开启线程进行处理数据,这个方法是对的*/
                        dealGkDataServiceSuperWithThreadPool.dealGKApi(byteBuffer1);
                        /*解决方法六:将接收到的数据存储到缓冲区中,然后使用多线程从缓冲区中取出,方法实现写在method3中*/
                    } 

        业务处理逻辑:

package com.ruoyi.system.service.customService.dealGKService_ThreadPool;

import com.ruoyi.system.domain.*;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.utlis.CSVFileUtil;
import com.ruoyi.system.utlis.ConvertUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.LongAdder;

/**
 * @Author 不要有情绪的  ljy
 * @Date 2024/3/22 15:54
 * @Description:
 */
@Service
public class DealGkDataServiceSuperWithThreadPool {


    @Value("${GK.statusLogPath}")
    private String stateLogFilePath;  //状态日志存储路径


    @Autowired
    private OperateLogInfoMapper operateLogInfoMapper;
    @Autowired
    private InstructLogInfoMapper instructLogInfoMapper;
    @Autowired
    private Instruct1553bLogInfoMapper instruct1553bLogInfoMapper;
    @Autowired
    private InstructRs422LogInfoMapper instructRs422LogInfoMapper;
    @Autowired
    private StateLogInfoMapper stateLogInfoMapper;
    @Autowired
    private ErrorLogInfoMapper errorLogInfoMapper;


    int frontTimeFlag = -1;

    private int currentReceivedFrameNum = 0;  //用于计算管控接收帧数

    private Map<Integer, BlockingQueue<byte[]>> currTimeFlagToQueue = new HashMap<>();
    int threadNum = 1;
    private ExecutorService threadPool;

    private Counter counter = new Counter();




    public DealGkDataServiceSuperWithThreadPool() {
        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
        System.out.println("核心线程数:   " + corePoolSize);
        int maximumPoolSize = corePoolSize * 2;
        long keepAliveTime = 60L;
        threadPool = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>()
        );
    }

    public void dealGKApi(ByteBuffer byteBuffer) {
        currentReceivedFrameNum++;
        if (currentReceivedFrameNum % 1000 == 0) {
            System.out.println("管控当前接收的数据帧数(每隔1000打印一次): " + currentReceivedFrameNum);
        }


        int currTimeFlag = byteBuffer.getInt();       //当前时间标识,用于区分是否丢包
        int packagesTotalNum = byteBuffer.getInt();   //表示当前发送的包总数
        int currPackageNum = byteBuffer.getInt();     //表示当前包序号
        int messageLength = byteBuffer.getInt();     //消息长度
        int remainingBytes = byteBuffer.remaining();
        byte[] remainingData = new byte[messageLength];   //用于获取日志长度 1024
        if (remainingBytes > 0) {
            byteBuffer.get(remainingData); // 获取剩余的字节
            threadPool.submit(new GKRunnable(remainingData));
        }

    }


    class GKRunnable implements Runnable {
        private byte[] bytes;

        public GKRunnable(byte[] remainingData) {
            this.bytes = remainingData;
        }

        @Override
        public void run() {
            System.out.println("新启动一个线程用于处理管控日志,当前线程名:  " + Thread.currentThread().getName());
            ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);   //将不用byte数组放到ByteBuffer中
            byteBuffer.put(bytes);
            dealWithAssembledFrame(byteBuffer);
        }

        private void dealWithAssembledFrame(ByteBuffer byteBuffer) {
            byteBuffer.flip();
            byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  //转化为小端
            dealLogPart(byteBuffer);
        }

        /*
           todo 要写的内容为:出现错误就要将异常抛出,并且找到帧头的位置
         */
        private void dealLogPart(ByteBuffer byteBuffer) {
//            /*找到帧头的位置*/
//            searchFrameHeaderAndSetPosition(byteBuffer);

            while (byteBuffer.position() != byteBuffer.capacity() && (byteBuffer.position() + 4) < byteBuffer.capacity()) {
                try {
                    /*找到帧头的位置*/
                    searchFrameHeaderAndSetPosition(byteBuffer);
                    int startPosition = byteBuffer.position();//获取开始的长度
                    //每个日志都包含的部分
                    byte[] bytes2 = new byte[2];
                    byteBuffer.get(bytes2);
                    String frameHeaderInfo = ConvertUtil.byteArrayToHexString(bytes2);   //日志帧头字段
                    short logLength = byteBuffer.getShort();                             //日志长度
                    int logNumber = byteBuffer.getShort();                             //日志编号
//                    System.out.println(logNumber + logLength);
                    byte logType = byteBuffer.get();//byte转化为字符串                     //日志类型
                    String logTypeStr = String.format("%02X", logType);
                    int time = byteBuffer.getInt();                //日志时间
                    //根据日志类型,选择处理日志剩余方式
                    if ("01".equals(logTypeStr) || "02".equals(logTypeStr) || "03".equals(logTypeStr)) {
                        byte sendingAndReceivingBit = byteBuffer.get();
                        byte sourceDeviceId = byteBuffer.get();
                        byte targetDeviceId = byteBuffer.get();
                        //得到日志内容长度    先将日志长度转化为十进制,然后减掉帧头信息,减掉日志长度,减掉日志编号,减掉日志类型、减掉时间,减掉校验码,减掉发送接收位,减掉源原设备ID,减掉目标设备ID
                        int logContentLength = logLength - 2 - 2 - 2 - 1 - 4 - 1 - 1 - 1 - 1;
                        String instructDataContent = null;
                        if ("01".equals(logTypeStr)) {  //子地址+数据内容,子地址占1个字节
                            byte[] bytes = new byte[1];
                            byteBuffer.get(bytes);
                            String subAddress = ConvertUtil.byteArrayToHexString(bytes);
                            int dataContentLength = logContentLength - 1;
                            byte[] bytes1 = new byte[dataContentLength];// 输出剩余字节的十六进制表示,即指令数据内容
                            byteBuffer.get(bytes1);
                            instructDataContent = ConvertUtil.byteArrayToHexString(bytes1);
                            dealInstruct1553bLog(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time, sendingAndReceivingBit, sourceDeviceId, targetDeviceId, subAddress, instructDataContent);
                        } else if ("02".equals(logTypeStr)) { //can指令类型(1个字节)  + ID(子地址)+数据内容,ID占4个字节
                            byte[] bytes = new byte[1];
                            byteBuffer.get(bytes);
                            String canInstructType = ConvertUtil.byteArrayToHexString(bytes);
                            byte[] bytes1 = new byte[4];
                            byteBuffer.get(bytes1);
                            String subAddress = ConvertUtil.byteArrayToHexString(bytes1);
                            int dataContentLength = logContentLength - 1 - 4;
                            byte[] ID = new byte[dataContentLength];// 输出剩余字节的十六进制表示,即指令数据内容
                            byteBuffer.get(ID);
                            instructDataContent = ConvertUtil.byteArrayToHexString(ID);
                            dealInstructCANLog(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time, sendingAndReceivingBit, sourceDeviceId, targetDeviceId, canInstructType, subAddress, instructDataContent);
                        } else if ("03".equals(logTypeStr)) { //数据内容
                            byte[] bytes1 = new byte[logContentLength];
                            byteBuffer.get(bytes1);
                            instructDataContent = ConvertUtil.byteArrayToHexString(bytes1);
                            dealInstructRs422Log(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time, sendingAndReceivingBit, sourceDeviceId, targetDeviceId, instructDataContent);
                        }
                    } else if ("04".equals(logTypeStr)) {
                        //存储到excel表中
//                        dealStateLog(byteBuffer, startPosition, time);
                        //存储到数据库中
                        dealStateLog(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time);
                    } else if ("05".equals(logTypeStr)) {
                        dealOperateLog(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time);
                    } else if ("06".equals(logTypeStr)) {
                        dealErrorLog(byteBuffer, startPosition, logLength, logNumber & 0xFFFF, time);
                    } else {
                        System.out.println("as;dasd");
                    }
                } catch (Exception e) {
                    if (e.getCause().toString().contains("SQLIntegrityConstraintViolationException")) {
                        counter.increment();
                    }else {
                        e.printStackTrace();

//                        System.err.println(e.getMessage());
                    }
                    //在处理过程报错了,那么就认为日志内容错误,就要重新寻找帧头的位置
                    searchFrameHeaderAndSetPosition(byteBuffer);
                }
            }

            System.out.println("线程名称:" + Thread.currentThread().getName() + "  线程id:" + Thread.currentThread().getId() + "  处理完成!");

            System.out.println("========"+counter.getCount());
        }

        private void dealStateLog(ByteBuffer byteBuffer, int startPosition, short logLength, int logNumber, int time) {
            byte[] bytes92 = new byte[92];  //将92个长度传递到byte数组中
            byteBuffer.get(bytes92);
            int endPosition = byteBuffer.position();
            byte checkCodeByte = calculateCheckCode(byteBuffer, startPosition, endPosition);
            byte checkCode = byteBuffer.get();  //用于校验日志的正确和完整性
            boolean logIsIntegrity = logIsIntegrity(checkCode, checkCodeByte);
            if (logIsIntegrity) {
                StateLogInfo stateLogInfo = new StateLogInfo();
                stateLogInfo.setLogLength(Short.toString(logLength));
                stateLogInfo.setLogNumber(Long.valueOf(logNumber));
                stateLogInfo.setTime(Long.valueOf(time));
//                Integer size = stateLogInfoMapper.searchDataIsDuplicate(stateLogInfo);
//                if (size > 0) { //判断数据库中是否已经存在
//                    return;
//                }
                //将参数设置到stateLogInfo实例中
                setParameter(bytes92, stateLogInfo);
                stateLogInfoMapper.insertStateLogInfo(stateLogInfo);
            } else {
                logIsNotIntegrity(byteBuffer, startPosition, endPosition);
            }
        }


        private void searchFrameHeaderAndSetPosition(ByteBuffer byteBuffer) {
            /*找到帧头的位置*/
            int frameHeaderPosition = findFrameHeaderPosition(byteBuffer, hexStringToByteArray("eafc"));
            if (frameHeaderPosition != -1) {
                byteBuffer.position(frameHeaderPosition);
            } else {
                System.out.println("未找到帧头为 eafc 的位置");
                return;   //说明从头查到尾都没有查到,就直接退出
            }
        }


        /**
         * 判断日志是否完整的函数,如果不完整,那么就要找下一个eafc帧头的位置
         *
         * @param byteBuffer
         */
        private void logIsNotIntegrity(ByteBuffer byteBuffer, int startPosition, int endPosition) {
            System.out.println("日志不完整!丢掉");
            //如果日志不完整,那么就要找到下一帧的头位置
            /*找到帧头的位置*/
            searchFrameHeaderAndSetPosition(byteBuffer);
        }

      


        /**
         * 判断是否为完整日志的函数isIntegrity(日志长度 + 帧头 - 校验码)
         *
         * @param a
         * @param b
         * @return
         */
        private boolean logIsIntegrity(byte a, byte b) {
            return a == b;
        }


        /**
         * 计算校验码的函数
         *
         * @param byteBuffer
         * @param startPosition
         * @param endPosition
         * @return
         */
        private byte calculateCheckCode(ByteBuffer byteBuffer, int startPosition, int endPosition) {
            int length = endPosition - startPosition;
            byteBuffer.position(startPosition);
            byte res = 0;
            for (int i = 0; i < length; i++) {
                byte b = byteBuffer.get();
                res += b;
            }
            return res;
        }

        /**
         * 通过遍历的方式得到帧头的位置
         *
         * @param byteBuffer
         * @param frameHeader
         * @return
         */
        public int findFrameHeaderPosition(ByteBuffer byteBuffer, byte[] frameHeader) {
            // 记录当前位置
            int startPosition = byteBuffer.position();
            // 遍历 ByteBuffer 从当前位置开始搜索帧头
            for (int i = startPosition; i < byteBuffer.limit() - frameHeader.length + 1; i++) {
                // 标记当前位置
                byteBuffer.position(i);
                boolean found = true;
                for (int j = 0; j < frameHeader.length; j++) {
                    if (byteBuffer.get() != frameHeader[j]) {
                        found = false;
                        break;
                    }
                }
                if (found) {
                    // 恢复 ByteBuffer 的当前位置
//                byteBuffer.position(startPosition);
                    return i; // 返回帧头 'e' 的位置
                }
            }
            // 恢复 ByteBuffer 的当前位置
//        byteBuffer.position(startPosition);
            // 如果没有找到,返回 -1 表示未找到
            return -1;
        }


        /**
         * 将十六进制字符串转换为字节数组的方法
         *
         * @param hexString
         * @return
         */
        public byte[] hexStringToByteArray(String hexString) {
            int len = hexString.length();
            byte[] byteArray = new byte[len / 2]; // 每两个十六进制字符表示一个字节
            for (int i = 0; i < len; i += 2) {
                byteArray[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                        + Character.digit(hexString.charAt(i + 1), 16));
            }
            return byteArray;
        }

    }

}

class Counter {
    private LongAdder count = new LongAdder();

    public void increment() {
        count.increment();
    }

    public int getCount() {
        return count.intValue();
    }
}

解释:以上代码是采用多线程,将接收到的数据帧解析为日志,并将日志存储到数据库中,根据日志类型不同,存储到不同的数据库表中。

4、总结

        采用DatagramPacket实现数据帧接收准备,将接收到的每一帧数据解析为日志,每一帧都交给一个线程去处理,为节省线程频繁创建和销毁的资源,采用多线程。

学习之所以会想睡觉,是因为那是梦开始的地方。
ଘ(੭ˊᵕˋ)੭ (开心) ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)
                                                                                                        ------不写代码不会凸的小刘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/756669.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

L03_Redis知识图谱

这些知识点你都掌握了吗?大家可以对着问题看下自己掌握程度如何?对于没掌握的知识点,大家自行网上搜索,都会有对应答案,本文不做知识点详细说明,只做简要文字或图示引导。 Redis 全景图 Redis 知识全景图都包括什么呢?简单来说,就是“两大维度,三大主线”。 Redis …

MySQL连接IDEA(Java Web)保姆级教程

第一步&#xff1a;新建项目(File)->Project 第二步&#xff1a;New Project(JDK最好设置1.8版本与数据库适配&#xff0c;详细适配网请到MySQL官网查询MySQL :: MySQL 8.3 Reference Manual :: Search Results) 第三步&#xff1a;点中MySQLTest(项目名)并连续双击shift键-…

昇思25天学习打卡营第2天|数据集Dataset

学习目标&#xff1a;熟练掌握mindspore.dataset mindspore.dataset中有常用的视觉、文本、音频开源数据集供下载&#xff0c;点赞、关注收藏哦 了解mindspore.dataset mindspore.dataset应用实践 拓展自定义数据集 昇思平台学习时间记录: 一、关于mindspore.dataset minds…

【STM32】在标准库中使用定时器

1.TIM简介 STM32F407系列控制器有2个高级控制定时器、10个通用定时器和2个基本定时器。通常情况下&#xff0c;先看定时器挂在哪个总线上APB1或者APB2&#xff0c;然后定时器时钟需要在此基础上乘以2。 2.标准库实现定时中断 #ifndef __BSP_TIMER_H #define __BSP_TIMER_H#if…

.[emcrypts@tutanota.de].mkp勒索病毒新变种该如何应对?

引言 在数字化时代&#xff0c;随着信息技术的迅猛发展&#xff0c;网络安全问题日益凸显。其中&#xff0c;勒索病毒作为一种极具破坏力的恶意软件&#xff0c;给个人和企业带来了巨大的经济损失和数据安全风险。近期&#xff0c;一种名为“.mkp勒索病毒”的新型威胁开始在网络…

多线程引发的安全问题

前言&#x1f440;~ 上一章我们介绍了线程的一些基础知识点&#xff0c;例如创建线程、查看线程、中断线程、等待线程等知识点&#xff0c;今天我们讲解多线程下引发的安全问题 线程安全&#xff08;最复杂也最重要&#xff09; 产生线程安全问题的原因 锁&#xff08;重要…

在 Python 中创建列表时,应该写 `[]` 还是 `list()`?

在 Python 中&#xff0c;创建列表有两种写法&#xff1a; # 写法一&#xff1a;使用一对方括号 list_1 []# 写法二&#xff1a;调用 list() list_2 list() 那么哪种写法更好呢&#xff1f; 单从写法上来看&#xff0c;[] 要比 list() 简洁&#xff0c;那在性能和功能方面…

江科大笔记—读写内部闪存FLASH读取芯片ID

读写内部闪存FLASH 右下角是OLED&#xff0c;然后左上角在PB1和PB11两个引脚&#xff0c;插上两个按键用于控制。下一个代码读取芯片ID&#xff0c;这个也是接上一个OLED&#xff0c;能显示测试数据就可以了。 STM32-STLINK Utility 本节的代码调试&#xff0c;使用辅助软件…

[机缘参悟-200] - 对自然、人性、人生、人心、人际、企业、社会、宇宙全面系统的感悟 - 全图解

对自然、人性、人生、人心、人际、企业、社会、宇宙进行全面系统的感悟&#xff0c;是一个极其深邃且复杂的主题。以下是对这些领域的简要感悟&#xff1a; 自然&#xff1a; 自然是人类生存的根基&#xff0c;它充满了无尽的奥秘和美丽。自然界的平衡和循环规律&#xff0c;教…

运算符重载之日期类的实现

接上一篇文章&#xff0c;废话不多说&#xff0c;直接上代码 Date.h #pragma once #include<iostream> using namespace std; #include<assert.h>class Date {//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend …

学编程容易遇到的误区,请提前规避

随着互联网行业的蓬勃发展和编程技术的普及&#xff0c;越来越多的人开始对编程感兴趣。然而&#xff0c;编程学习并非一蹴而就&#xff0c;新手入门时常常会陷入误区&#xff0c;影响学习状态效率。 今天&#xff0c;我们来一起揭开编程学习常见的五大误区&#xff0c;希望能…

Workbench密码登录登录失败

Workbench密码登录登录失败操作系统禁用了密码登录方式&#xff0c;会导致使用了正确的用户名和密码仍无法登录 sudo vim /etc/ssh/sshd_config 输入O进入编辑 改完后重启 systemctl restart sshd.service 登录报错 有试了几遍登上了 可能是改完还要等一会儿

Python:探索高效、智能的指纹识别技术(简单易懂)

目录 概括 导入库 函数一 参数&#xff1a; 函数二 函数三 主函数 运行结果 src&#xff1a; model_base 7.bmp ​编辑 总结 概括 指纹识别是一种基于人体生物特征的身份验证技术。它通过捕捉和分析手指上的独特纹路和细节特征&#xff0c;实现高准确度的身份识别。…

账做错了怎么办?看完这篇三分钟学会调错账|柯桥职业技能培训

作为会计遇到错账、漏账在所难免。既然错误在所难免&#xff0c;如果纠正错误就十分重要。今天就跟小编一起学下如何调账。 在处理错账之前&#xff0c;我们首先要把会计科目做一下分类&#xff0c;以便于我们找到错账的类型和原因。会计科目可以分为资产负债类科目&#x…

2.SQL注入-字符型

SQL注入-字符型(get) 输入kobe查询出现id和邮箱 猜测语句,字符在数据库中需要用到单引号或者双引号 select 字段1,字段2 from 表名 where usernamekobe;在数据库中查询对应的kobe&#xff0c;根据上图对应上。 select id,email from member where usernamekobe;编写payload语…

Emp.dll文件丢失?理解Emp.dll重要性与处理常见问题

在繁多的动态链接库&#xff08;DLL&#xff09;文件中&#xff0c;emp.dll 可能不是最广为人知的&#xff0c;但在特定软件或环境中&#xff0c;它扮演着关键角色。本文旨在深入探讨 emp.dll 的功能、重要性以及面对常见问题时的解决策略。 什么是 emp.dll&#xff1f; Emp.d…

【Java Gui精美界面】IDEA安装及配置SwingX

SwingX 是一个基于 Swing 的 Java GUI 库&#xff0c;旨在为 Swing 提供额外的功能和丰富的组件 特点描述基于 Swing继承了 Swing 的所有特性和功能。丰富组件SwingX 提供了一组高级 UI 组件&#xff0c;例如 TreeTable仍在发展中不活跃的发展ing。。。支持搜索高亮如 TreeTab…

【单元测试】Controller、Service、Repository 层的单元测试

Controller、Service、Repository 层的单元测试 1.Controller 层的单元测试1.1 创建一个用于测试的控制器1.2 编写测试 2.Service 层的单元测试2.1 创建一个实体类2.2 创建服务类2.3 编写测试 3.Repository 1.Controller 层的单元测试 下面通过实例演示如何在控制器中使用 Moc…

【漏洞复现】飞企互联——SQL注入

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 飞企互联-FE企业运营管理平台是一个基于云计算、智能化、大数据…

JAVA高级进阶13单元测试、反射、注解

第十三天、单元测试、反射、注解 单元测试 介绍 单元测试 就是针对最小的功能单元(方法)&#xff0c;编写测试代码对其进行正确性测试 咱们之前是如何进行单元测试的&#xff1f; 有啥问题 &#xff1f; 只能在main方法编写测试代码&#xff0c;去调用其他方法进行测试。 …