文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java 串口通信(RS232/485)

2023-08-17 11:27

关注

在这里插入图片描述
Java 实现串口通信,同时通过 WebSocket 与 UI 实时交互传递通信数据

准备工作:

虚拟串口工具:Launch Virtual Serial Port Driver
串口调试助手:SSCOM

在这里插入图片描述

RS485

根据 Modbus 协议,常规485通讯的信息发送形式如下:地址  功能码 数据信息 校验码1byte 1byte  nbyte  2byte

在线 CRC检验码计算:CRC 测试链接

1.Java 串口通信配置

1.扩展包和依赖库

RXTXcomm.jar   放入 {JAVA_HOME}/jre/lib/extrxtxserial.dll 放入 {JAVA_HOME}/jre/bin

以上两个包可以直接网上下载,注意和JDK版本搭配即可

2.Pom配置

串口通信包:rxtx

    4.0.0    org.example    SerialPort    1.0-SNAPSHOT            8        8        UTF-8                            org.springframework.boot            spring-boot-starter-web            2.7.4                            org.projectlombok            lombok            1.18.24                            org.springframework            spring-websocket            5.3.27                            org.rxtx            rxtx            2.1.7                                    nexus-aliyun            nexus-aliyun            http://maven.aliyun.com/nexus/content/groups/public/                            true                                        false                                                public            aliyun nexus            http://maven.aliyun.com/nexus/content/groups/public/                            true                                        false                        

2.启动类

package com.serial.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class SerialApplication {    public static void main(String[] args) {        SpringApplication.run(SerialApplication.class,args);    }}

3.工具包类

1.Common

package com.serial.demo.util;public class Common {    public static String HEX_STRING = "0123456789ABCDEF";    public static final String NONE = "无";    public static final String ODD = "奇";    public static final String EVEN = "偶";    public static final String FORMAT_HEX="HEX";}

2.Crc16Modbus

