From 829c9ce9caf0382ccf33be79cc4f37b832e1c763 Mon Sep 17 00:00:00 2001 From: CaiXiang <939387484@qq.com> Date: Thu, 22 Dec 2022 12:25:27 +0800 Subject: [PATCH] =?UTF-8?q?S7=20=E6=9B=B4=E6=96=B0=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=A4=A7=E7=89=88=E6=9C=AC=EF=BC=8C=E5=BE=85=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 14 +- .../dc/s7/my/s7connector/api/S7Connector.java | 8 +- .../my/s7connector/api/utils/ByteUtils.java | 42 ++- .../s7/my/s7connector/enmuc/S7ClientNew.java | 355 ++++++++++++++++++ .../exception/S7CheckResultException.java | 64 ++++ .../s7connector/exception/S7IOException.java | 66 ++++ .../exception/S7ParseDataException.java | 64 ++++ .../my/s7connector/impl/S7BaseConnection.java | 17 +- .../s7connector/impl/nodave/PLCinterface.java | 15 +- .../s7connector/impl/nodave/S7Connection.java | 6 +- .../impl/nodave/TCPConnection.java | 18 +- .../qgs/dc/s7/my/s7connector/type/PlcVar.java | 2 +- .../com/qgs/dc/s7/retry/S7RetryTemplate.java | 60 +++ .../qgs/dc/s7/retrydemo/RetryDemoTask.java | 39 ++ .../com/qgs/dc/s7/retrydemo/RetryMain.java | 58 +++ .../retrydemo/SpringS7RetryTemplateTest.java | 66 ++++ .../com/qgs/dc/s7/retrydemo/TestMain.java | 20 + 17 files changed, 865 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/qgs/dc/s7/my/s7connector/enmuc/S7ClientNew.java create mode 100644 src/main/java/com/qgs/dc/s7/my/s7connector/exception/S7CheckResultException.java create mode 100644 src/main/java/com/qgs/dc/s7/my/s7connector/exception/S7IOException.java create mode 100644 src/main/java/com/qgs/dc/s7/my/s7connector/exception/S7ParseDataException.java create mode 100644 src/main/java/com/qgs/dc/s7/retry/S7RetryTemplate.java create mode 100644 src/main/java/com/qgs/dc/s7/retrydemo/RetryDemoTask.java create mode 100644 src/main/java/com/qgs/dc/s7/retrydemo/RetryMain.java create mode 100644 src/main/java/com/qgs/dc/s7/retrydemo/SpringS7RetryTemplateTest.java create mode 100644 src/main/java/com/qgs/dc/s7/retrydemo/TestMain.java diff --git a/pom.xml b/pom.xml index bbf4779..d8f026f 100644 --- a/pom.xml +++ b/pom.xml @@ -80,11 +80,19 @@ + + + org.springframework.retry + spring-retry + 1.2.2.RELEASE + + + com.alibaba fastjson - 1.2.78 + 2.0.21 @@ -201,6 +209,10 @@ influxdb-client-java 6.7.0 + + junit + junit + diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/api/S7Connector.java b/src/main/java/com/qgs/dc/s7/my/s7connector/api/S7Connector.java index f209099..e7f9413 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/api/S7Connector.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/api/S7Connector.java @@ -30,7 +30,7 @@ public interface S7Connector extends Closeable { * @param offset * @return */ - public byte[] read(DaveArea area, int areaNumber, int bytes, int offset) throws Exception; + public byte[] read(DaveArea area, int areaNumber, int bytes, int offset); /** * Reads an area 读需要bit 位置的 变量(其实就是 read bool变量的时候调用这个方法。) @@ -41,7 +41,7 @@ public interface S7Connector extends Closeable { * @param offset * @return */ - public byte[] read(DaveArea area, int areaNumber, int bytes, int offset, int bitOffset, TransportSize transportSize) throws Exception; + public byte[] read(DaveArea area, int areaNumber, int bytes, int offset, int bitOffset, TransportSize transportSize); /** * Writes an area * desc : 只要未抛出异常,都是 操作成功的 @@ -50,9 +50,9 @@ public interface S7Connector extends Closeable { * @param offset * @param buffer */ - public void write(DaveArea area, int areaNumber, int offset, byte[] buffer) throws Exception; + public void write(DaveArea area, int areaNumber, int offset, byte[] buffer); //如果 bitOffset 没有 那么就填0 - public void write(DaveArea area, int areaNumber, int byteOffset, int bitOffset, byte[] buffer, PlcVar var) throws Exception; + public void write(DaveArea area, int areaNumber, int byteOffset, int bitOffset, byte[] buffer, PlcVar var); } diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/api/utils/ByteUtils.java b/src/main/java/com/qgs/dc/s7/my/s7connector/api/utils/ByteUtils.java index 6444008..97c6973 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/api/utils/ByteUtils.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/api/utils/ByteUtils.java @@ -6,6 +6,7 @@ package com.qgs.dc.s7.my.s7connector.api.utils; * @DATE: 2021/12/16 9:08 */ +import com.qgs.dc.s7.my.s7connector.exception.S7ParseDataException; import com.qgs.dc.s7.my.s7connector.utils.CommonFunctions; import java.io.UnsupportedEncodingException; @@ -47,9 +48,14 @@ public class ByteUtils { } } - public static String addDate(String timeParam, Long day) throws ParseException { + public static String addDate(String timeParam, Long day) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 日期格式 - Date date = dateFormat.parse(timeParam); // 指定日期 + Date date = null; + try { + date = dateFormat.parse(timeParam); // 指定日期 + }catch (Exception e){ + throw new S7ParseDataException("ByteUtils.addDate error:", e); + } long time = date.getTime(); // 得到指定日期的毫秒数 day = day * 24 * 60 * 60 * 1000; // 要加上的天数转换成毫秒数 @@ -175,13 +181,13 @@ public class ByteUtils { // String ascii = new String(b, Charset.forName("UTF-8")); // return ascii; // } - public static Character toChar(byte[] b) throws UnsupportedEncodingException { + public static Character toChar(byte[] b) { if(b.length==1){ return toChar(b[0]); } return byteToChar(b); } - public static Character toChar(byte b) throws UnsupportedEncodingException { + public static Character toChar(byte b){ return byteToChar(b); } // public static String toChar(byte b) throws UnsupportedEncodingException { @@ -194,7 +200,7 @@ public class ByteUtils { /** * return null 代表返回传入参数不正确str 取的length太小 * */ - public static String toStr(byte[] b) throws UnsupportedEncodingException { + public static String toStr(byte[] b) { Integer length = Byte.toUnsignedInt(b[1]); if(length>(b.length-2)){ return null; @@ -207,7 +213,7 @@ public class ByteUtils { // String s = new String(content); return ascii; } - public static String[] toStrArray(byte[] b,Integer length,Integer strSize) throws UnsupportedEncodingException { + public static String[] toStrArray(byte[] b,Integer length,Integer strSize) { String[] res = new String[length]; strSize+=2; for(int i=0;i toBoolArray(byte[] b) throws UnsupportedEncodingException { + public static List toBoolArray(byte[] b) { List res = new ArrayList<>(); for(int i=0;i queue = new LinkedList(); @@ -347,7 +353,7 @@ public class ByteUtils { // return res; // } - public static List toByteArray(byte[] b) throws UnsupportedEncodingException { + public static List toByteArray(byte[] b) { List res = new ArrayList<>(); for(int i=0;i toCharArray(byte[] b) throws UnsupportedEncodingException { + public static List toCharArray(byte[] b) { List res = new ArrayList<>(); for(int i=0;i 有符号的整形 * */ - public static List toWordArray(byte[] b) throws UnsupportedEncodingException { + public static List toWordArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+2)<=b.length){ @@ -383,7 +389,7 @@ public class ByteUtils { /** * 默认:dword => 有符号的整形 * */ - public static List toDWordArray(byte[] b) throws UnsupportedEncodingException { + public static List toDWordArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+4)<=b.length){ @@ -444,7 +450,7 @@ public class ByteUtils { /** * USInt 无符号整形 1个字节 =》 Integer * */ - public static List toUSIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toUSIntArray(byte[] b) { List res = new ArrayList<>(); for(int i=0;i toUIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toUIntArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+2)<=b.length){ @@ -468,7 +474,7 @@ public class ByteUtils { /** * UDInt 无符号整形 4个字节 =》 Long * */ - public static List toUDIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toUDIntArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+4)<=b.length){ @@ -483,7 +489,7 @@ public class ByteUtils { /** * SInt 无符号整形 1个字节 =》 Integer * */ - public static List toSIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toSIntArray(byte[] b) { List res = new ArrayList<>(); for(int i=0;i toIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toIntArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+2)<=b.length){ @@ -507,7 +513,7 @@ public class ByteUtils { /** * DInt 无符号整形 4个字节 =》 Integer * */ - public static List toDIntArray(byte[] b) throws UnsupportedEncodingException { + public static List toDIntArray(byte[] b) { List res = new ArrayList<>(); int i=0; while ((i+4)<=b.length){ diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/enmuc/S7ClientNew.java b/src/main/java/com/qgs/dc/s7/my/s7connector/enmuc/S7ClientNew.java new file mode 100644 index 0000000..31fff9d --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/enmuc/S7ClientNew.java @@ -0,0 +1,355 @@ +package com.qgs.dc.s7.my.s7connector.enmuc; + + +import com.qgs.dc.s7.my.s7connector.api.DaveArea; +import com.qgs.dc.s7.my.s7connector.api.S7Connector; +import com.qgs.dc.s7.my.s7connector.api.factory.S7ConnectorFactory; +import com.qgs.dc.s7.my.s7connector.api.utils.ByteUtils; +import com.qgs.dc.s7.my.s7connector.exception.S7Exception; +import com.qgs.dc.s7.my.s7connector.type.PlcVar; +import com.qgs.dc.s7.my.s7connector.utils.CommonFunctions; +import com.qgs.dc.s7.retry.S7RetryTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + + +/** + * @Desc: "" + * @Author: caixiang + * @DATE: 2022/1/15 13:01 + */ +public enum S7ClientNew { + //TODO 步骤1 这里是配置多PLC 的,,,有多个plc 就在这里配置一个枚举类 + //1500 西门子200smart、1200、1500默认的 机架号=0 槽位号=1; 300/400 默认的 机架-0 插槽-2 + S7_1200("192.168.0.52",0,1,1,PlcVarActual.HeartBeatFor1200), + + S7_1500("192.168.0.51",0,1,1,PlcVarActual.HeartBeat), + //1500 机架-0 插槽-1 + //后续 在这里扩展 多PLC应用。 + ; + private String host; + //默认 0 机架号 + private Integer rack; + //默认 0 + private Integer slot; + + //心跳变量,如果plc没有让电控的人加一个,这个是必填的 + private PlcVarActual heartBeat; + + private List connections; + //coreSize 是线程池的大小 + private Integer coreSize; + //pickOne 就是一个初始化 的轮询取余值 + private int pickOne; + private static final Logger logger = LoggerFactory.getLogger(S7ClientNew.class); + + + + + //coreSize 是线程池的数量 + S7ClientNew(String host, Integer rack, Integer slot, Integer coreSize, PlcVarActual heartBeat){ + this.host = host; + this.rack = rack; + this.slot = slot; + this.pickOne = 0; + + this.coreSize = coreSize; + + connections = new ArrayList<>(); + connectionPool(); + + } + + public String getHost(){ + return this.host; + } + + /** + * PlcVar(byte[]) 转 java对象 对照表 + * 单体变量 + * Bool ===> Boolean + * LREAL ===> Double + * REAL ===> Float + * DATE ===> String(yyyy-MM-dd 这种形式的类型) + * DTL ===> String("年-月-日-工作日-时-分-秒" 这种格式) + * TIME ===> Integer(单位 ms) + * USINT ===> Integer + * SINT ===> Integer + * UINT ===> Integer + * INT ===> Integer + * DINT ===> Integer + * UINT ===> Long + * Byte ===> Integer(有符号)(默认) + * Integer(无符号)(后续扩展) + * Char ===> Character + * WChar ===> Character + * String ===> String (特殊) + + + * 数组变量 + * BoolArray ===> List + * ByteArray ===> List + * WordArray ===> List + * DWordArray ===> List + * CharArray ===> List + * SIntArray ===> List + * IntArray ===> List + * DIntArray ===> List + * UIntArray ===> List + * USIntArray ===> List + * UDIntArray ===> List + * StringArray ===> String[] (特殊) + * + * 如果返回null,就代表出现了异常,并且尝试了 retryMax 次数,并且尝试重置连接 + * */ + public Object read(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset, Integer length, Integer strSizes, PlcVar type) { + S7Connector connector = getConnector(); + return S7RetryTemplate.getInstance().execute( + context -> { + //String 类型比较特殊。 String[] 也是同理,Sring数组里面的子项 也是有两个字节的 readBytes。 + if(type.equals(PlcVar.STRING)){ + Integer readBytes = 2; + byte[] read = connector.read( + area, + areaNumber, + readBytes, + byteOffset, + bitOffset, + type.getTransportSize() + ); + + Integer allLength = Integer.valueOf(read[1])+2; + byte[] readF = connector.read( + area, + areaNumber, + allLength, + byteOffset, + bitOffset, + type.getTransportSize() + ); + return type.toObject(readF); + }else if(type.equals(PlcVar.BOOL_Array)){ + + byte[] read = connector.read( + area, + areaNumber, + CommonFunctions.exactDivision(length,8), + byteOffset, + bitOffset, + type.getTransportSize() + ); + List booleans = ByteUtils.toBoolArray(read); + List res = new ArrayList(); + for(int i=0;i { + logger.info("S7-Retry : 已达到最大重试次数: "+S7RetryTemplate.getMaxRetryTimes()+", 现在尝试重新连接"); + if(replaceConnector(connector)==1){ + logger.info("S7-Retry-Read 现在恢复成功,创建新connection成功"); + return null; + }else { + logger.info("S7-Retry-Read 现在恢复失败,创建新connection失败"); + return null; + } + } + ); + } + + + + /** + * + * eg : + * Object newValue = Boolean.FALSE + * s7Service.write(PlcVarActual.HeartBeat, newValue, S7Client.S7_1200); + * + * PlcVar(byte[]) 转 java对象 对照表 + * 单体变量 + * Bool ===> Object newValue = Boolean.FALSE + * LREAL ===> Object newValue = Boolean.FALSE + * REAL ===> Object newValue = Boolean.FALSE + * DATE ===> 暂时没需求(有问题找我) + * DTL ===> 暂时没需求(有问题找我) + * TIME ===> 暂时没需求(有问题找我) + * USINT ===> Object newValue = new Integer(1) + * SINT ===> Object newValue = new Integer(1) + * UINT ===> Object newValue = new Integer(1) + * INT ===> Object newValue = new Integer(1) + * DINT ===> Object newValue = new Integer(1) + * UINT ===> Object newValue = new Integer(1) + * Byte ===> Object newValue = 0x11 + * + * Char ===> Object newValue = 'a' + * WChar ===> Object newValue = '菜' + * String ===> Object newValue = '你好啊' (特殊) + + + * 数组变量 + * 注意:在write的时候,你write的数量 一定要和 plc中存在的数量一一对应 + * BoolArray ===> boolean[] booleanArray = new boolean[2]; .... 赋予值 + * ByteArray ===> byte[] write_byteArrays = new byte[2]; + * WordArray ===> short[] shortArrays_content = new short[2]; + * DWordArray ===> int[] intArrays_content = new int[2]; + * CharArray ===> char[] charArrays_content = new char[2]; + * SIntArray ===> int[] sintArrays_content = new int[2]; + * IntArray ===> int[] iintArrays_content = new int[2]; + * DIntArray ===> int[] dintArrays_content = new int[2]; + * UIntArray ===> int[] uintArrays_content = new int[3]; + * USIntArray ===> int[] usintArrays_content = new int[3]; + * UDIntArray ===> int[] udintArrays_content = new int[3]; + * StringArray ===> String[] stringArrays_content = new String[3]; + * //如果有其他数据类型 这里没有找我扩展 + * + * + * */ + public void write(DaveArea area, Integer areaNumber, Integer byteOffset, Integer bitOffset,Integer strSize, PlcVar type, Object newValue) throws Exception { + S7Connector connector = getConnector(); + + S7RetryTemplate.getInstance().execute( + context -> { + //String 类型比较特殊。 String[] 也是同理,Sring数组里面的子项 也是有两个字节的 readBytes。 + if(type.equals(PlcVar.STRING)){ + connector.write( + area, + areaNumber, + byteOffset, + bitOffset, + ByteUtils.strToBytes(newValue.toString(), strSize), + type + ); + }else if(type.equals(PlcVar.BOOL_Array)){ + connector.write( + area, + areaNumber, + byteOffset, + bitOffset, + ByteUtils.toByteArray((boolean[])newValue), + type + ); + }else if(type.equals(PlcVar.STRING_Array)){ + //todo here 检查 read write service + connector.write( + area, + areaNumber, + byteOffset, + bitOffset, + ByteUtils.strArrayToBytes((String[])newValue, strSize), + type + ); + }else { + byte[] bytes = type.toBytes(newValue); + connector.write( + area, + areaNumber, + byteOffset, + bitOffset, + bytes, + type + ); + } + return null; + }, + context -> { + logger.info("S7-Retry-Write : 已达到最大重试次数: "+S7RetryTemplate.getMaxRetryTimes()+", 现在尝试重新连接"); + if( replaceConnector(connector) == 1 ){ + logger.info("S7-Retry-Write 现在恢复成功,创建新connection成功"); + return null; + }else { + logger.info("S7-Retry-Write 现在恢复失败,创建新connection失败"); + return null; + } + } + ); + + + + + } + + /** + * desc: 传入的connection 是需要被替换的 + * return: + * 1 代表替换成功 + * -1 代表替换失败(创建connection失败,原因:出现异常.原来的connection也被舍弃掉了) + * */ + private Integer replaceConnector(S7Connector oldOne){ + connections.remove(oldOne); + S7Connector connect = connect(host, rack, slot); + if(connect == null){ + return -1; + }else { + connections.add(connect); + return 1; + } + + } + + public S7Connector getConnector() { + int size = connections.size(); + S7Connector s7Connector = connections.get((pickOne + size) % size); + pickOne+=1; + pickOne = (pickOne)%size; + return s7Connector; + } + + private S7Connector connect(String host,Integer rack,Integer slot ){ + try { + S7Connector connector = S7ConnectorFactory + .buildTCPConnector() + .withHost(host) + .withRack(rack) //optional rack 是机架号 + .withSlot(slot) //optional slot 是插槽号 + .build(); + return connector; + }catch (S7Exception e){ +// logger.info("创建S7Connector 连接失败,原因:"+e.getMessage()); + return null; + } + + } + + private void resetConnetctions(){ + this.connections = new ArrayList(); + connectionPool(); + } + + private void connectionPool(){ + for(int i=0;i MAX_SIZE) { final byte[] ret = new byte[bytes]; //注意这里 嵌套了 递归,让无限递归去解决 MAX_SIZE的问题。 @@ -108,7 +110,7 @@ public abstract class S7BaseConnection implements S7Connector { } } @Override - public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset, int bitOffset, TransportSize transportSize) throws Exception { + public synchronized byte[] read(final DaveArea area, final int areaNumber, final int bytes, final int offset, int bitOffset, TransportSize transportSize) { if(bitOffset==0){ return read(area,areaNumber,bytes,offset); } @@ -128,6 +130,7 @@ public abstract class S7BaseConnection implements S7Connector { final byte[] buffer = new byte[bytes]; final int ret = this.dc.readBytes(area, areaNumber, offset,bitOffset, bytes, buffer,transportSize); + //thr resultException checkResult(ret); return buffer; } @@ -135,7 +138,7 @@ public abstract class S7BaseConnection implements S7Connector { /** {@inheritDoc} */ @Override - public synchronized void write(final DaveArea area, final int areaNumber, final int offset, final byte[] buffer) throws Exception { + public synchronized void write(final DaveArea area, final int areaNumber, final int offset, final byte[] buffer) { if (buffer.length > MAX_SIZE) { // Split buffer final byte[] subBuffer = new byte[MAX_SIZE]; @@ -155,7 +158,7 @@ public abstract class S7BaseConnection implements S7Connector { } @Override - public synchronized void write(final DaveArea area, final int areaNumber, final int byteOffset, final int bitOffset, final byte[] buffer, PlcVar var) throws Exception { + public synchronized void write(final DaveArea area, final int areaNumber, final int byteOffset, final int bitOffset, final byte[] buffer, PlcVar var) { if(bitOffset==0){ write(area,areaNumber,byteOffset,buffer); return; diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/PLCinterface.java b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/PLCinterface.java index 8d25ea0..ece684b 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/PLCinterface.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/PLCinterface.java @@ -15,6 +15,7 @@ limitations under the License. */ package com.qgs.dc.s7.my.s7connector.impl.nodave; +import com.qgs.dc.s7.my.s7connector.exception.S7IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +46,7 @@ public final class PLCinterface { this.protocol = protocol; } - public int read(final byte[] b, int start, int len) { + public int read(final byte[] b, int start, int len){ int res; try { int retry = 0; @@ -67,18 +68,20 @@ public final class PLCinterface { } return res; } catch (final IOException e) { - logger.info("Socket read字节流失败: "+e); - return 0; + String errMsg = "Socket read字节流失败: "+e.getMessage(); + logger.info(errMsg); + throw new S7IOException(errMsg); } } - public int write(final byte[] b, final int start, final int len) { + public int write(final byte[] b, final int start, final int len){ try { this.out.write(b, start, len); return 1; } catch (final IOException e) { - logger.info("Socket write字节流失败: "+e); - return 0; + String errMsg = "Socket write字节流失败: "+e.getMessage(); + logger.info(errMsg); + throw new S7IOException(errMsg); } } diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/S7Connection.java b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/S7Connection.java index 77502c8..9c4ef64 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/S7Connection.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/S7Connection.java @@ -76,7 +76,7 @@ public abstract class S7Connection { * it's NULL you can get your data from the resultPointer in daveConnection * long as you do not send further requests. */ - public ResultSet execReadRequest(final PDU p) { + public ResultSet execReadRequest(final PDU p) throws Exception { PDU p2; int errorState; errorState = this.exchange(p); @@ -269,7 +269,7 @@ public abstract class S7Connection { * build the PDU for a PDU length negotiation * 构建第二次握手 */ - public int negPDUlengthRequest() { + public int negPDUlengthRequest() throws Exception { int res; final PDU p = new PDU(this.msgOut, this.PDUstartOut); //S7-Param 构造 @@ -320,7 +320,7 @@ public abstract class S7Connection { final PDU p1 = new PDU(this.msgOut, this.PDUstartOut); p1.initReadRequest(); p1.addVarToReadRequest(area, DBnum, start, len); - //发送read var request + //通过tcp链接 发送read var request,thr S7IOException res = this.exchange(p1); if (res != Nodave.RESULT_OK) { this.semaphore.release(); diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/TCPConnection.java b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/TCPConnection.java index e6b1b7d..e1c58ac 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/TCPConnection.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/impl/nodave/TCPConnection.java @@ -17,6 +17,7 @@ package com.qgs.dc.s7.my.s7connector.impl.nodave; import com.qgs.dc.s7.my.s7connector.api.utils.ByteUtils; import com.qgs.dc.s7.my.s7connector.enmuc.S7Client; +import com.qgs.dc.s7.my.s7connector.exception.S7IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,7 +60,7 @@ public final class TCPConnection extends S7Connection { * * @return the int */ - public int connectPLC() { + public int connectPLC() throws Exception { //COTP包 字节流(是上位机 和plc进行的第一次握手) final byte[] b4 = { //b4.length = 18 (byte) 0x11, //(16进制)当前字节以后的字节数 @@ -113,7 +114,7 @@ public final class TCPConnection extends S7Connection { * 0 代表成功 * */ @Override - public int exchange(final PDU p1) { + public int exchange(final PDU p1){ this.msgOut[4] = (byte) 0x02; //当前字节以后的字节数(也就是下面两个字节) this.msgOut[5] = (byte) 0xf0; //PDU Type this.msgOut[6] = (byte) 0x80; //TPDU number (如果这个字节首位为1 代表就是最后一个传输单元了 如1000 0000 ;; 如果首字母不为1 就代表这个Request是比较长的要分多个unit传输,后7位就代表传输单元序号) @@ -123,9 +124,9 @@ public final class TCPConnection extends S7Connection { if(writeRes!=0 && readRes!=0){ return 0; }else { - - logger.info("exchange 出现了问题:① writeRes:"+(writeRes==0?"writeISOPacket成功":"writeISOPacket失败")+"② readRes:"+(readRes==0?"readISOPacket成功":"readISOPacket失败")); - return 1 ; + String errMsg = "exchange 出现了问题:① writeRes:"+(writeRes==0?"writeISOPacket成功":"writeISOPacket失败")+"② readRes:"+(readRes==0?"readISOPacket成功":"readISOPacket失败"); + logger.info(errMsg); + throw new S7IOException(errMsg); } } @@ -133,15 +134,14 @@ public final class TCPConnection extends S7Connection { * Read iso packet. * * @return the int - * 0 ==> 不成功 + * 0 ==> 不成功(异常) * -1 ==> 读到一个空的字节流 (这种情况很少) * 其他 ==> 成功 */ - protected int readISOPacket() { + protected int readISOPacket(){ //read return 为0 就是异常 int res = this.iface.read(this.msgIn, 0, 4); if (res == 4) { -// final int len = (0x100 * this.msgIn[2]) + this.msgIn[3]; //读取字节数 这串是bug代码 final int len = (0x100 * ByteUtils.toUInt(this.msgIn[2])) + ByteUtils.toUInt(this.msgIn[3]); res += this.iface.read(this.msgIn, 4, len); } else { @@ -159,7 +159,7 @@ public final class TCPConnection extends S7Connection { * 1 ==> 成功 * 0 ==> 不成功(出现异常了) */ - protected int sendISOPacket(int size) { + protected int sendISOPacket(int size){ size += 4; //下面包装的 是TPKT 字节包(第一次握手) this.msgOut[0] = (byte) 0x03; //Version ,默认版本3 diff --git a/src/main/java/com/qgs/dc/s7/my/s7connector/type/PlcVar.java b/src/main/java/com/qgs/dc/s7/my/s7connector/type/PlcVar.java index de5e305..57ca970 100644 --- a/src/main/java/com/qgs/dc/s7/my/s7connector/type/PlcVar.java +++ b/src/main/java/com/qgs/dc/s7/my/s7connector/type/PlcVar.java @@ -330,7 +330,7 @@ public enum PlcVar { * * * */ - public Object toObject(byte[] value) throws ParseException, UnsupportedEncodingException { + public Object toObject(byte[] value) { if(!isArray){ Object res = null; switch (dataType) { diff --git a/src/main/java/com/qgs/dc/s7/retry/S7RetryTemplate.java b/src/main/java/com/qgs/dc/s7/retry/S7RetryTemplate.java new file mode 100644 index 0000000..08d3282 --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/retry/S7RetryTemplate.java @@ -0,0 +1,60 @@ +package com.qgs.dc.s7.retry; + +import com.qgs.dc.s7.my.s7connector.exception.S7CheckResultException; +import com.qgs.dc.s7.my.s7connector.exception.S7IOException; +import org.springframework.remoting.RemoteAccessException; +import org.springframework.retry.backoff.FixedBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author caixiang + * @description 重试模板和重试策略 + */ +public class S7RetryTemplate extends RetryTemplate { + + private volatile static S7RetryTemplate instance = null; + /** + * 重试间隔时间ms,默认1000ms + * */ + private static long fixedPeriodTime = 500L; + /** + * 最大重试次数,默认为3 + */ + private static int maxRetryTimes = 3; + /** + * 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 + */ + private static Map, Boolean> exceptionMap = new HashMap<>(); + private S7RetryTemplate() { + // 代表S7IOException 这个异常是需要重试的(true), 如果你想设置这个异常不去重试,那么可以把它设置为false。 + exceptionMap.put(S7IOException.class,true); + exceptionMap.put(S7CheckResultException.class,true); + + } + public static int getMaxRetryTimes() { + return maxRetryTimes; + } + + public static S7RetryTemplate getInstance() { + if (instance == null) { + synchronized (S7RetryTemplate.class) { + if (instance == null) { + FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); + // 定义重试间隔-间隔 fixedPeriodTime ms再重试,总共重试3次 + backOffPolicy.setBackOffPeriod(fixedPeriodTime); + instance = new S7RetryTemplate(); + // 定义重试次数- maxRetryTimes + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); + instance.setRetryPolicy(retryPolicy); + + instance.setBackOffPolicy(backOffPolicy); + } + } + } + return instance; + } +} diff --git a/src/main/java/com/qgs/dc/s7/retrydemo/RetryDemoTask.java b/src/main/java/com/qgs/dc/s7/retrydemo/RetryDemoTask.java new file mode 100644 index 0000000..a6896de --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/retrydemo/RetryDemoTask.java @@ -0,0 +1,39 @@ +package com.qgs.dc.s7.retrydemo; + + +import org.apache.commons.lang3.RandomUtils; +import org.springframework.remoting.RemoteAccessException; + +/** + * @Author: cx + * @Description: + */ +public class RetryDemoTask { + /** + * 重试方法 + * @return + */ + public static boolean retryTask(String param) { + System.out.println("retry-task : 收到请求参数:"+param); + + int i = RandomUtils.nextInt(0,11); + System.out.println("retry-task : 随机生成的数:"+i); + if (i == 0) { + System.out.println("retry-task : 为0,抛出参数异常."); + //因为Illeague这个异常我们没有在exceptionMap里面配置,所以 抛出这个异常后 + //spring-retry不会进行重试,而是会直接进入recovery函数 + throw new IllegalArgumentException("retry-task : 参数异常"); + }else if (i == 1){ + System.out.println("retry-task : 为1,返回true."); + return true; + }else if (i == 2){ + System.out.println("retry-task : 为2,返回false."); + return false; + }else{ + //因为RemoteAccessExcep这个异常我们在exceptionMap里面配置了,所以 抛出这个异常后 + //spring-retry会进行重试 + System.out.println("retry-task : 大于2,抛出自定义异常."); + throw new RemoteAccessException("retry-task : 大于2,抛出远程访问异常"); + } + } +} diff --git a/src/main/java/com/qgs/dc/s7/retrydemo/RetryMain.java b/src/main/java/com/qgs/dc/s7/retrydemo/RetryMain.java new file mode 100644 index 0000000..5f6cfbc --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/retrydemo/RetryMain.java @@ -0,0 +1,58 @@ +package com.qgs.dc.s7.retrydemo; + +import org.springframework.remoting.RemoteAccessException; +import org.springframework.retry.backoff.FixedBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; + +import java.util.HashMap; +import java.util.Map; + +public class RetryMain { + public static void main(String[] args) { + + /** + * 重试间隔时间ms,默认1000ms + * */ + long fixedPeriodTime = 1000L; + /** + * 最大重试次数,默认为3 + */ + int maxRetryTimes = 3; + /** + * 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 + */ + Map, Boolean> exceptionMap = new HashMap<>(); + + + exceptionMap.put(RemoteAccessException.class,true); + + // 构建重试模板实例 + RetryTemplate retryTemplate = new RetryTemplate(); + + // 设置重试回退操作策略,主要设置重试间隔时间 + FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); + backOffPolicy.setBackOffPeriod(fixedPeriodTime); + + // 设置重试策略,主要设置重试次数 + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); + + retryTemplate.setRetryPolicy(retryPolicy); + retryTemplate.setBackOffPolicy(backOffPolicy); + + Boolean execute = retryTemplate.execute( + //RetryCallback + retryContext -> { + boolean b = RetryDemoTask.retryTask("abc"); + System.err.println("retry-main : 调用的结果:"+b+",times:"+retryContext.getRetryCount()); + return b; + }, + retryContext -> { + //RecoveryCallback + System.err.println("retry-main : 已达到最大重试次数或抛出了不重试的异常~~~"); + return false; + } + ); + System.err.println("retry-main : 执行结果:"+execute); + } +} diff --git a/src/main/java/com/qgs/dc/s7/retrydemo/SpringS7RetryTemplateTest.java b/src/main/java/com/qgs/dc/s7/retrydemo/SpringS7RetryTemplateTest.java new file mode 100644 index 0000000..8c480ca --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/retrydemo/SpringS7RetryTemplateTest.java @@ -0,0 +1,66 @@ +package com.qgs.dc.s7.retrydemo; + +import org.junit.Test; +import org.springframework.remoting.RemoteAccessException; +import org.springframework.retry.backoff.FixedBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: zgd + * @Description: spring-retry 重试框架 + */ +public class SpringS7RetryTemplateTest { + + /** + * 重试间隔时间ms,默认1000ms + * */ + private long fixedPeriodTime = 1000L; + /** + * 最大重试次数,默认为3 + */ + private int maxRetryTimes = 3; + /** + * 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试 + */ + private Map, Boolean> exceptionMap = new HashMap<>(); + + + @Test + public void test() { + + //文档:https://blog.csdn.net/minghao0508/article/details/123972703 + exceptionMap.put(RemoteAccessException.class,true); + + // 构建重试模板实例 + RetryTemplate retryTemplate = new RetryTemplate(); + + // 设置重试回退操作策略,主要设置重试间隔时间 + FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); + backOffPolicy.setBackOffPeriod(fixedPeriodTime); + + // 设置重试策略,主要设置重试次数 + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap); + + retryTemplate.setRetryPolicy(retryPolicy); + retryTemplate.setBackOffPolicy(backOffPolicy); + + Boolean execute = retryTemplate.execute( + //RetryCallback + retryContext -> { + boolean b = RetryDemoTask.retryTask("abc"); + System.out.println("调用的结果:"+b); + return b; + }, + retryContext -> { + //RecoveryCallback + System.out.println("已达到最大重试次数或抛出了不重试的异常~~~"); + return false; + } + ); + System.out.println("执行结果:"+execute); + } + +} diff --git a/src/main/java/com/qgs/dc/s7/retrydemo/TestMain.java b/src/main/java/com/qgs/dc/s7/retrydemo/TestMain.java new file mode 100644 index 0000000..fd73a10 --- /dev/null +++ b/src/main/java/com/qgs/dc/s7/retrydemo/TestMain.java @@ -0,0 +1,20 @@ +package com.qgs.dc.s7.retrydemo; + +import java.util.ArrayList; +import java.util.List; + +public class TestMain { + public static void main(String[] args) { + Integer a = 1; + Integer a1 = 2; + Integer a2 = 3; + List list = new ArrayList(); + list.add(a); + list.add(a1); + list.add(a2); + Integer integer = list.get(0); + list.remove(integer); + System.out.println(); + + } +}