CRC16 Modbus Java 实现:计算数据的校验码
package com.serial.demo.util;public class Crc16Modbus {            private static final int INITIAL_VALUE = 0xFFFF;    private static final boolean IS_OUT_PUT_OVER_TURN = true;        public static byte[] getData(String... hexes) {        byte[] data = new byte[hexes.length];        int i = 0;        for (String hex:hexes){            //先转为数字在转为 byte            data[i++] = (byte) Integer.parseInt(hex, 16);        }        return merge(data);    }        public static byte[] merge(byte[] data) {        byte[] crc = getCrc16(data);        int dLen = data.length;        int cLen = crc.length;        byte[] result = new byte[dLen + cLen];        System.arraycopy(data,0,result,0,dLen);        System.arraycopy(crc,0,result,dLen,cLen);        return result;    }        private static byte[] getCrc16(byte[] data) {        int len = data.length;        int crc = INITIAL_VALUE;        int i, j;        for (i = 0; i < len; i++) {            // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器            crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (data[i] & 0xFF));            for (j = 0; j < 8; j++) {                // 把 CRC 寄存器的内容右移一位(朝低位)用 0 填补最高位, 并检查右移后的移出位                if ((crc & 0x0001) > 0) {                    // 如果移出位为 1, CRC寄存器与多项式A001进行异或                    crc = crc >> 1;                    crc = crc ^ 0xA001;                } else {                    // 如果移出位为 0,再次右移一位                    crc = crc >> 1;                }            }        }        return intToBytes(crc);    }        private static byte[] intToBytes(int value)  {        byte[] src = new byte[2];        byte hig = (byte) ((value>>8) & 0xFF);        byte low = (byte) (value & 0xFF);        if (IS_OUT_PUT_OVER_TURN){            src[0] = low;            src[1] = hig;        } else {            src[0] = hig;            src[1] = low;        }        return src;    }        public static String byteTo16String(byte[] data) {        StringBuffer buffer = new StringBuffer();        for (byte b : data) {            byteToHex(buffer,b);        }        return buffer.toString().toUpperCase();    }        public static void byteToHex(StringBuffer buffer ,byte b) {        if (b < 0) {            buffer.append(Integer.toString(b + 256, 16));        } else if (b == 0) {            buffer.append("00 ");        } else if (b > 0 && b <= 15) {            buffer.append("0" + Integer.toString(b, 16));        } else if (b > 15) {            buffer.append(Integer.toString(b, 16));        }        buffer.append(" ");    }}

3.SerialUtil

package com.serial.demo.util;import gnu.io.SerialPort;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.nio.charset.StandardCharsets;public class SerialUtil {        public static String toHex(String str){        StringBuffer sbf = new StringBuffer();        byte[] b = str.getBytes(StandardCharsets.UTF_8);        for (int i = 0; i < b.length; i++) {            String hex = Integer.toHexString(b[i] & 0xFF);            if (hex.length() == 1) {                hex = '0' + hex;            }            sbf.append(hex.toUpperCase() + "  ");        }        return sbf.toString().trim();    }        public static String toStr(String hex) {        return new String(hexToByte(hex));    }        public static byte[] hexToByte(String hex){        hex = hex.toUpperCase().replace(" ","");        ByteArrayOutputStream bao = new ByteArrayOutputStream(hex.length() / 2);        // 将每2位16进制整数组装成一个字节        for (int i = 0; i < hex.length(); i += 2) {            bao.write((Common.HEX_STRING.indexOf(hex.charAt(i)) << 4 | Common.HEX_STRING.indexOf(hex.charAt(i + 1))));        }        return bao.toByteArray();    }        public static int getParity(String checkBit){        if (Common.NONE.equals(checkBit)){            return SerialPort.PARITY_NONE;        } else if (Common.ODD.equals(checkBit)){            return SerialPort.PARITY_ODD;        } else if (Common.EVEN.equals(checkBit)){            return SerialPort.PARITY_EVEN;        } else {            return SerialPort.PARITY_NONE;        }    }        public static byte[] readFromPort(InputStream in) {        byte[] bytes = {};        try {            // 缓冲区大小为一个字节            byte[] readBuffer = new byte[1];            int bytesNum = in.read(readBuffer);            while (bytesNum > 0) {                bytes = concat(bytes, readBuffer);                bytesNum = in.read(readBuffer);            }        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (in != null) {                    in.close();                    in = null;                }            } catch (IOException e) {                e.printStackTrace();            }        }        return bytes;    }        public static String printHexString(String format, byte[] b) {        String result = new String(b);        if (Common.FORMAT_HEX.equals(format)){            return SerialUtil.toHex(result);        }        return result;    }        public static byte[] concat(byte[] firstArray, byte[] secondArray) {        if (firstArray == null || secondArray == null) {            if (firstArray != null) {                return firstArray;            }            if (secondArray != null) {                return secondArray;            }            return null;        }        byte[] bytes = new byte[firstArray.length + secondArray.length];        System.arraycopy(firstArray, 0, bytes, 0, firstArray.length);        System.arraycopy(secondArray, 0, bytes, firstArray.length, secondArray.length);        return bytes;    }}

4.WebSocket 配置

1.启动配置

package com.serial.demo.socket;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configurationpublic class WebSocketConfig {        @Bean    public ServerEndpointExporter serverEndpointExporter(){        return new ServerEndpointExporter();    }}

2.监听配置

package com.serial.demo.socket;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.websocket.OnClose;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;@Slf4j@Component@ServerEndpoint("/websocket/{sid}")public class SerialWebSocket {        private static Map webSocketMap = new ConcurrentHashMap<>(16);        private Session session;        private String sid;        @OnOpen    public void onOpen(@PathParam("sid") String sid,Session session){        this.session = session;        this.sid = sid;        webSocketMap.put(sid,this);        //sendMessage(sid,"Hello:");    }        @OnClose    public void onClose(@PathParam("sid") String sid){        try {            SerialWebSocket socket = webSocketMap.remove(sid);            if (socket != null){                socket.session.close();                socket = null;            }        } catch (IOException e) {            log.error("Close {} exception:",sid,e);        }    }        @OnMessage    public void onMessage(String message){        log.info("sid {} msg {}",this.sid,message);    }        public static void sendMessage(String sid,String message){        SerialWebSocket socket = webSocketMap.get(sid);        if (socket != null){            try {                socket.session.getBasicRemote().sendText(message);            } catch (IOException e) {                log.error("Send {} message {} exception:",sid,message,e);            }        }    }        public static void broadcast(String message){        for (String sid:webSocketMap.keySet()){            sendMessage(sid,message);        }    }}

5.UI交互类

1.串口配置对象

package com.serial.demo.entity;import lombok.Data;@Datapublic class SerialEntity {    private String portId;    private int bitRate;    private int dataBit;    private int stopBit;    private String checkBit;    private String format;}

2.串口信息获取接口

package com.serial.demo.controller;import com.serial.demo.config.SerialPortConfig;import com.serial.demo.util.SerialUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;@CrossOrigin@RestController@RequestMapping("/serial")public class SerialController {    @Autowired    SerialPortConfig serial;        @GetMapping("/getSerialPortList")    public List getSerialPortList(){        return serial.getSerialPortList();    }        @GetMapping("/toHex")    public String toHex(String str){        return SerialUtil.toHex(str);    }        @GetMapping("/toStr")    public String toStr(String hex){        return SerialUtil.toStr(hex);    }}

3.RS232接口

package com.serial.demo.controller;import com.serial.demo.config.Rs232Config;import com.serial.demo.entity.SerialEntity;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@CrossOrigin@RestController@RequestMapping("/serial/232")public class Rs232Controller {    @Autowired    Rs232Config rs232Config;        @PostMapping("/open")    public boolean open(@RequestBody SerialEntity serial){        return rs232Config.openPort(serial);    }        @GetMapping("/close/{portId}")    public void close(@PathVariable("portId") String portId){        rs232Config.closePort(portId);    }        @GetMapping("/send/{portId}/{format}/{msg}")    public void close(@PathVariable("portId") String portId,@PathVariable("format") String format,@PathVariable("msg") String msg){        rs232Config.sendData(portId,format,msg);    }}

4.RS485接口

package com.serial.demo.controller;import com.serial.demo.config.Rs485Config;import com.serial.demo.entity.SerialEntity;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@CrossOrigin@RestController@RequestMapping("/serial/485")public class Rs485Controller {    @Autowired    Rs485Config rs485Config;        @PostMapping("/open")    public boolean open(@RequestBody SerialEntity serial){        return rs485Config.openPort(serial);    }        @GetMapping("/close/{portId}")    public void close(@PathVariable("portId") String portId){        rs485Config.closePort(portId);    }        @GetMapping("/send/{portId}/{format}/{msg}")    public void close(@PathVariable("portId") String portId,@PathVariable("format") String format,@PathVariable("msg") String msg){        rs485Config.sendData(portId,format,msg);    }}

6.串口配置类

1.串口配置

package com.serial.demo.config;import gnu.io.CommPortIdentifier;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.util.Collections;import java.util.Enumeration;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.stream.Collectors;@Slf4j@Componentpublic class SerialPortConfig {        private static Map serialMap;    @PostConstruct    private void init(){        refreshCom();    }        public void refreshCom(){        Enumeration portList = CommPortIdentifier.getPortIdentifiers();        CommPortIdentifier serial;        Map temp = new ConcurrentHashMap<>(16);        while (portList.hasMoreElements()){            serial = portList.nextElement();            if (serial.getPortType() == CommPortIdentifier.PORT_SERIAL){                temp.put(serial.getName(),serial);            }        }        serialMap = Collections.unmodifiableMap(temp);    }        public List getSerialPortList(){        return serialMap.keySet().stream().sorted().collect(Collectors.toList());    }        public Map getSerialMap(){        return serialMap;    }}

2.RS232串口配置

package com.serial.demo.config;import com.serial.demo.entity.SerialEntity;import com.serial.demo.util.Common;import com.serial.demo.util.SerialUtil;import gnu.io.CommPortIdentifier;import gnu.io.PortInUseException;import gnu.io.SerialPort;import gnu.io.UnsupportedCommOperationException;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.nio.charset.StandardCharsets;import java.util.Map;import java.util.TooManyListenersException;import java.util.concurrent.ConcurrentHashMap;@Slf4j@Componentpublic class Rs232Config {    private static final int DELAY_TIME = 1000;    @Autowired    SerialPortConfig config;        private Map serialPortMap = new ConcurrentHashMap<>(16);        public boolean openPort(SerialEntity serial) {        String portId = serial.getPortId();        CommPortIdentifier commPortIdentifier = config.getSerialMap().get(portId);        if (null != commPortIdentifier){            SerialPort serialPort = null;            int bitRate = 0,dataBit = 0,stopBit = 0,parity = 0;            try {                serialPort = (SerialPort) commPortIdentifier.open(portId,DELAY_TIME);                // 设置监听器生效 当有数据时通知                serialPort.notifyOnDataAvailable(true);                // 比特率、数据位、停止位、奇偶校验位                bitRate = serial.getBitRate();                dataBit = serial.getDataBit();                stopBit = serial.getStopBit();                parity = SerialUtil.getParity(serial.getCheckBit());                serialPort.setSerialPortParams(bitRate, dataBit, stopBit,parity);            } catch (PortInUseException e) {                log.error("Open CommPortIdentifier {} Exception:",serial.getPortId(),e );                return false;            } catch (UnsupportedCommOperationException e) {                log.error("Set SerialPortParams BitRate {} DataBit {} StopBit {} Parity {} Exception:",bitRate,dataBit,stopBit,parity,e);                return false;            }            // 设置当前串口的输入输出流            InputStream input;            OutputStream output;            try {                input = serialPort.getInputStream();                output = serialPort.getOutputStream();            } catch (IOException e) {                log.error("Get serialPort data stream exception:",e);                return false;            }            // 给当前串口添加一个监听器            try {                serialPort.addEventListener(new Serial232Listener(input,output,serial.getFormat()));            } catch (TooManyListenersException e) {                log.error("Get serialPort data stream exception:",e);                return false;            }            serialPortMap.put(portId,serialPort);            return true;        }        return false;    }        public void closePort(String portId){        SerialPort serialPort = serialPortMap.remove(portId);        if (null != serialPort){            serialPort.close();        }    }        public void sendData(String portId,String format,String message){        SerialPort serialPort = serialPortMap.get(portId);        if (null == serialPort){            return;        }        OutputStream output = null;        try {            byte[] bytes;            if (Common.FORMAT_HEX.equals(format)){                bytes = SerialUtil.hexToByte(message);            } else {                bytes = message.getBytes(StandardCharsets.UTF_8);            }            output = serialPort.getOutputStream();            output.write(bytes);            output.flush();        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            if (null != output){                try {                    output.close();                } catch (IOException e) {                    throw new RuntimeException(e);                }            }        }    }}

3.RS232串口监听

package com.serial.demo.config;import com.serial.demo.socket.SerialWebSocket;import com.serial.demo.util.Crc16Modbus;import com.serial.demo.util.SerialUtil;import gnu.io.SerialPortEvent;import gnu.io.SerialPortEventListener;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class Serial232Listener implements SerialPortEventListener {    InputStream inputStream;    OutputStream outputStream;    String format;    public Serial232Listener(InputStream input, OutputStream output, String format){        inputStream = input;        outputStream = output;        this.format = format;    }    @Override    public void serialEvent(SerialPortEvent event) {        switch (event.getEventType()) {            case SerialPortEvent.BI:            case SerialPortEvent.OE:            case SerialPortEvent.FE:            case SerialPortEvent.PE:            case SerialPortEvent.CD:            case SerialPortEvent.CTS:            case SerialPortEvent.DSR:            case SerialPortEvent.RI:            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:                break;            case SerialPortEvent.DATA_AVAILABLE:                // 当有可用数据时读取数据                byte[] readBuffer = null;                int availableBytes = 0;                try {                    availableBytes = inputStream.available();                    while (availableBytes > 0) {                        readBuffer = SerialUtil.readFromPort(inputStream);                        String needData = Crc16Modbus.byteTo16String(readBuffer);                        SerialWebSocket.broadcast(needData);                        availableBytes = inputStream.available();                    }                } catch (IOException e) {                }            default:                break;        }    }}

4.RS485串口配置

package com.serial.demo.config;import com.serial.demo.entity.SerialEntity;import com.serial.demo.util.Common;import com.serial.demo.util.Crc16Modbus;import com.serial.demo.util.SerialUtil;import gnu.io.CommPortIdentifier;import gnu.io.PortInUseException;import gnu.io.SerialPort;import gnu.io.UnsupportedCommOperationException;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.nio.charset.StandardCharsets;import java.util.Map;import java.util.TooManyListenersException;import java.util.concurrent.ConcurrentHashMap;@Slf4j@Componentpublic class Rs485Config {    private static final int DELAY_TIME = 1000;    @Autowired    SerialPortConfig config;        private Map serialPortMap = new ConcurrentHashMap<>(16);        public boolean openPort(SerialEntity serial) {        String portId = serial.getPortId();        CommPortIdentifier commPortIdentifier = config.getSerialMap().get(portId);        if (null != commPortIdentifier){            SerialPort serialPort;            int bitRate = 0,dataBit = 0,stopBit = 0,parity = 0;            try {                serialPort = (SerialPort) commPortIdentifier.open(portId,DELAY_TIME);                // 设置监听器生效 当有数据时通知                serialPort.notifyOnDataAvailable(true);                serialPort.setDTR(true);                serialPort.setRTS(true);                // 比特率、数据位、停止位、奇偶校验位                bitRate = serial.getBitRate();                dataBit = serial.getDataBit();                stopBit = serial.getStopBit();                parity = SerialUtil.getParity(serial.getCheckBit());                serialPort.setSerialPortParams(bitRate, dataBit, stopBit,parity);            } catch (PortInUseException e) {                log.error("Open CommPortIdentifier {} Exception:",serial.getPortId(),e );                return false;            } catch (UnsupportedCommOperationException e) {                log.error("Set SerialPortParams BitRate {} DataBit {} StopBit {} Parity {} Exception:",bitRate,dataBit,stopBit,parity,e);                return false;            }            // 设置当前串口的输入输出流            InputStream input;            OutputStream output;            try {                input = serialPort.getInputStream();                output = serialPort.getOutputStream();            } catch (IOException e) {                log.error("Get serialPort data stream exception:",e);                return false;            }            // 给当前串口添加一个监听器            try {                serialPort.addEventListener(new Serial485Listener(input,output,serial.getFormat()));            } catch (TooManyListenersException e) {                log.error("Get serialPort data stream exception:",e);                return false;            }            serialPortMap.put(portId,serialPort);            return true;        }        return false;    }        public void closePort(String portId){        SerialPort serialPort = serialPortMap.remove(portId);        if (null != serialPort){            serialPort.close();        }    }        public void sendData(String portId,String format,String message){        SerialPort serialPort = serialPortMap.get(portId);        if (null == serialPort){            return;        }        OutputStream output = null;        try {            byte[] bytes = new byte[0];            if (Common.FORMAT_HEX.equals(format)){                bytes = SerialUtil.hexToByte(message);                bytes = Crc16Modbus.merge(bytes);            }            output = serialPort.getOutputStream();            output.write(bytes);            output.flush();        } catch (IOException e) {            throw new RuntimeException(e);        } finally {            if (null != output){                try {                    output.close();                } catch (IOException e) {                    throw new RuntimeException(e);                }            }        }    }}

5.RS485串口监听

package com.serial.demo.config;import com.serial.demo.socket.SerialWebSocket;import com.serial.demo.util.Crc16Modbus;import com.serial.demo.util.SerialUtil;import gnu.io.SerialPortEvent;import gnu.io.SerialPortEventListener;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class Serial485Listener implements SerialPortEventListener {    InputStream inputStream;    OutputStream outputStream;    String format;    public Serial485Listener(InputStream input, OutputStream output, String format){        inputStream = input;        outputStream = output;        this.format = format;    }    @Override    public void serialEvent(SerialPortEvent event) {        switch (event.getEventType()) {            case SerialPortEvent.BI:            case SerialPortEvent.OE:            case SerialPortEvent.FE:            case SerialPortEvent.PE:            case SerialPortEvent.CD:            case SerialPortEvent.CTS:            case SerialPortEvent.DSR:            case SerialPortEvent.RI:            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:                break;            case SerialPortEvent.DATA_AVAILABLE:                // 当有可用数据时读取数据                byte[] readBuffer = null;                int availableBytes = 0;                try {                    availableBytes = inputStream.available();                    while (availableBytes > 0) {                        readBuffer = SerialUtil.readFromPort(inputStream);                        String needData = printHexString(readBuffer);                        SerialWebSocket.broadcast(needData);                        availableBytes = inputStream.available();                    }                } catch (IOException e) {                }            default:                break;        }    }        public static String printHexString(byte[] b) {        return Crc16Modbus.byteTo16String(b);    }}
                                    Serial Communication                                                
串口配置
端口
波特率
数据位
停止位
校验位
操作
RS485
RS485
接收区设置
数据格式
停止显示
WebSocket
发送区设置
自动发送
数据格式
类型
发送
类型转换
STR
HEX

1.串口通信

ASCII 收数

在这里插入图片描述

ASCII发数

在这里插入图片描述

切换为自动发送后即自动发送当前数据

在这里插入图片描述

Hex 收数

在这里插入图片描述

Hex 发数

在这里插入图片描述

2.CRC16通信

Hex 收数

在这里插入图片描述

Hex 发数

在这里插入图片描述

来源地址:https://blog.csdn.net/weixin_42176639/article/details/131544863

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