diff --git a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java index a80375a4..1c2a5f0c 100644 --- a/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java +++ b/source/consensus/consensus-bftsmart/src/main/java/com/jd/blockchain/consensus/bftsmart/client/BftsmartMessageService.java @@ -40,7 +40,7 @@ public class BftsmartMessageService implements MessageService { asyncFuture.complete(result); } catch (Exception e) { - throw new RuntimeException(); + throw new RuntimeException(e); } finally { asyncPeerProxyPool.returnObject(asynchServiceProxy); diff --git a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java index ed756109..9205a119 100644 --- a/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java +++ b/source/ledger/ledger-model/src/main/java/com/jd/blockchain/ledger/TransactionState.java @@ -29,6 +29,11 @@ public enum TransactionState { */ LEDGER_ERROR((byte) 2), + /** + * 数据序列更新错误; + */ + DATA_SEQUENCE_UPDATE_ERROR((byte) 3), + /** * 系统错误; */ diff --git a/source/peer/pom.xml b/source/peer/pom.xml index 01311d5c..51746e7d 100644 --- a/source/peer/pom.xml +++ b/source/peer/pom.xml @@ -20,6 +20,11 @@ consensus-framework ${project.version} + + com.jd.blockchain + state-transfer + ${project.version} + com.jd.blockchain ledger-rpc diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceReaderImpl.java b/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceReaderImpl.java new file mode 100644 index 00000000..71161411 --- /dev/null +++ b/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceReaderImpl.java @@ -0,0 +1,132 @@ +//package com.jd.blockchain.peer.statetransfer; +// +//import com.jd.blockchain.binaryproto.BinaryEncodingUtils; +//import com.jd.blockchain.crypto.hash.HashDigest; +//import com.jd.blockchain.ledger.LedgerBlock; +//import com.jd.blockchain.ledger.LedgerTransaction; +//import com.jd.blockchain.ledger.core.LedgerManage; +//import com.jd.blockchain.ledger.core.LedgerRepository; +//import com.jd.blockchain.ledger.core.TransactionSet; +//import com.jd.blockchain.statetransfer.DataSequenceElement; +//import com.jd.blockchain.statetransfer.DataSequenceInfo; +//import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +//import com.jd.blockchain.storage.service.DbConnection; +//import com.jd.blockchain.storage.service.DbConnectionFactory; +//import com.jd.blockchain.tools.initializer.LedgerBindingConfig; +//import com.jd.blockchain.utils.codec.Base58Utils; +//import com.jd.blockchain.utils.codec.HexUtils; +//import org.springframework.beans.factory.annotation.Autowired; +// +///** +// *数据序列差异的提供者需要使用的回调接口实现类 +// * @author zhangshuang +// * @create 2019/4/11 +// * @since 1.0.0 +// */ +//public class DataSequenceReaderImpl implements DataSequenceReader { +// +// private LedgerManage ledgerManager; +// +// private DbConnectionFactory connFactory; +// +// private LedgerBindingConfig config; +// +// public DataSequenceReaderImpl(LedgerBindingConfig config, LedgerManage ledgerManager, DbConnectionFactory connFactory) { +// this.config = config; +// this.ledgerManager = ledgerManager; +// this.connFactory = connFactory; +// } +// +// +// /** +// * @param id 账本哈希的Base58编码 +// * @return DataSequenceInfo 数据序列信息 +// */ +// @Override +// public DataSequenceInfo getDSInfo(String id) { +// +// byte[] hashBytes = Base58Utils.decode(id); +// +// HashDigest ledgerHash = new HashDigest(hashBytes); +// +// LedgerBindingConfig.BindingConfig bindingConfig = config.getLedger(ledgerHash); +// DbConnection dbConnNew = connFactory.connect(bindingConfig.getDbConnection().getUri(), +// bindingConfig.getDbConnection().getPassword()); +// LedgerRepository ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); +// +// return new DataSequenceInfo(id, ledgerRepository.getLatestBlockHeight()); +// } +// +// /** +// * +// * @param id 账本哈希的Base58编码 +// * @param from 数据序列复制的起始高度 +// * @param to 数据序列复制的结束高度 +// * @return DataSequenceElement【】数据序列差异数据元素的数组 +// */ +// @Override +// public DataSequenceElement[] getDSDiffContent(String id, long from, long to) { +// +// DataSequenceElement[] dataSequenceElements = new DataSequenceElement[(int)(to - from + 1)]; +// for (long i = from; i < to + 1; i++) { +// dataSequenceElements[(int)(i - from)] = getDSDiffContent(id, i); +// } +// +// return dataSequenceElements; +// } +// +// /** +// * 账本交易序列化 +// * @param transaction 账本交易 +// * @return byte[] 对账本交易进行序列化的结果 +// */ +// private byte[] serialize(LedgerTransaction transaction) { +// return BinaryEncodingUtils.encode(transaction, LedgerTransaction.class); +// } +// +// /** +// * 获得账本某一高度区块上的所有交易 +// * @param id 账本哈希的Base58编码 +// * @param height 账本的某个区块高度 +// * @return DataSequenceElement 数据序列差异数据元素 +// */ +// @Override +// public DataSequenceElement getDSDiffContent(String id, long height) { +// +// int lastHeightTxTotalNums = 0; +// +// byte[][] transacionDatas = null; +// +// byte[] hashBytes = Base58Utils.decode(id); +// +// HashDigest ledgerHash = new HashDigest(hashBytes); +// +// LedgerBindingConfig.BindingConfig bindingConfig = config.getLedger(ledgerHash); +// DbConnection dbConnNew = connFactory.connect(bindingConfig.getDbConnection().getUri(), +// bindingConfig.getDbConnection().getPassword()); +// LedgerRepository ledgerRepository = ledgerManager.register(ledgerHash, dbConnNew.getStorageService()); +// +// LedgerBlock ledgerBlock = ledgerRepository.getBlock(height); +// TransactionSet transactionSet = ledgerRepository.getTransactionSet(ledgerBlock); +// +// if (height > 0) { +// lastHeightTxTotalNums = (int) ledgerRepository.getTransactionSet(ledgerRepository.getBlock(height - 1)).getTotalCount(); +// } +// +// int currentHeightTxTotalNums = (int)ledgerRepository.getTransactionSet(ledgerRepository.getBlock(height)).getTotalCount(); +// +// // get all transactions from current height block +// int currentHeightTxNums = currentHeightTxTotalNums - lastHeightTxTotalNums; +// +// LedgerTransaction[] transactions = transactionSet.getTxs(lastHeightTxTotalNums , currentHeightTxNums); +// +// for (int i = 0; i < transactions.length; i++) { +// byte[] transactionData = serialize(transactions[i]); +// transacionDatas[i] = transactionData; +// } +// +// return new DataSequenceElement(id, height, transacionDatas); +// } +// +// +//} diff --git a/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceWriterImpl.java b/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceWriterImpl.java new file mode 100644 index 00000000..774e69b5 --- /dev/null +++ b/source/peer/src/main/java/com/jd/blockchain/peer/statetransfer/DataSequenceWriterImpl.java @@ -0,0 +1,170 @@ +//package com.jd.blockchain.peer.statetransfer; +// +//import com.jd.blockchain.consensus.service.MessageHandle; +//import com.jd.blockchain.ledger.TransactionState; +//import com.jd.blockchain.statetransfer.DataSequenceElement; +//import com.jd.blockchain.statetransfer.DataSequenceInfo; +//import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +//import com.jd.blockchain.statetransfer.comparator.DataSequenceComparator; +// +//import java.util.ArrayList; +//import java.util.Collections; +// +///** +// *数据序列差异的请求者需要使用的回调接口实现类 +// * @author zhangshuang +// * @create 2019/4/11 +// * @since 1.0.0 +// */ +//public class DataSequenceWriterImpl implements DataSequenceWriter { +// +// private long currHeight; +// private ArrayList deceidedElements = new ArrayList(); +// +// private MessageHandle batchMessageHandle; +// +// +// public DataSequenceWriterImpl(MessageHandle batchMessageHandle) { +// this.batchMessageHandle = batchMessageHandle; +// } +// +// /** +// * 检查数据序列差异元素中的高度是否合理; +// * @param currHeight 当前结点的账本高度 +// * @param dsUpdateElements 需要更新到本地结点的数据序列元素List +// * @return +// */ +// private int checkElementsHeight(long currHeight, ArrayList dsUpdateElements) { +// boolean lossMiddleElements = false; +// +// // lose first element +// if (currHeight + 1 < dsUpdateElements.get(0).getHeight()){ +// System.out.println("Diff response loss first element error!"); +// return DataSequenceErrorType.DATA_SEQUENCE_LOSS_FIRST_ELEMENT.CODE; +// } +// else { +// for (int i = 0; i < dsUpdateElements.size(); i++) { +// if (dsUpdateElements.get(i).getHeight() == currHeight + 1 + i) { +// deceidedElements.add(dsUpdateElements.get(i)); +// } +// // lose middle elements +// else { +// lossMiddleElements = true; +// break; +// } +// } +// +// if (lossMiddleElements) { +// System.out.println("Diff response loss middle elements error!"); +// return DataSequenceErrorType.DATA_SEQUENCE_LOSS_MIDDLE_ELEMENT.CODE; +// } +// +// System.out.println("Diff response elements height normal!"); +// return DataSequenceErrorType.DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL.CODE; +// } +// +// } +// +// /** +// * 对本地结点执行账本更新 +// * @param realmName 账本哈希的Base58编码 +// * @return void +// */ +// private void exeUpdate(String realmName) { +// +// for (int i = 0; i < deceidedElements.size(); i++) { +// byte[][] element = deceidedElements.get(i).getData(); +// +// String batchId = batchMessageHandle.beginBatch(realmName); +// try { +// int msgId = 0; +// for (byte[] txContent : element) { +// batchMessageHandle.processOrdered(msgId++, txContent, realmName, batchId); +// } +// // 结块 +// batchMessageHandle.completeBatch(realmName, batchId); +// batchMessageHandle.commitBatch(realmName, batchId); +// } catch (Exception e) { +// // todo 需要处理应答码 404 +// batchMessageHandle.rollbackBatch(realmName, batchId, TransactionState.DATA_SEQUENCE_UPDATE_ERROR.CODE); +// } +// } +// +// } +// +// /** +// * @param dsInfo 当前结点的数据序列信息 +// * @param diffContents 数据序列差异的数据元素数组 +// * @return int 更新结果码 +// */ +// @Override +// public int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement[] diffContents) { +// int result = 0; +// +// try { +// ArrayList dsUpdateElements = new ArrayList(); +// //remove unexpected elements +// for (int i = 0 ; i < diffContents.length; i++) { +// if (diffContents[i].getId().equals(dsInfo.getId())) { +// dsUpdateElements.add(diffContents[i]); +// } +// } +// +// // sort elements by height +// Collections.sort(dsUpdateElements, new DataSequenceComparator()); +// +// currHeight = dsInfo.getHeight(); +// +// // check element's height +// result = checkElementsHeight(currHeight, dsUpdateElements); +// +// // cann't exe update +// if (result == DataSequenceErrorType.DATA_SEQUENCE_LOSS_FIRST_ELEMENT.CODE) { +// return result; +// } +// // exe elements update +// else { +// exeUpdate(dsInfo.getId()); +// return result; +// } +// } catch (Exception e) { +// System.out.println(e.getMessage()); +// e.printStackTrace(); +// } +// +// return result; +// } +// +// @Override +// public int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement diffContents) { +// return 0; +// } +// +// +// /** +// * 数据序列更新错误码 +// * @param +// * @return +// */ +// public enum DataSequenceErrorType { +// DATA_SEQUENCE_LOSS_FIRST_ELEMENT((byte) 0x1), +// DATA_SEQUENCE_LOSS_MIDDLE_ELEMENT((byte) 0x2), +// DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL((byte) 0x3), +// ; +// public final int CODE; +// +// private DataSequenceErrorType(byte code) { +// this.CODE = code; +// } +// +// public static DataSequenceErrorType valueOf(byte code) { +// for (DataSequenceErrorType errorType : DataSequenceErrorType.values()) { +// if (errorType.CODE == code) { +// return errorType; +// } +// } +// throw new IllegalArgumentException("Unsupported code[" + code + "] of errorType!"); +// } +// } +// +//} diff --git a/source/pom.xml b/source/pom.xml index 2f1a1d74..a36580f8 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -32,10 +32,12 @@ storage gateway peer + state-transfer sdk tools test deployment + stp @@ -297,6 +299,12 @@ commons-collections4 4.1 + + + com.google.guava + guava + 19.0 + diff --git a/source/state-transfer/pom.xml b/source/state-transfer/pom.xml new file mode 100644 index 00000000..d17c8e03 --- /dev/null +++ b/source/state-transfer/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + com.jd.blockchain + jdchain-root + 0.9.0-SNAPSHOT + + state-transfer + + + + com.jd.blockchain + stp-communication + ${project.version} + + + com.jd.blockchain + utils-common + ${project.version} + + + + com.jd.blockchain + utils-serialize + ${project.version} + + + + + + + diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequence.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequence.java new file mode 100644 index 00000000..237a6d47 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequence.java @@ -0,0 +1,75 @@ +package com.jd.blockchain.statetransfer; + +import java.net.InetSocketAddress; +import java.util.LinkedList; + +/** + * 测试过程建立的一个数据序列 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DataSequence { + + private InetSocketAddress address; + private String id; + + // 每个数据序列维护了一系列的数据序列元素 + private LinkedList dataSequenceElements = new LinkedList<>(); + + + public DataSequence(InetSocketAddress address, String id) { + this.address = address; + this.id = id; + } + + public String getId() { + return id; + } + + public InetSocketAddress getAddress() { + return address; + } + + + public void addElements(DataSequenceElement[] elements) { + for (DataSequenceElement element : elements) { + addElement(element); + } + } + + public void addElement(DataSequenceElement element) { + try { + if (dataSequenceElements.size() == 0) { + if (element.getHeight() != 0) { + throw new IllegalArgumentException("Data sequence add element height error!"); + } + dataSequenceElements.addLast(element); + } + else { + if (dataSequenceElements.getLast().getHeight() != element.getHeight() - 1) { + throw new IllegalArgumentException("Data sequence add element height error!"); + } + dataSequenceElements.addLast(element); + } + + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + public LinkedList getDataSequenceElements() { + return dataSequenceElements; + } + + public DataSequenceInfo getDSInfo() { + if (dataSequenceElements.size() == 0) { + return new DataSequenceInfo(id, -1); + } + else { + return new DataSequenceInfo(id, dataSequenceElements.getLast().getHeight()); + } + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceElement.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceElement.java new file mode 100644 index 00000000..aaf6e7f5 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceElement.java @@ -0,0 +1,53 @@ +package com.jd.blockchain.statetransfer; + +import java.io.Serializable; + +/** + * 数据序列需要复制内容的元素或单位 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public class DataSequenceElement implements Serializable { + + private static final long serialVersionUID = -719578198150380571L; + + //数据序列的唯一标识符; + private String id; + + //数据序列的某个高度; + private long height; + + //对应某个高度的数据序列内容 + private byte[][] data; + + public DataSequenceElement(String id, long height, byte[][] data) { + this.id = id; + this.height = height; + this.data = data; + } + + public long getHeight() { + return height; + } + + public void setHeight(long height) { + this.height = height; + } + + public String getId() { + return id; + } + + public void setId(String id) { + id = id; + } + + public byte[][] getData() { + return data; + } + + public void setData(byte[][] data) { + this.data = data; + } +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceInfo.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceInfo.java new file mode 100644 index 00000000..6f0f2e10 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/DataSequenceInfo.java @@ -0,0 +1,37 @@ +package com.jd.blockchain.statetransfer; + +/** + * 共识结点上的某个数据序列的当前状态信息,每个共识结点可以对应任意个数据序列; + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public class DataSequenceInfo { + + //数据序列的唯一标识 + private String id; + + //数据序列的当前高度 + private long height; + + public DataSequenceInfo(String id, long height) { + this.id = id; + this.height = height; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public long getHeight() { + return height; + } + + public void setHeight(long height) { + this.height = height; + } +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReader.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReader.java new file mode 100644 index 00000000..e137b929 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReader.java @@ -0,0 +1,39 @@ +package com.jd.blockchain.statetransfer.callback; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; + +/** + * 数据序列差异提供者需要使用的回调接口 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public interface DataSequenceReader { + + /** + * 差异提供者根据数据序列标识符获取数据序列当前状态; + * @param id 数据序列标识符 + * @return 数据序列当前状态信息 + */ + DataSequenceInfo getDSInfo(String id); + + + /** + * 差异提供者根据数据序列标识符以及起始,结束高度提供数据序列该范围的差异内容; + * @param id 数据序列标识符 + * @param from 差异的起始高度 + * @param to 差异的结束高度 + * @return 差异元素组成的数组 + */ + DataSequenceElement[] getDSDiffContent(String id, long from, long to); + + + /** + * 差异提供者根据数据序列标识符以及高度提供数据序列的差异内容; + * @param id 数据序列标识符 + * @param height 要获得哪个高度的差异元素 + * @return 差异元素 + */ + DataSequenceElement getDSDiffContent(String id, long height); +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReaderImpl.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReaderImpl.java new file mode 100644 index 00000000..3d581b2d --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceReaderImpl.java @@ -0,0 +1,59 @@ +package com.jd.blockchain.statetransfer.callback; + +import com.jd.blockchain.statetransfer.DataSequence; +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; + +import java.util.LinkedList; + +/** + * 数据序列差异的提供者需要使用的回调接口实现类 + * @author zhangshuang + * @create 2019/4/22 + * @since 1.0.0 + */ + +public class DataSequenceReaderImpl implements DataSequenceReader { + + DataSequence currDataSequence; + + public DataSequenceReaderImpl(DataSequence currDataSequence) { + this.currDataSequence = currDataSequence; + } + + @Override + public DataSequenceInfo getDSInfo(String id) { + return currDataSequence.getDSInfo(); + } + + @Override + public DataSequenceElement[] getDSDiffContent(String id, long from, long to) { + DataSequenceElement[] elements = new DataSequenceElement[(int)(to - from + 1)]; + + int i = 0; + LinkedList dataSequenceElements = currDataSequence.getDataSequenceElements(); + for (DataSequenceElement element : dataSequenceElements) { + if (element.getHeight() < from || element.getHeight() > to) { + continue; + } + else { + elements[i++] = element; + } + } + + return elements; + + } + + @Override + public DataSequenceElement getDSDiffContent(String id, long height) { + for(DataSequenceElement dataSequenceElement : currDataSequence.getDataSequenceElements()) { + if (dataSequenceElement.getHeight() == height) { + return dataSequenceElement; + + } + } + return null; + } +} + diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriter.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriter.java new file mode 100644 index 00000000..b45eb3ac --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriter.java @@ -0,0 +1,30 @@ +package com.jd.blockchain.statetransfer.callback; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; + +/** + * 数据序列差异请求者获得差异内容后需要回调该接口 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public interface DataSequenceWriter { + + /** + * 差异请求者更新本地数据序列的状态,一次可以更新多个差异元素 + * @param dsInfo 数据序列当前状态信息 + * @param diffContents 需要更新的差异元素数组 + * @return 更新结果编码 + */ + int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement[] diffContents); + + /** + * 差异请求者更新本地数据序列的状态,一次只更新一个差异元素 + * @param dsInfo 数据序列当前状态信息 + * @param diffContent 需要更新的差异元素 + * @return 更新结果编码 + */ +// int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement diffContent); + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriterImpl.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriterImpl.java new file mode 100644 index 00000000..c510e843 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/callback/DataSequenceWriterImpl.java @@ -0,0 +1,142 @@ +package com.jd.blockchain.statetransfer.callback; + +import com.jd.blockchain.statetransfer.DataSequence; +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; + +import java.util.ArrayList; + +/** + * 数据序列差异的请求者需要使用的回调接口实现类 + * @author zhangshuang + * @create 2019/4/22 + * @since 1.0.0 + */ +public class DataSequenceWriterImpl implements DataSequenceWriter { + + private long currHeight; + private DataSequence currDataSequence; + private ArrayList deceidedElements = new ArrayList(); + + public DataSequenceWriterImpl(DataSequence currDataSequence) { + this.currDataSequence = currDataSequence; + } + + /** + * 检查数据序列差异元素中的高度是否合理; + * @param currHeight 当前结点的账本高度 + * @param dsUpdateElements 需要更新到本地结点的数据序列元素List + * @return + */ + private int checkElementsHeight(long currHeight, ArrayList dsUpdateElements) { + boolean lossMiddleElements = false; + + // lose first element + if (currHeight + 1 < dsUpdateElements.get(0).getHeight()){ + System.out.println("Diff response loss first element error!"); + return DataSequenceErrorType.DATA_SEQUENCE_LOSS_FIRST_ELEMENT.CODE; + } + else { + for (int i = 0; i < dsUpdateElements.size(); i++) { + if (dsUpdateElements.get(i).getHeight() == currHeight + 1 + i) { + deceidedElements.add(dsUpdateElements.get(i)); + } + // lose middle elements + else { + lossMiddleElements = true; + break; + } + } + + if (lossMiddleElements) { + System.out.println("Diff response loss middle elements error!"); + return DataSequenceErrorType.DATA_SEQUENCE_LOSS_MIDDLE_ELEMENT.CODE; + } + + System.out.println("Diff response elements height normal!"); + return DataSequenceErrorType.DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL.CODE; + } + + } + + @Override + public int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement[] diffContents) { + + int result = 0; + + try { + ArrayList dsUpdateElements = new ArrayList(); + + if (diffContents == null) { + throw new IllegalArgumentException("Update diffContents is null!"); + } + + //remove unexpected elements + for (int i = 0 ; i < diffContents.length; i++) { + if (diffContents[i].getId().equals(dsInfo.getId())) { + dsUpdateElements.add(diffContents[i]); + } + } + + currHeight = dsInfo.getHeight(); + + // check element's height + result = checkElementsHeight(currHeight, dsUpdateElements); + + // cann't exe update + if (result == DataSequenceErrorType.DATA_SEQUENCE_LOSS_FIRST_ELEMENT.CODE) { + return result; + } + // exe elements update + else { + System.out.println("Old data sequence state: "); + System.out.println(" Current height = " + currDataSequence.getDataSequenceElements().getLast().getHeight()); + currDataSequence.addElements(deceidedElements.toArray(new DataSequenceElement[deceidedElements.size()])); + + System.out.println("Update diffContents is completed!"); + System.out.println("New data sequence state: "); + System.out.println(" Current height = " + currDataSequence.getDataSequenceElements().getLast().getHeight()); + + return result; + } + + + + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + + return DataSequenceErrorType.DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL.CODE; + + } + +// @Override +// public int updateDSInfo(DataSequenceInfo dsInfo, DataSequenceElement diffContent) { +// currDataSequence.addElement(diffContent); +// return DataSequenceErrorType.DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL.CODE; +// } + + public enum DataSequenceErrorType { + DATA_SEQUENCE_LOSS_FIRST_ELEMENT((byte) 0x1), + DATA_SEQUENCE_LOSS_MIDDLE_ELEMENT((byte) 0x2), + DATA_SEQUENCE_ELEMENT_HEIGHT_NORMAL((byte) 0x3), + ; + public final int CODE; + + private DataSequenceErrorType(byte code) { + this.CODE = code; + } + + public static DataSequenceErrorType valueOf(byte code) { + for (DataSequenceErrorType errorType : DataSequenceErrorType.values()) { + if (errorType.CODE == code) { + return errorType; + } + } + throw new IllegalArgumentException("Unsupported code[" + code + "] of errorType!"); + } + } + +} + diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/comparator/DataSequenceComparator.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/comparator/DataSequenceComparator.java new file mode 100644 index 00000000..9e0afbe5 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/comparator/DataSequenceComparator.java @@ -0,0 +1,32 @@ +package com.jd.blockchain.statetransfer.comparator; + +import com.jd.blockchain.statetransfer.DataSequenceElement; + +import java.util.Comparator; + +/** + * 数据序列差异元素的高度比较器 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DataSequenceComparator implements Comparator { + + // sort by data sequence height + /** + * 对差异元素根据高度大小排序 + * @param o1 差异元素1 + * @param o2 差异元素2 + * @return >0 or <0 + */ + @Override + public int compare(DataSequenceElement o1, DataSequenceElement o2) { + long height1; + long height2; + + height1 = o1.getHeight(); + height2 = o2.getHeight(); + + return (int) (height1 - height2); + } +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/exception/DataSequenceException.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/exception/DataSequenceException.java new file mode 100644 index 00000000..5e6248c0 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/exception/DataSequenceException.java @@ -0,0 +1,21 @@ +package com.jd.blockchain.statetransfer.exception; + +/** + * 数据序列异常处理 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DataSequenceException extends RuntimeException { + + private static final long serialVersionUID = -4090881296855827889L; + + + public DataSequenceException(String message) { + super(message); + } + public DataSequenceException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSDefaultMessageExecutor.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSDefaultMessageExecutor.java new file mode 100644 index 00000000..6c67e8c8 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSDefaultMessageExecutor.java @@ -0,0 +1,80 @@ +package com.jd.blockchain.statetransfer.message; + +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +import com.jd.blockchain.statetransfer.process.DSTransferProcess; +import com.jd.blockchain.statetransfer.result.DSDiffRequestResult; +import com.jd.blockchain.stp.communication.MessageExecutor; +import com.jd.blockchain.stp.communication.RemoteSession; + +/** + * 数据序列差异提供者使用,解析收到的差异请求消息并产生响应 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public class DSDefaultMessageExecutor implements MessageExecutor { + + DataSequenceReader dsReader; + DataSequenceWriter dsWriter; + + public DSDefaultMessageExecutor(DataSequenceReader dsReader, DataSequenceWriter dsWriter) { + this.dsReader = dsReader; + this.dsWriter = dsWriter; + } + + /** + * 对状态机复制的差异请求进行响应 + * @param key 请求消息的Key + * @param data 需要解码的字节数组 + * @param session 指定响应需要使用的目标结点会话 + * @return 配置为自动响应时,返回值为响应的字节数组,配置为手动响应时,不需要关注返回值 + */ + + @Override + public byte[] receive(String key, byte[] data, RemoteSession session) { + + try { + Object object = DSMsgResolverFactory.getDecoder(dsWriter, dsReader).decode(data); + + // 解析CMD_DSINFO_REQUEST 请求的情况 + if (object instanceof String) { + String id = (String)object; + byte[] respLoadMsg = DSMsgResolverFactory.getEncoder(dsWriter, dsReader).encode(DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_RESPONSE, id, 0, 0); + session.reply(key, new DataSequenceLoadMessage(respLoadMsg)); + } + // 解析CMD_GETDSDIFF_REQUEST 请求的情况 + else if (object instanceof DSDiffRequestResult) { + + DSDiffRequestResult requestResult = (DSDiffRequestResult)object; + String id = requestResult.getId(); + long fromHeight = requestResult.getFromHeight(); + long toHeight = requestResult.getToHeight(); + //每个高度的数据序列差异元素进行一次响应的情况 + for (long i = fromHeight; i < toHeight + 1; i++) { + byte[] respLoadMsg = DSMsgResolverFactory.getEncoder(dsWriter, dsReader).encode(DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_RESPONSE, id, i, i); + session.reply(key, new DataSequenceLoadMessage(respLoadMsg)); + } + //所有差异进行一次响应的情况 + } + else { + throw new IllegalArgumentException("Receive data exception, unknown message type!"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + /** + * 响应类型设置 + * 分手动响应,自动响应两种类型 + */ + @Override + public REPLY replyType() { + return REPLY.MANUAL; + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSMsgResolverFactory.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSMsgResolverFactory.java new file mode 100644 index 00000000..c0dfae8d --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DSMsgResolverFactory.java @@ -0,0 +1,34 @@ +package com.jd.blockchain.statetransfer.message; + +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; + +/** + * 数据序列消息解析器工厂 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + * + */ +public class DSMsgResolverFactory { + + /** + * 获得数据序列消息编码器实例 + * @param dsWriter 差异请求者执行数据序列更新的执行器 + * @param dsReader 差异响应者执行数据序列读取的执行器 + * @return 消息编码器实例 + */ + public static DataSequenceMsgEncoder getEncoder(DataSequenceWriter dsWriter, DataSequenceReader dsReader) { + return new DataSequenceMsgEncoder(dsWriter, dsReader); + } + + /** + * 获得数据序列消息解码器实例 + * @param dsWriter 差异请求者执行数据序列更新的执行器 + * @param dsReader 差异响应者执行数据序列读取的执行器 + * @return 消息解码器实例 + */ + public static DataSequenceMsgDecoder getDecoder(DataSequenceWriter dsWriter, DataSequenceReader dsReader) { + return new DataSequenceMsgDecoder(dsWriter, dsReader); + } +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceLoadMessage.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceLoadMessage.java new file mode 100644 index 00000000..a4131e61 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceLoadMessage.java @@ -0,0 +1,28 @@ +package com.jd.blockchain.statetransfer.message; + +import com.jd.blockchain.stp.communication.message.LoadMessage; + +/** + * 数据序列复制的负载消息 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + * + */ +public class DataSequenceLoadMessage implements LoadMessage { + + byte[] bytes; + + public DataSequenceLoadMessage(byte[] bytes) { + this.bytes = bytes; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public byte[] toBytes() { + return bytes; + } +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgDecoder.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgDecoder.java new file mode 100644 index 00000000..6bfa4c97 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgDecoder.java @@ -0,0 +1,107 @@ +package com.jd.blockchain.statetransfer.message; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +import com.jd.blockchain.statetransfer.process.DSTransferProcess; +import com.jd.blockchain.statetransfer.result.DSDiffRequestResult; +import com.jd.blockchain.utils.io.BytesUtils; +import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; + +/** + * 数据序列消息解码器 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DataSequenceMsgDecoder { + + private int heightSize = 8; + private int msgTypeSize = 1; + + private long respHeight; + private long fromHeight; + private long toHeight; + private int idSize; + private byte[] idBytes; + private String id; + private int diffElemSize; + private byte[] diffElem; + DataSequenceElement dsElement; + + private DataSequenceWriter dsWriter; + private DataSequenceReader dsReader; + + public DataSequenceMsgDecoder(DataSequenceWriter dsWriter, DataSequenceReader dsReader) { + this.dsWriter = dsWriter; + this.dsReader = dsReader; + } + + + /** + * 对编过码的字节数组解码,还原成对象实例 + * @param loadMessage 字节序列 + * @return 解码后的对象 + */ + public Object decode(byte[] loadMessage) { + + try { + if (loadMessage.length <= 5) { + System.out.println("LoadMessage size is less than 5!"); + throw new IllegalArgumentException(); + } + + int dataLength = BytesUtils.toInt(loadMessage, 0, 4); + byte msgCode = loadMessage[4]; + + // send by diff provider, diff requester decode + if (msgCode == DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_RESPONSE.CODE) { + respHeight = BytesUtils.toLong(loadMessage, 4 + msgTypeSize); + idSize = BytesUtils.toInt(loadMessage, 4 + msgTypeSize + heightSize, 4); + idBytes = new byte[idSize]; + System.arraycopy(loadMessage, 4 + msgTypeSize + heightSize + 4, idBytes, 0, idSize); + id = new String(idBytes); + return new DataSequenceInfo(id, respHeight); + } + // send by diff provider, diff requester decode + else if (msgCode == DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_RESPONSE.CODE) { + diffElemSize = BytesUtils.toInt(loadMessage, 4 + msgTypeSize, 4); + diffElem = new byte[diffElemSize]; + System.arraycopy(loadMessage, 4 + msgTypeSize + 4, diffElem, 0, diffElemSize); + dsElement = BinarySerializeUtils.deserialize(diffElem); + return dsElement; + } + // send by diff requester, diff provider decode + else if (msgCode == DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_REQUEST.CODE) { + idSize = BytesUtils.toInt(loadMessage, 4 + msgTypeSize, 4); + idBytes = new byte[idSize]; + System.arraycopy(loadMessage, 4 + msgTypeSize + 4, idBytes, 0, idSize); + id = new String(idBytes); + return id; + } + // send by diff requester, diff provider decode + else if (msgCode == DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_REQUEST.CODE) { + fromHeight = BytesUtils.toLong(loadMessage, 4 + msgTypeSize); + toHeight = BytesUtils.toLong(loadMessage, 4 + msgTypeSize + heightSize); + idSize = BytesUtils.toInt(loadMessage, 4 + msgTypeSize + heightSize + heightSize, 4); + idBytes = new byte[idSize]; + System.arraycopy(loadMessage, 4 + msgTypeSize + heightSize + heightSize + 4, idBytes, 0, idSize); + id = new String(idBytes); + return new DSDiffRequestResult(id, fromHeight, toHeight); + } + else { + System.out.println("Unknown message type!"); + throw new IllegalArgumentException(); + } + + } catch (Exception e) { + System.out.println("Error to decode message: " + e.getMessage() + "!"); + e.printStackTrace(); + + } + + return null; + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgEncoder.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgEncoder.java new file mode 100644 index 00000000..71b3ef4c --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/message/DataSequenceMsgEncoder.java @@ -0,0 +1,133 @@ +package com.jd.blockchain.statetransfer.message; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +import com.jd.blockchain.statetransfer.process.DSTransferProcess; +import com.jd.blockchain.utils.io.BytesUtils; +import com.jd.blockchain.utils.serialize.binary.BinarySerializeUtils; + +/** + * 数据序列消息编码器 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DataSequenceMsgEncoder { + + private int heightSize = 8; + private int msgTypeSize = 1; + + private DataSequenceWriter dsWriter; + private DataSequenceReader dsReader; + + public DataSequenceMsgEncoder(DataSequenceWriter dsWriter, DataSequenceReader dsReader) { + this.dsWriter = dsWriter; + this.dsReader = dsReader; + } + + /** + * 目前暂时考虑fromHeight与toHeight相同的情况,即每次只对一个高度的差异内容进行编码并响应 + * 把消息编码成字节数组,再交给通信层传输 + * @param msgType 数据序列状态复制消息类型 + * @param id 数据序列唯一标识符 + * @param fromHeight 差异元素起始高度 + * @param toHeight 差异元素结束高度 + */ + public byte[] encode(DSTransferProcess.DataSequenceMsgType msgType, String id, long fromHeight, long toHeight) { + + try { + + int dataLength; + int idSize = id.getBytes().length; + byte[] loadMessage = null; + + // different encoding methods for different message types + // send by diff requester, diff requester encode + if (msgType == DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_REQUEST) { + + // CMD_DSINFO_REQUEST Message parts : 4 bytes total message size, 1 byte message type coe, + // 4 bytes id length, id content size bytes + + dataLength = 4 + msgTypeSize + 4 + idSize; + + loadMessage = new byte[dataLength]; + + System.arraycopy(BytesUtils.toBytes(dataLength), 0, loadMessage, 0, 4); + loadMessage[4] = msgType.CODE; + System.arraycopy(BytesUtils.toBytes(idSize), 0, loadMessage, 4 + msgTypeSize, 4); + System.arraycopy(id.getBytes(), 0, loadMessage, 4 + msgTypeSize + 4, idSize); + } + // send by diff requester, diff requester encode + else if (msgType == DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_REQUEST) { + + // CMD_GETDSDIFF_REQUEST Message parts : 4 bytes total message size, 1 byte message type coe, 8 bytes from height, + // 8 bytes to height, 4 bytes id length, id content size bytes + + dataLength = 4 + msgTypeSize + heightSize + heightSize + 4 + idSize; + + loadMessage = new byte[dataLength]; + + System.arraycopy(BytesUtils.toBytes(dataLength), 0, loadMessage, 0, 4); + loadMessage[4] = msgType.CODE; + System.arraycopy(BytesUtils.toBytes(fromHeight), 0, loadMessage, 4 + msgTypeSize, heightSize); + System.arraycopy(BytesUtils.toBytes(toHeight), 0, loadMessage, 4 + msgTypeSize + heightSize, heightSize); + System.arraycopy(BytesUtils.toBytes(idSize), 0, loadMessage, 4 + msgTypeSize + heightSize + heightSize, 4); + System.arraycopy(id.getBytes(), 0, loadMessage, 4 + msgTypeSize + heightSize + heightSize + 4, idSize); + } + // send by diff provider, diff provider encode + else if (msgType == DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_RESPONSE) { + + // CMD_DSINFO_RESPONSE Message parts : 4 bytes total message size, 1 byte message type coe, 8 bytes data sequence local height, + // 4 bytes id length, id content size bytes + + dataLength = 4 + msgTypeSize + heightSize + 4 + idSize; + + loadMessage = new byte[dataLength]; + + System.arraycopy(BytesUtils.toBytes(dataLength), 0, loadMessage, 0, 4); + loadMessage[4] = msgType.CODE; + System.arraycopy(BytesUtils.toBytes(dsReader.getDSInfo(id).getHeight()), 0, loadMessage, 4 + msgTypeSize, heightSize); + + System.arraycopy(BytesUtils.toBytes(idSize), 0, loadMessage, 4 + msgTypeSize + heightSize, 4); + System.arraycopy(id.getBytes(), 0, loadMessage, 4 + msgTypeSize + heightSize + 4, idSize); + + } + // send by diff provider, diff provider encode + else if (msgType == DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_RESPONSE) { + if (fromHeight != toHeight) { + throw new IllegalArgumentException("Height parameter error!"); + } + + // CMD_DSINFO_RESPONSE Message parts : 4 bytes total message size, 1 byte message type coe, + // 4 bytes diffElem size, diff content size; + + // 回调reader,获得这个高度上的所有差异的数据序列内容,并组织成DataSequenceElement结构 + DataSequenceElement element = dsReader.getDSDiffContent(id, fromHeight); + + byte[] diffElem = BinarySerializeUtils.serialize(element); + + dataLength = 4 + msgTypeSize + 4 + diffElem.length; + loadMessage = new byte[dataLength]; + + System.arraycopy(BytesUtils.toBytes(dataLength), 0, loadMessage, 0, 4); //total size + loadMessage[4] = msgType.CODE; //msgType size + System.arraycopy(BytesUtils.toBytes(diffElem.length), 0, loadMessage, 4 + msgTypeSize, 4); // diffElem size + System.arraycopy(diffElem, 0, loadMessage, 4 + msgTypeSize + 4, diffElem.length); // diffElem bytes + } + else { + System.out.println("Unknown message type!"); + throw new IllegalArgumentException(); + } + + return loadMessage; + + } catch (Exception e) { + System.out.println("Error to encode message type : " + msgType + "!"); + e.printStackTrace(); + } + + return null; + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSProcessManager.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSProcessManager.java new file mode 100644 index 00000000..97b14602 --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSProcessManager.java @@ -0,0 +1,186 @@ +package com.jd.blockchain.statetransfer.process; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +import com.jd.blockchain.statetransfer.comparator.DataSequenceComparator; +import com.jd.blockchain.statetransfer.message.DSDefaultMessageExecutor; +import com.jd.blockchain.statetransfer.result.DSInfoResponseResult; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; + +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.*; + +/** + * 数据序列状态复制过程管理器 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + * + */ +public class DSProcessManager { + + private static Map dSProcessMap = new ConcurrentHashMap<>(); + private RemoteSession[] remoteSessions; + private long dsInfoResponseTimeout = 20000; + private ExecutorService writeExecutors = Executors.newFixedThreadPool(5); + private int returnCode = 0; + + /** + * 启动一个指定数据序列的状态复制过程 + * @param dsInfo 数据序列当前状态信息 + * @param listener 本地监听者 + * @param targets 目标结点 + * @param dsWriter 差异请求者执行数据序列更新的执行器 + * @param dsReader 差异响应者执行数据序列读取的执行器 + * @return returnCode 执行结果码 + */ + public int startDSProcess(DataSequenceInfo dsInfo, InetSocketAddress listener, InetSocketAddress[] targets, DataSequenceWriter dsWriter, DataSequenceReader dsReader) { + + // create remote sessions manager, add listener + LocalNode listenNode = new LocalNode(listener.getHostName(), listener.getPort(), new DSDefaultMessageExecutor(dsReader, dsWriter)); + + RemoteSessionManager remoteSessionManager = new RemoteSessionManager(listenNode); + + // data sequence transfer process life cycle start + DSTransferProcess dsTransferProcess = new DSTransferProcess(dsInfo, targets); + dsTransferProcess.setDSReader(dsReader); + dsTransferProcess.setDSWriter(dsWriter); + dsTransferProcess.setRemoteSessionManager(remoteSessionManager); + + dSProcessMap.put(dsInfo.getId(), dsTransferProcess); + + try { + + //wait all listener nodes start + Thread.sleep(2000); + + // start network connections with targets + dsTransferProcess.start(); + + //get all target sessions + remoteSessions = dsTransferProcess.getSessions(); + + // async message send process + CallBackBarrier callBackBarrier = CallBackBarrier.newCallBackBarrier(remoteSessions.length, dsInfoResponseTimeout); + + // response message manage map + LinkedList dsInfoResponses = new LinkedList<>(); + + System.out.println("Async send CMD_DSINFO_REQUEST msg to targets will start!"); + // step1: send get dsInfo request, then hold + for (RemoteSession remoteSession : remoteSessions) { + CallBackDataListener dsInfoResponse = dsTransferProcess.send(DSTransferProcess.DataSequenceMsgType.CMD_DSINFO_REQUEST, remoteSession, 0, 0, callBackBarrier); + dsInfoResponses.addLast(dsInfoResponse); + } + + System.out.println("Wait CMD_DSINFO_RESPONSE msg from targets!"); + // step2: collect get dsInfo response + LinkedList receiveResponses = new LinkedList<>(); + if (callBackBarrier.tryCall()) { + Iterator iterator = dsInfoResponses.iterator(); + while (iterator.hasNext()) { + CallBackDataListener receiveResponse = iterator.next(); + if (receiveResponse.isDone()) { + receiveResponses.addLast(receiveResponse); + } + } + } + + System.out.printf("%s:%d Compute diff info!\r\n", listener.getHostName(), listener.getPort()); + // step3: process received responses + DSInfoResponseResult diffResult = dsTransferProcess.computeDiffInfo(receiveResponses); + + System.out.printf("%s:%d Diff info result height = %x!\r\n", listener.getHostName(), listener.getPort(), diffResult.getMaxHeight()); + + // height diff + long diff = dsInfo.getHeight() - diffResult.getMaxHeight(); + + if (diff == 0 || diff > 0) { + System.out.printf("%s:%d No duplication is required!\r\n", listener.getHostName(), listener.getPort()); + // no duplication is required, life cycle ends +// dsTransferProcess.close(); + dSProcessMap.remove(dsInfo.getId()); + return returnCode; + + } + else { + System.out.printf("%s:%d Duplication is required!\r\n", listener.getHostName(), listener.getPort()); + // step4: async send get data sequence diff request + // single step get diff + // async message send process + CallBackBarrier callBackBarrierDiff = CallBackBarrier.newCallBackBarrier((int)(diffResult.getMaxHeight() - dsInfo.getHeight()), dsInfoResponseTimeout); + LinkedList dsDiffResponses = new LinkedList<>(); + + RemoteSession responseSession = findResponseSession(diffResult.getMaxHeightRemoteNode(), remoteSessions); + System.out.println("Async send CMD_GETDSDIFF_REQUEST msg to targets will start!"); + + // step5: collect get data sequence diff response + for (long height = dsInfo.getHeight() + 1; height < diffResult.getMaxHeight() + 1; height++) { + CallBackDataListener dsDiffResponse = dsTransferProcess.send(DSTransferProcess.DataSequenceMsgType.CMD_GETDSDIFF_REQUEST, responseSession, height, height, callBackBarrierDiff); + dsDiffResponses.addLast(dsDiffResponse); + } + // 上述发送不合理,考虑一次性发送请求 + System.out.println("Wait CMD_GETDSDIFF_RESPONSE msg from targets!"); + LinkedList receiveDiffResponses = new LinkedList<>(); + if (callBackBarrierDiff.tryCall()) { + for (int i = 0; i < dsDiffResponses.size(); i++) { + CallBackDataListener asyncFutureDiff = dsDiffResponses.get(i); + if (asyncFutureDiff.isDone()) { + receiveDiffResponses.addLast(asyncFutureDiff.getCallBackData()); + } + } + } + + System.out.printf("%s:%d ReceiveDiffResponses size = %d !\r\n", listener.getHostName(), listener.getPort(), receiveDiffResponses.size()); + // step6: process data sequence diff response, update local data sequence state + System.out.println("Compute diff elements!"); + ArrayList dataSequenceElements = dsTransferProcess.computeDiffElement(receiveDiffResponses.toArray(new byte[receiveDiffResponses.size()][])); + System.out.println("Update local data sequence!"); + Collections.sort(dataSequenceElements, new DataSequenceComparator()); + returnCode = dsWriter.updateDSInfo(dsInfo, dataSequenceElements.toArray(new DataSequenceElement[dataSequenceElements.size()])); + + // data sequence transfer complete, close all sessions, end process life cycle + System.out.println("Close all sessions"); +// dsTransferProcess.close(); + dSProcessMap.remove(dsInfo.getId()); + } + + + } catch (Exception e) { + e.printStackTrace(); + } + return returnCode; + } + + /** + * 根据远端结点找与远端结点建立的会话 + * @param remoteNode 远端结点 + * @param remoteSessions 本地维护的远端结点会话表 + * @return 与远端结点对应的会话 + */ + RemoteSession findResponseSession(RemoteNode remoteNode, RemoteSession[] remoteSessions) { + for (RemoteSession remoteSession : remoteSessions) { + if (remoteSession.remoteNode().equals(remoteNode)) { + return remoteSession; + } + } + return null; + } + /** + * + * + */ +// void setDSReader(DataSequenceReader reader) { +// +// } + + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSTransferProcess.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSTransferProcess.java new file mode 100644 index 00000000..45fc89fb --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/process/DSTransferProcess.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.statetransfer.process; + +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; +import com.jd.blockchain.statetransfer.callback.DataSequenceReader; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriter; +import com.jd.blockchain.statetransfer.message.DSMsgResolverFactory; +import com.jd.blockchain.statetransfer.message.DataSequenceLoadMessage; +import com.jd.blockchain.statetransfer.result.DSInfoResponseResult; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import com.jd.blockchain.utils.IllegalDataException; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Map; + +/** + * 数据序列状态复制过程 + * @author zhangshuang + * @create 2019/4/11 + * @since 1.0.0 + */ +public class DSTransferProcess { + + private InetSocketAddress[] targets; + private DataSequenceWriter dsWriter; + private DataSequenceReader dsReader; + private DataSequenceInfo dsInfo; + private RemoteSessionManager remoteSessionManager; + private RemoteSession[] remoteSessions; + private String id; + + /** + * @param dsInfo 数据序列当前状态信息 + * @param targets 目标结点 + */ + public DSTransferProcess(DataSequenceInfo dsInfo, InetSocketAddress[] targets) { + this.dsInfo = dsInfo; + this.targets = targets; + this.id = dsInfo.getId(); + } + + /** + * @param dsWriter 差异请求者执行数据序列更新的执行器 + * @return void + */ + public void setDSWriter(DataSequenceWriter dsWriter) { + this.dsWriter = dsWriter; + } + + /** + * @param dsReader 差异响应者执行数据序列读取的执行器 + * @return void + */ + public void setDSReader(DataSequenceReader dsReader) { + this.dsReader = dsReader; + } + + /** + * @param remoteSessionManager 远端会话管理器 + * @return void + */ + public void setRemoteSessionManager(RemoteSessionManager remoteSessionManager) { + this.remoteSessionManager = remoteSessionManager; + } + + + /** + * + * @return 数据序列标识符 + */ + public String getId() { + return id; + } + + /** + * @param msgType 数据序列差异请求消息类型 + * @param remoteSession 目标结点对应的会话 + * @param fromHeight 差异起始高度 + * @param toHeight 差异结束高度 + * @param callBackBarrier 异步回调 + * @return 异步回调 + */ + CallBackDataListener send(DataSequenceMsgType msgType, RemoteSession remoteSession, long fromHeight, long toHeight, CallBackBarrier callBackBarrier) { + + byte[] loadMessage = DSMsgResolverFactory.getEncoder(dsWriter, dsReader).encode(msgType, id, fromHeight, toHeight); + + return remoteSession.asyncRequest(new DataSequenceLoadMessage(loadMessage), callBackBarrier); + } + + /** + * 计算数据序列差异元素数组 + * @param diffArray 差异的字节数组 + * @return 对差异字节数组的解码结果 + */ + public ArrayList computeDiffElement(byte[][] diffArray) { + + ArrayList dataSequenceElements = new ArrayList<>(); + + for (int i = 0 ; i < diffArray.length; i++) { + Object object = DSMsgResolverFactory.getDecoder(dsWriter, dsReader).decode(diffArray[i]); + if (object instanceof DataSequenceElement) { + dataSequenceElements.add((DataSequenceElement) object); + } + else { + throw new IllegalDataException("Unknown instance object!"); + } + } + + return dataSequenceElements; + } + + /** + * 根据差异提供者响应的数据序列状态信息找到拥有最大数据序列高度的远端结点 + * @param receiveResponses 数据序列差异请求者收到的远端结点状态的响应信息 + * @return 得到远端数据序列的最大高度以及拥有者结点 + */ + public DSInfoResponseResult computeDiffInfo(LinkedList receiveResponses) { + long maxHeight = 0; + RemoteNode maxHeightRemoteNode = null; + + System.out.println("ComputeDiffInfo receiveResponses size = "+ receiveResponses.size()); + + try { + for (CallBackDataListener receiveResponse : receiveResponses) { + Object object = DSMsgResolverFactory.getDecoder(dsWriter, dsReader).decode(receiveResponse.getCallBackData()); + if (object instanceof DataSequenceInfo) { + DataSequenceInfo dsInfo = (DataSequenceInfo) object; + long height = dsInfo.getHeight(); + // sava max height and its remote node + if (maxHeight < height) { + maxHeight = height; + maxHeightRemoteNode = receiveResponse.remoteNode(); + } + } + else { + throw new IllegalDataException("Unknown instance object!"); + } + + } + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + + return new DSInfoResponseResult(maxHeight, maxHeightRemoteNode); + } + + /** + * 获取本复制过程维护的远端会话表 + * @param + * @return 远端会话表数组 + */ + public RemoteSession[] getSessions() { + return remoteSessions; + } + + /** + * 关闭本复制过程维护的所有远端会话 + * @return void + */ + public void close() { + for (RemoteSession session : remoteSessions) { + session.closeAll(); + } + } + + /** + * 建立与远端目标结点的连接,产生本地维护的远端会话表 + * @return void + */ + public void start() { + + RemoteNode[] remoteNodes = new RemoteNode[targets.length]; + + for (int i = 0; i < remoteNodes.length; i++) { + remoteNodes[i] = new RemoteNode(targets[i].getHostName(), targets[i].getPort()); + } + + remoteSessions = remoteSessionManager.newSessions(remoteNodes); + } + + + /** + * 数据序列状态传输使用的消息类型 + * + */ + public enum DataSequenceMsgType { + CMD_DSINFO_REQUEST((byte) 0x1), + CMD_DSINFO_RESPONSE((byte) 0x2), + CMD_GETDSDIFF_REQUEST((byte) 0x3), + CMD_GETDSDIFF_RESPONSE((byte) 0x4), + ; + public final byte CODE; + + private DataSequenceMsgType(byte code) { + this.CODE = code; + } + + public static DataSequenceMsgType valueOf(byte code) { + for (DataSequenceMsgType msgType : DataSequenceMsgType.values()) { + if (msgType.CODE == code) { + return msgType; + } + } + throw new IllegalArgumentException("Unsupported code[" + code + "] of msgType!"); + } + } + + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSDiffRequestResult.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSDiffRequestResult.java new file mode 100644 index 00000000..af62bd6c --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSDiffRequestResult.java @@ -0,0 +1,45 @@ +package com.jd.blockchain.statetransfer.result; + +/** + * 数据序列差异提供者解码请求者"CMD_GETDSDIFF_REQUEST"消息时得到的结果 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DSDiffRequestResult { + + String id; + long fromHeight; + long toHeight; + + public DSDiffRequestResult(String id ,long fromHeight, long toHeight) { + this.id = id; + this.fromHeight = fromHeight; + this.toHeight = toHeight; + } + + public String getId() { + return id; + } + + public long getFromHeight() { + return fromHeight; + } + + public long getToHeight() { + return toHeight; + } + + public void setId(String id) { + this.id = id; + } + + public void setFromHeight(long fromHeight) { + this.fromHeight = fromHeight; + } + + public void setToHeight(long toHeight) { + this.toHeight = toHeight; + } + +} diff --git a/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSInfoResponseResult.java b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSInfoResponseResult.java new file mode 100644 index 00000000..7f6d48cc --- /dev/null +++ b/source/state-transfer/src/main/java/com/jd/blockchain/statetransfer/result/DSInfoResponseResult.java @@ -0,0 +1,37 @@ +package com.jd.blockchain.statetransfer.result; + +import com.jd.blockchain.stp.communication.node.RemoteNode; + +/** + * 数据序列差异请求者解码提供者"CMD_DSINFO_RESPONSE"消息时得到的结果 + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class DSInfoResponseResult { + + long maxHeight; + RemoteNode maxHeightRemoteNode; + + public DSInfoResponseResult(long maxHeight, RemoteNode maxHeightRemoteNode) { + this.maxHeight = maxHeight; + this.maxHeightRemoteNode = maxHeightRemoteNode; + } + + public long getMaxHeight() { + return maxHeight; + } + + public RemoteNode getMaxHeightRemoteNode() { + return maxHeightRemoteNode; + } + + public void setMaxHeight(long maxHeight) { + this.maxHeight = maxHeight; + } + + public void setMaxHeightRemoteNode(RemoteNode maxHeightRemoteNode) { + this.maxHeightRemoteNode = maxHeightRemoteNode; + } + +} diff --git a/source/state-transfer/src/test/java/test/com/jd/blockchain/statetransfer/StateTransferLayerTest.java b/source/state-transfer/src/test/java/test/com/jd/blockchain/statetransfer/StateTransferLayerTest.java new file mode 100644 index 00000000..5741262e --- /dev/null +++ b/source/state-transfer/src/test/java/test/com/jd/blockchain/statetransfer/StateTransferLayerTest.java @@ -0,0 +1,155 @@ +package test.com.jd.blockchain.statetransfer; + +import com.jd.blockchain.statetransfer.DataSequence; +import com.jd.blockchain.statetransfer.DataSequenceElement; +import com.jd.blockchain.statetransfer.DataSequenceInfo; +import com.jd.blockchain.statetransfer.callback.DataSequenceReaderImpl; +import com.jd.blockchain.statetransfer.callback.DataSequenceWriterImpl; +import com.jd.blockchain.statetransfer.process.DSProcessManager; +import com.jd.blockchain.utils.codec.Base58Utils; +import org.junit.Before; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.LinkedList; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author zhangshuang + * @create 2019/4/18 + * @since 1.0.0 + */ +public class StateTransferLayerTest { + + private final int[] listenPorts = new int[]{9000, 9010, 9020, 9030}; + + private String localIp = "127.0.0.1"; + + private int DataSequenceNum = 1; + + private int nodesNum = 4; + + private byte[] idBytes = new byte[20]; + + private Random rand = new Random(); + + private String[] dataSequenceIds = new String[DataSequenceNum]; + + private InetSocketAddress[] remoteNodeIps = new InetSocketAddress[nodesNum]; + + private final ExecutorService threadPool = Executors.newFixedThreadPool(8); + + private static LinkedList dataSequencesPerNode = new LinkedList<>(); + + // 假定每个数据序列元素里有四条记录数据 + private byte[][] dsElementDatas = new byte[4][]; + + + @Before + public void init() { + + // 产生两个唯一的数据序列Id标识 + for (int i = 0; i < DataSequenceNum; i++) { + + dataSequenceIds[i] = new String(); + rand.nextBytes(idBytes); + dataSequenceIds[i] = Base58Utils.encode(idBytes); + } + + // 准备好所有的远端结点,包括监听者 + for (int i = 0; i < nodesNum; i++) { + remoteNodeIps[i] = new InetSocketAddress(localIp, listenPorts[i]); + } + + // 为数据序列的每个高度准备好内容,为了方便测试,每个高度的内容设置为一致 + for (int i = 0; i < dsElementDatas.length; i++) { + rand.nextBytes(idBytes); + dsElementDatas[i] = idBytes; + } + + // 为结点准备数据序列 + for (String id : dataSequenceIds) { + for (int i = 0; i < remoteNodeIps.length; i++) { + DataSequence dataSequence = new DataSequence(remoteNodeIps[i], id); + + // 为数据序列的0,1,2高度添加内容 + for (int j = 0; j < 3; j++) { + dataSequence.addElement(new DataSequenceElement(id, j, dsElementDatas)); + } + dataSequencesPerNode.addLast(dataSequence); + } + + // 把其中一个结点的数据序列与其他结点区别开来 + for (int i = 0; i < dataSequencesPerNode.size(); i++) { + DataSequence dataSequence = dataSequencesPerNode.get(i); + if (dataSequence.getAddress().getPort() != listenPorts[0]) { + // 为数据序列的3,4高度添加内容 + for (int j = 3; j < 5; j++) { + dataSequence.addElement(new DataSequenceElement(id, j, dsElementDatas)); + } + } + } + } + } + + InetSocketAddress[] getTargetNodesIp(InetSocketAddress listenIp, InetSocketAddress[] remoteNodeIps) { + + // 获得除监听结点之外的其他远端结点 + InetSocketAddress[] targets = new InetSocketAddress[remoteNodeIps.length - 1]; + int j = 0; + + for (int i = 0; i < remoteNodeIps.length; i++) { + if ((remoteNodeIps[i].getHostName().equals(listenIp.getHostName())) && (remoteNodeIps[i].getPort() == listenIp.getPort())) { + continue; + } + targets[j++] = new InetSocketAddress(remoteNodeIps[i].getHostName(), remoteNodeIps[i].getPort()); + } + + return targets; + + } + + + DataSequence findDataSequence(String id, InetSocketAddress listenNodeAddr) { + for (DataSequence dataSequence : dataSequencesPerNode) { + if ((dataSequence.getAddress().getPort() == listenNodeAddr.getPort() && (dataSequence.getAddress().getHostName().equals(listenNodeAddr.getHostName())) + && (dataSequence.getId().equals(id)))) { + return dataSequence; + } + } + return null; + } + + + @Test + public void test() { + + CountDownLatch countDownLatch = new CountDownLatch(nodesNum); + + for (String id : dataSequenceIds) { + for (int i = 0; i < nodesNum; i++) { + InetSocketAddress listenNode = remoteNodeIps[i]; + threadPool.execute(() -> { + // 创建数据序列处理管理者实例 + DSProcessManager dsProcessManager = new DSProcessManager(); + DataSequence currDataSequence = findDataSequence(id, listenNode); + DataSequenceInfo dsInfo = currDataSequence.getDSInfo(); + InetSocketAddress[] targets = getTargetNodesIp(listenNode, remoteNodeIps); + dsProcessManager.startDSProcess(dsInfo, listenNode, targets, new DataSequenceWriterImpl(currDataSequence), new DataSequenceReaderImpl(currDataSequence)); + countDownLatch.countDown(); + }); + } + } + + // 等待数据序列更新完成 + try { + Thread.sleep(60000); + countDownLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/source/stp/pom.xml b/source/stp/pom.xml new file mode 100644 index 00000000..fd7fc35e --- /dev/null +++ b/source/stp/pom.xml @@ -0,0 +1,21 @@ + + + + + jdchain-root + com.jd.blockchain + 0.9.0-SNAPSHOT + + 4.0.0 + + stp + pom + + stp-communication + + + + UTF-8 + + diff --git a/source/stp/stp-communication/pom.xml b/source/stp/stp-communication/pom.xml new file mode 100644 index 00000000..96798377 --- /dev/null +++ b/source/stp/stp-communication/pom.xml @@ -0,0 +1,50 @@ + + + + + stp + com.jd.blockchain + 0.9.0-SNAPSHOT + + 4.0.0 + + stp-communication + + stp-communication + + + UTF-8 + 1.8 + 1.8 + + + + + junit + junit + test + + + + commons-codec + commons-codec + + + + com.alibaba + fastjson + + + + io.netty + netty-all + 4.1.29.Final + + + + com.google.guava + guava + + + diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/MessageExecutor.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/MessageExecutor.java new file mode 100644 index 00000000..dba7e0d0 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/MessageExecutor.java @@ -0,0 +1,56 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.MessageExecutor + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午10:59 + * Description: + */ +package com.jd.blockchain.stp.communication; + +/** + * 消息执行器 + * 该执行器由其他应用实现 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + * @date 2019-04-18 15:29 + */ + +public interface MessageExecutor { + + /** + * 接收到receive消息如何处理 + * + * @param key + * 请求消息的Key,调用者需要在应答时通过该Key应答远端 + * @param data + * 请求消息的内容 + * @param session + * 远端Session,描述该消息是从哪发送来的 + * @return + * 应答结果 + */ + byte[] receive(String key, byte[] data, RemoteSession session); + + /** + * 应答方式 + * + * @return + * 参考:{@link REPLY} + */ + REPLY replyType(); + + // 应答方式 + enum REPLY { + // 手动应答:Receiver不会自动发送应答请求,需要调用 + // session.reply(String key, LoadMessage loadMessage) 或 + // asyncReply(String key, LoadMessage loadMessage) + MANUAL, + + // 自动应答:Receiver会根据receive方法的应答结果自动调用应答 + // 使用者不能重新调用 + AUTO, + ; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/RemoteSession.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/RemoteSession.java new file mode 100644 index 00000000..0ce9fceb --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/RemoteSession.java @@ -0,0 +1,206 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.RemoteSession + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午11:15 + * Description: + */ +package com.jd.blockchain.stp.communication; + +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.connection.Connection; +import com.jd.blockchain.stp.communication.message.LoadMessage; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import org.apache.commons.codec.binary.Hex; + +import java.util.concurrent.TimeUnit; + + +/** + * 远端Session + * + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public class RemoteSession { + + /** + * 本地节点ID + */ + private String localId; + + /** + * 远端节点 + */ + private RemoteNode remoteNode; + + /** + * 远端连接 + */ + private Connection connection; + + /** + * 对应远端节点消息的处理器 + * 该处理器若为NULL,则使用当前节点默认处理器 + */ + private MessageExecutor messageExecutor; + + /** + * 构造器 + * @param localId + * 本地节点ID + * @param connection + * 对应连接 + */ + public RemoteSession(String localId, Connection connection) { + this(localId, connection, null); + } + + /** + * 构造器 + * @param localId + * 本地节点ID + * @param connection + * 对应连接 + * @param messageExecutor + * 对应远端消息处理器 + */ + public RemoteSession(String localId, Connection connection, MessageExecutor messageExecutor) { + this.localId = localId; + this.connection = connection; + this.messageExecutor = messageExecutor; + this.remoteNode = connection.remoteNode(); + } + + public void init() { + connection.initSession(this); + } + + public void initExecutor(MessageExecutor messageExecutor) { + this.messageExecutor = messageExecutor; + } + + /** + * 同步请求 + * 该请求会阻塞原线程 + * + * @param loadMessage + * 要请求的负载消息 + * @return + * 应答,直到有消息应答或出现异常 + * @throws Exception + */ + public byte[] request(LoadMessage loadMessage) throws Exception { + return this.connection.request(this.localId, loadMessage, null).getCallBackData(); + } + + /** + * 同步请求 + * 该请求会阻塞原线程 + * + * @param loadMessage + * 要请求的负载消息 + * @param time + * 请求的最长等待时间 + * @param timeUnit + * 请求的最长等待单位 + * @return + * 应答,直到有消息或时间截止或出现异常 + * @throws Exception + */ + public byte[] request(LoadMessage loadMessage, long time, TimeUnit timeUnit) throws Exception { + return this.connection.request(this.localId, loadMessage, null).getCallBackData(time, timeUnit); + } + + /** + * 异步请求 + * 不会阻塞调用线程 + * + * @param loadMessage + * 要发送的负载消息 + * @return + * 应答,需要调用者从Listener中获取结果 + */ + public CallBackDataListener asyncRequest(LoadMessage loadMessage) { + return asyncRequest(loadMessage, null); + } + + /** + * 异步请求 + * 不会阻塞调用线程 + * + * @param loadMessage + * 要请求的负载消息 + * @param callBackBarrier + * 回调栅栏(用于多个请求时进行统一阻拦) + * @return + * 应答,需要调用者从Listener中获取结果 + */ + public CallBackDataListener asyncRequest(LoadMessage loadMessage, CallBackBarrier callBackBarrier) { + return this.connection.request(this.localId, loadMessage, callBackBarrier); + } + + /** + * 应答 + * + * @param key + * 请求消息的Key + * @param loadMessage + * 需要应答的负载消息 + */ + public void reply(String key, LoadMessage loadMessage) { + this.connection.reply(this.localId, key, loadMessage); + } + + public void closeAll() { + this.connection.closeAll(); + } + + public void closeReceiver() { + this.connection.closeReceiver(); + } + + public void closeSender() { + this.connection.closeSender(); + } + + /** + * 返回本地节点ID + * + * @return + */ + public String localId() { + return localId; + } + + /** + * 返回远端对应的SessionID + * + * @return + */ + public String remoteSessionId() { + return Hex.encodeHexString(remoteNode.toString().getBytes()); + } + + /** + * 返回远端对应执行器 + * + * @return + */ + public MessageExecutor messageExecutor() { + return this.messageExecutor; + } + + /** + * 返回对应远端节点 + * + * @return + */ + public RemoteNode remoteNode() { + return remoteNode; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackBarrier.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackBarrier.java new file mode 100644 index 00000000..41da6b66 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackBarrier.java @@ -0,0 +1,74 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.CallBackBarrier + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/12 上午10:22 + * Description: + */ +package com.jd.blockchain.stp.communication.callback; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * 回调栅栏 + * 用于对批量请求的应答回调处理 + * @author shaozhuguang + * @create 2019/4/12 + * @since 1.0.0 + */ + +public class CallBackBarrier { + + private CountDownLatch countDownLatch; + + /** + * 默认最大尝试调用时间(单位:毫秒) + */ + private long maxTryCallMillSeconds = 2000; + + + /** + * 静态构造器 + * @param barrierLength + * 请求的远端数量 + * @return + */ + public static final CallBackBarrier newCallBackBarrier(int barrierLength) { + return new CallBackBarrier(barrierLength); + } + + /** + * 静态构造器 + * @param barrierLength + * 请求的远端数量 + * @param maxTryCallMillSeconds + * 最大尝试的时间,单位:毫秒 + * @return + */ + public static final CallBackBarrier newCallBackBarrier(int barrierLength, long maxTryCallMillSeconds) { + return new CallBackBarrier(barrierLength, maxTryCallMillSeconds); + } + + private CallBackBarrier(int barrierLength) { + this.countDownLatch = new CountDownLatch(barrierLength); + } + + private CallBackBarrier(int barrierLength, long maxTryCallMillSeconds) { + this.countDownLatch = new CountDownLatch(barrierLength); + this.maxTryCallMillSeconds = maxTryCallMillSeconds; + } + + public void release() { + countDownLatch.countDown(); + } + + public boolean tryCall() throws InterruptedException { + return countDownLatch.await(maxTryCallMillSeconds, TimeUnit.MILLISECONDS); + } + + public boolean tryCall(long timeout, TimeUnit unit) throws InterruptedException { + return countDownLatch.await(timeout, unit); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackDataListener.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackDataListener.java new file mode 100644 index 00000000..9de7aa32 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackDataListener.java @@ -0,0 +1,113 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.callback.CallBackDataListener + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/15 下午4:40 + * Description: + */ +package com.jd.blockchain.stp.communication.callback; + +import com.jd.blockchain.stp.communication.node.RemoteNode; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 数据回调监听器 + * @author shaozhuguang + * @create 2019/4/15 + * @since 1.0.0 + */ + +public class CallBackDataListener { + + /** + * Future + */ + private CompletableFuture future = new CompletableFuture<>(); + + /** + * 远端节点 + */ + private RemoteNode remoteNode; + + private boolean isFill = false; + + private Lock lock = new ReentrantLock(); + + + /** + * 构造器 + * @param remoteNode + * 远端节点信息 + */ + public CallBackDataListener(RemoteNode remoteNode) { + this.remoteNode = remoteNode; + } + + /** + * 获取返回的数据 + * 调用该方法会阻塞当前线程,直到有数据返回或出现异常 + * @return + * 应答结果 + * @throws InterruptedException + * @throws ExecutionException + */ + public byte[] getCallBackData() throws InterruptedException, ExecutionException { + return future.get(); + } + + /** + * 指定时间内获取返回的数据 + * 调用该方法会阻塞当前线程,直到时间到达或有数据返回或出现异常 + * @param time + * 超时时间 + * @param timeUnit + * 超时单位 + * @return + * 应答结果,若指定时间内没有数据,则返回null + * @throws InterruptedException + * @throws ExecutionException + * @throws TimeoutException + */ + public byte[] getCallBackData(long time, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(time, timeUnit); + } + + /** + * 设置返回的数据 + * @param data + */ + public void setCallBackData(byte[] data) { + // 防止数据多次设置 + if (!isFill) { + try { + lock.lock(); + // Double Check + if (!isFill) { + future.complete(data); + isFill = true; + } + } finally { + lock.unlock(); + } + } + } + + public RemoteNode remoteNode() { + return this.remoteNode; + } + + /** + * 判断是否异步操作完成 + * @return + */ + public boolean isDone() { + return future.isDone(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackLauncher.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackLauncher.java new file mode 100644 index 00000000..e1f9473a --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/callback/CallBackLauncher.java @@ -0,0 +1,80 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.callback.CallBackLauncher + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/17 下午6:27 + * Description: + */ +package com.jd.blockchain.stp.communication.callback; + +import java.util.concurrent.Semaphore; + +/** + * 启动器回调 + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + * @date 2019-04-19 09:53 + */ + +public class CallBackLauncher { + + /** + * 是否启动成功 + */ + private boolean isBootSuccess = false; + + /** + * 信号量 + */ + private Semaphore isBooted = new Semaphore(0, true); + + /** + * 异常 + */ + private Exception exception; + + /** + * 标识当前启动成功 + */ + public void bootSuccess() { + isBootSuccess = true; + release(); + } + + /** + * 标识当前启动失败 + * @param e + * 导致失败的异常信息 + */ + public void bootFail(Exception e) { + this.exception = e; + isBootSuccess = false; + release(); + } + + /** + * 等待启动完成 + * 调用该方法会阻塞当前线程,知道启动完成或发生异常 + * @return + * 当前对象 + * @throws InterruptedException + */ + public CallBackLauncher waitingBooted() throws InterruptedException { + this.isBooted.acquire(); + return this; + } + + public boolean isBootSuccess() { + return isBootSuccess; + } + + public Exception exception() { + return exception; + } + + private void release() { + this.isBooted.release(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AbstractAsyncExecutor.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AbstractAsyncExecutor.java new file mode 100644 index 00000000..fc99cb05 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AbstractAsyncExecutor.java @@ -0,0 +1,71 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.AbstractAsyncExecutor + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/17 上午11:16 + * Description: + */ +package com.jd.blockchain.stp.communication.connection; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.jd.blockchain.stp.communication.callback.CallBackLauncher; + +import java.util.concurrent.*; + +/** + * 抽象异步执行器 + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + */ + +public abstract class AbstractAsyncExecutor implements AsyncExecutor{ + + /** + * 线程池可处理队列的容量 + */ + private static final int QUEUE_CAPACITY = 1024; + + /** + * 回调执行器 + */ + protected final CallBackLauncher callBackLauncher = new CallBackLauncher(); + + /** + * 默认提供的初始化活跃线程调度器 + * @return + */ + @Override + public ThreadPoolExecutor initRunThread() { + ThreadFactory timerFactory = new ThreadFactoryBuilder() + .setNameFormat(threadNameFormat()).build(); + + ThreadPoolExecutor runThread = new ThreadPoolExecutor(1, 1, + 60, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(QUEUE_CAPACITY), + timerFactory, + new ThreadPoolExecutor.AbortPolicy()); + + return runThread; + } + + /** + * 启动完成后回调 + * 该调用会阻塞当前线程,直到启动完成,无论是成功或失败 + * @return + * 回调执行器 + * 成功或失败会在回调执行器中有所体现 + * @throws InterruptedException + */ + @Override + public CallBackLauncher waitBooted() throws InterruptedException { + return callBackLauncher.waitingBooted(); + } + + /** + * 线程池中的线程命名格式 + * @return + */ + public abstract String threadNameFormat(); +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AsyncExecutor.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AsyncExecutor.java new file mode 100644 index 00000000..5bfdd682 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/AsyncExecutor.java @@ -0,0 +1,36 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.AsyncExecutor + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/17 上午11:14 + * Description: + */ +package com.jd.blockchain.stp.communication.connection; + +import com.jd.blockchain.stp.communication.callback.CallBackLauncher; + +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 异步执行器接口 + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + */ + +public interface AsyncExecutor { + + /** + * 初始化运行线程 + * @return + */ + ThreadPoolExecutor initRunThread(); + + /** + * 启动完成后返回调度执行器 + * @return + * @throws InterruptedException + */ + CallBackLauncher waitBooted() throws InterruptedException; +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Connection.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Connection.java new file mode 100644 index 00000000..5d534686 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Connection.java @@ -0,0 +1,220 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.Connection + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 下午5:39 + * Description: + */ +package com.jd.blockchain.stp.communication.connection; + +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.callback.CallBackLauncher; +import com.jd.blockchain.stp.communication.connection.listener.ReplyListener; +import com.jd.blockchain.stp.communication.message.LoadMessage; +import com.jd.blockchain.stp.communication.message.SessionMessage; +import com.jd.blockchain.stp.communication.message.TransferMessage; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; + +import java.util.Random; + +/** + * 统一连接对象 + * 该对象中有两个对象Receiver和Sender + * Receiver为复用对象(每个端口监听产生的Receiver只有一个) + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + * @date 2019-04-18 14:49 + */ +public class Connection { + + /** + * 远端节点 + */ + private RemoteNode remoteNode; + + /** + * 接收器 + */ + private Receiver receiver; + + /** + * 发送器 + */ + private Sender sender; + + /** + * 构造器 + * + * @param receiver + */ + public Connection(Receiver receiver) { + this.receiver = receiver; + } + + /** + * 初始化RemoteSession + * + * @param remoteSession + */ + public void initSession(RemoteSession remoteSession) { + this.receiver.initRemoteSession(remoteSession.remoteSessionId(), remoteSession); + } + + /** + * 连接远端 + * + * @param remoteNode + * 远端节点 + * @param messageExecutorClass + * 希望远端节点处理本地节点消息时的消息处理器 + * @return + * 回调执行器 + * @throws InterruptedException + */ + public CallBackLauncher connect(RemoteNode remoteNode, String messageExecutorClass) throws InterruptedException { + this.remoteNode = remoteNode; + this.sender = new Sender(this.receiver.localNode(), this.remoteNode, sessionMessage(messageExecutorClass)); + this.sender.connect(); + return this.sender.waitBooted(); + } + + /** + * 发送请求 + * + * 处理过程简述如下: + * 1、生成底层消息(TransferMessage),其中消息类型为请求,用于描述本次发送的消息是用于请求应答; + * 2、根据消息的唯一Key,生成listenKey,并生成应答监听器 + * 3、将应答监听器添加到Receiver中(Receiver中是以Map存储) + * 4、调用Sender发送消息至对端节点 + * 5、返回应答监听器的回调数据监听对象 + * + * @param sessionId + * 当前SessionId + * @param loadMessage + * 载体消息 + * @param callBackBarrier + * 回调栅栏 + * @return + */ + public CallBackDataListener request(String sessionId, LoadMessage loadMessage, CallBackBarrier callBackBarrier) { + + TransferMessage transferMessage = transferMessage(sessionId, null, loadMessage, TransferMessage.MESSAGE_TYPE.TYPE_REQUEST); + + // 监听器的Key + String listenKey = transferMessage.toListenKey(); + + // 创建监听器 + ReplyListener replyListener = new ReplyListener(listenKey, this.remoteNode, callBackBarrier); + + // 添加监听器至Receiver + this.receiver.addListener(replyListener); + + // 发送请求 + this.sender.send(transferMessage); + + return replyListener.callBackDataListener(); + } + + /** + * 发送应答 + * + * @param sessionId + * 当前SessionID + * @param key + * 请求消息的Key,用于描述对应的请求 + * @param loadMessage + * 应答的载体消息 + */ + public void reply(String sessionId, String key, LoadMessage loadMessage) { + TransferMessage transferMessage = transferMessage(sessionId, key, loadMessage, TransferMessage.MESSAGE_TYPE.TYPE_RESPONSE); + + // 通过Sender发送数据 + this.sender.send(transferMessage); + } + + /** + * 生成载体消息的Key + * + * @param loadMessage + * @return + */ + private String loadKey(LoadMessage loadMessage) { + // key每次不能一致,因此增加随机数 + byte[] randomBytes = new byte[8]; + new Random().nextBytes(randomBytes); + byte[] loadBytes = loadMessage.toBytes(); + byte[] keyBytes = new byte[loadBytes.length + randomBytes.length]; + System.arraycopy(randomBytes, 0, keyBytes, 0, randomBytes.length); + System.arraycopy(loadBytes, 0, keyBytes, randomBytes.length, loadBytes.length); + // 使用Sha256求Hash + byte[] sha256Bytes = DigestUtils.sha256(keyBytes); + // 使用base64作为Key + return Base64.encodeBase64String(sha256Bytes); + } + + /** + * 生成TransferMessage + * + * @param sessionId + * 节点ID + * @param key + * 消息Key + * @param loadMessage + * 载体消息 + * @param messageType + * 消息类型 + * @return + */ + private TransferMessage transferMessage(String sessionId, String key, LoadMessage loadMessage, TransferMessage.MESSAGE_TYPE messageType) { + + if (key == null || key.length() == 0) { + key = loadKey(loadMessage); + } + + TransferMessage transferMessage = new TransferMessage( + sessionId, messageType.code(), key, loadMessage.toBytes()); + + return transferMessage; + } + + /** + * 生成SessionMessage + * + * @param messageExecutorClass + * + * @return + */ + private SessionMessage sessionMessage(String messageExecutorClass) { + + LocalNode localNode = this.receiver.localNode(); + + SessionMessage sessionMessage = new SessionMessage( + localNode.getHostName(), localNode.getPort(), messageExecutorClass); + + return sessionMessage; + } + + public void closeAll() { + closeReceiver(); + closeSender(); + } + + public RemoteNode remoteNode() { + return remoteNode; + } + + public void closeReceiver() { + this.receiver.close(); + } + + public void closeSender() { + this.sender.close(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Receiver.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Receiver.java new file mode 100644 index 00000000..fdbb0df6 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Receiver.java @@ -0,0 +1,161 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.inner.Receiver + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午10:59 + * Description: + */ +package com.jd.blockchain.stp.communication.connection; + +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.connection.handler.HeartBeatReceiverHandler; +import com.jd.blockchain.stp.communication.connection.handler.HeartBeatReceiverTrigger; +import com.jd.blockchain.stp.communication.connection.handler.ReceiverHandler; +import com.jd.blockchain.stp.communication.connection.listener.ReplyListener; +import com.jd.blockchain.stp.communication.manager.ConnectionManager; +import com.jd.blockchain.stp.communication.node.LocalNode; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.timeout.IdleStateHandler; + +import java.io.Closeable; +import java.net.InetSocketAddress; +import java.util.concurrent.*; + +/** + * 接收器 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public class Receiver extends AbstractAsyncExecutor implements Closeable { + + /** + * Netty中的BOSS线程 + */ + private final EventLoopGroup bossGroup = new NioEventLoopGroup(); + + /** + * Netty中的Worker线程 + */ + private final EventLoopGroup workerGroup = new NioEventLoopGroup(); + + /** + * 本地节点 + */ + private LocalNode localNode; + + /** + * 消息接收Handler + */ + private ReceiverHandler receiverHandler; + + public Receiver(LocalNode localNode) { + this.localNode = localNode; + } + + /** + * 启动监听 + */ + public void startListen() { + ServerBootstrap bootstrap = new ServerBootstrap(); + + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 1024) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .localAddress(new InetSocketAddress(this.localNode.getPort())) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline() +// .addLast(new LoggingHandler(LogLevel.ERROR)) + .addLast(new IdleStateHandler(8, 0, 0, TimeUnit.SECONDS)) + .addLast(new LineBasedFrameDecoder(1024)) + .addLast(new StringDecoder()) + .addLast(new HeartBeatReceiverTrigger()) + .addLast(new HeartBeatReceiverHandler()) + .addLast(receiverHandler); + } + }); + + // 由单独的线程启动,防止外部调用线程阻塞 + ThreadPoolExecutor runThread = initRunThread(); + runThread.execute(() -> { + try { + ChannelFuture f = bootstrap.bind().sync(); + boolean isStartSuccess = f.isSuccess(); + if (isStartSuccess) { + super.callBackLauncher.bootSuccess(); + // 启动成功 + f.channel().closeFuture().sync(); + } else { + // 启动失败 + throw new Exception("Receiver start fail :" + f.cause().getMessage() + " !!!"); + } + } catch (Exception e) { + super.callBackLauncher.bootFail(e); + } finally { + close(); + } + }); + } + + @Override + public String threadNameFormat() { + return "receiver-pool-%d"; + } + + /** + * 初始化ReceiverHandler + * + * @param connectionManager + * 连接管理器 + * @param messageExecutorClass + * 当前节点的消息处理Class + */ + public void initReceiverHandler(ConnectionManager connectionManager, String messageExecutorClass) { + receiverHandler = new ReceiverHandler(connectionManager, messageExecutorClass, this.localNode); + } + + /** + * 初始化远端Session + * + * @param sessionId + * + * @param remoteSession + */ + public void initRemoteSession(String sessionId, RemoteSession remoteSession) { + receiverHandler.putRemoteSession(sessionId, remoteSession); + } + + /** + * 添加监听器 + * + * @param replyListener + */ + public void addListener(ReplyListener replyListener) { + receiverHandler.addListener(replyListener); + } + + @Override + public void close() { + receiverHandler.close(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + public LocalNode localNode() { + return this.localNode; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Sender.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Sender.java new file mode 100644 index 00000000..7c17f18f --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/Sender.java @@ -0,0 +1,207 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.inner.Sender + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午10:58 + * Description: + */ +package com.jd.blockchain.stp.communication.connection; + +import com.jd.blockchain.stp.communication.connection.handler.*; +import com.jd.blockchain.stp.communication.message.IMessage; +import com.jd.blockchain.stp.communication.message.SessionMessage; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.timeout.IdleStateHandler; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +/** + * 发送器 + * + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + * @date 2019-04-18 15:08 + */ +public class Sender extends AbstractAsyncExecutor implements Closeable { + + private final EventLoopGroup loopGroup = new NioEventLoopGroup(); + + private Bootstrap bootstrap; + + private ChannelFuture channelFuture; + + /** + * 当前节点的SessionMessage + */ + private SessionMessage sessionMessage; + + private LocalNode localNode; + + private RemoteNode remoteNode; + +// /** +// * 远端HOST +// */ +// private String remoteHost; +// +// /** +// * 远端端口 +// */ +// private int remotePort; + + /** + * 监听Handler(重连Handler) + */ + private WatchDogHandler watchDogHandler; + + public Sender(LocalNode localNode, RemoteNode remoteNode, SessionMessage sessionMessage) { + init(localNode, remoteNode, sessionMessage); + } + +// public Sender(String remoteHost, int remotePort, SessionMessage sessionMessage) { +// init(remoteHost, remotePort, sessionMessage); +// } + + /** + * 连接 + */ + public void connect() { + watchDogHandler = new WatchDogHandler(this.remoteNode.getHostName(), this.remoteNode.getPort(), bootstrap); + + ChannelHandlers frontChannelHandlers = new ChannelHandlers() + .addHandler(watchDogHandler); + + ChannelHandlers afterChannelHandlers = new ChannelHandlers() + .addHandler(new StringDecoder()) + .addHandler(new HeartBeatSenderTrigger()) + .addHandler(new HeartBeatSenderHandler()) + .addHandler(new SenderHandler(this.localNode, this.remoteNode, this.sessionMessage)); + + // 初始化watchDogHandler + watchDogHandler.init(frontChannelHandlers.toArray(), afterChannelHandlers.toArray()); + + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline() + .addLast(frontChannelHandlers.toArray()) + .addLast(new IdleStateHandler(10, 4, 0, TimeUnit.SECONDS)) + .addLast(new LineBasedFrameDecoder(1024)) + .addLast(afterChannelHandlers.toArray()); + } + }); + + ThreadPoolExecutor runThread = initRunThread(); + + // 单独线程进行连接,防止当前调用线程阻塞 + runThread.execute(() -> { + try { + // 发起连接请求 + channelFuture = bootstrap.connect(this.remoteNode.getHostName(), this.remoteNode.getPort()).sync(); + boolean isStartSuccess = channelFuture.isSuccess(); + if (isStartSuccess) { + // 启动成功 + // 设置ChannelFuture对象,以便于发送的连接状态处理 + watchDogHandler.initChannelFuture(channelFuture); + // 释放等待 + super.callBackLauncher.bootSuccess(); + // 等待客户端关闭连接 + channelFuture.channel().closeFuture().sync(); + } else { + // 启动失败 + throw new Exception("Sender start fail :" + channelFuture.cause().getMessage() + " !!!"); + } + } catch (Exception e) { + super.callBackLauncher.bootFail(e); + } finally { + close(); + } + }); + } + + /** + * 初始化相关配置 + * + * @param localNode + * 本地节点 + * @param remoteNode + * 远端节点 + * @param sessionMessage + * 本地节点连接到远端节点后发送的SessionMessage + */ + private void init(LocalNode localNode, RemoteNode remoteNode, SessionMessage sessionMessage) { + this.localNode = localNode; + this.remoteNode = remoteNode; + + this.sessionMessage = sessionMessage; + + this.bootstrap = new Bootstrap().group(loopGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true); + } + + @Override + public String threadNameFormat() { + return "sender-pool-%d"; + } + + /** + * 发送消息 + * + * @param message + * 消息统一接口 + */ + public void send(IMessage message) { + watchDogHandler.channelFuture().channel().writeAndFlush(message.toTransferByteBuf()); + } + + @Override + public void close() { + // 因为要重连,需要仍然需要使用该LoopGroup,因此不能关闭 +// loopGroup.shutdownGracefully(); + } + + /** + * ChannelHandler集合管理类 + */ + public static class ChannelHandlers { + + private List channelHandlers = new ArrayList<>(); + + /** + * 添加指定的ChannelHandler + * + * @param channelHandler + * 需要加入的ChannelHandler + * @return + */ + public ChannelHandlers addHandler(ChannelHandler channelHandler) { + channelHandlers.add(channelHandler); + return this; + } + + /** + * List集合转换为数组 + * + * @return + */ + public ChannelHandler[] toArray() { + ChannelHandler[] channelHandlerArray = new ChannelHandler[channelHandlers.size()]; + return channelHandlers.toArray(channelHandlerArray); + } + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverHandler.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverHandler.java new file mode 100644 index 00000000..ab3595e9 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverHandler.java @@ -0,0 +1,43 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.HeartBeatSenderHandler + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/15 上午10:10 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.jd.blockchain.stp.communication.message.HeartBeatMessage; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * 心跳接收Handler + * @author shaozhuguang + * @create 2019/4/15 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class HeartBeatReceiverHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + // 判断当前收到的信息是否为心跳信息 + if (HeartBeatMessage.isHeartBeat(msg)) { + // 收到的消息是心跳消息,此时需要回复一个心跳消息 + HeartBeatMessage.write(ctx); + System.out.println("Receive HeartBeat Request Message -> " + msg.toString()); + } else { + // 非心跳信息的情况下交由其他Handler继续处理 + super.channelRead(ctx, msg); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // 出现异常直接关闭连接 + ctx.close(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverTrigger.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverTrigger.java new file mode 100644 index 00000000..48288dc6 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatReceiverTrigger.java @@ -0,0 +1,42 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.HeartBeatSenderTrigger + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/15 上午10:11 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; + +/** + * 心跳接收触发器 + * @author shaozhuguang + * @create 2019/4/15 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class HeartBeatReceiverTrigger extends ChannelInboundHandlerAdapter { + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + // 服务端只会接收心跳数据后应答,而不会主动应答 + if (evt instanceof IdleStateEvent) { + IdleState idleState = ((IdleStateEvent) evt).state(); + // 读请求超时表示很久没有收到客户端请求 + if (idleState.equals(IdleState.READER_IDLE)) { + // 长时间未收到客户端请求,则关闭连接 + System.out.println("Long Time UnReceive HeartBeat Request, Close Connection !!!"); + ctx.close(); + } + } else { + // 非空闲状态事件,由其他Handler处理 + super.userEventTriggered(ctx, evt); + } + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderHandler.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderHandler.java new file mode 100644 index 00000000..be1c187b --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderHandler.java @@ -0,0 +1,42 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.HeartBeatSenderHandler + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/15 上午10:10 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.jd.blockchain.stp.communication.message.HeartBeatMessage; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * 心跳发送Handler + * @author shaozhuguang + * @create 2019/4/15 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class HeartBeatSenderHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + // 判断收到的消息 + if (HeartBeatMessage.isHeartBeat(msg)) { + // 假设收到的消息是字符串,并且是心跳消息,说明由服务端发送了心跳信息 + // TODO 此处不需要进行消息反馈,只需要打印日志即可 + System.out.println("Receive HeartBeat Response Message -> " + msg.toString()); + } else { + super.channelRead(ctx, msg); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // 出现异常直接关闭连接 + ctx.close(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderTrigger.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderTrigger.java new file mode 100644 index 00000000..9a61f31a --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/HeartBeatSenderTrigger.java @@ -0,0 +1,48 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.HeartBeatSenderTrigger + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/15 上午10:11 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.jd.blockchain.stp.communication.message.HeartBeatMessage; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; + +/** + * 心跳发送触发器 + * @author shaozhuguang + * @create 2019/4/15 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class HeartBeatSenderTrigger extends ChannelInboundHandlerAdapter { + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + + // 心跳事件(状态空闲事件) + if (evt instanceof IdleStateEvent) { + IdleState idleState = ((IdleStateEvent) evt).state(); + if (idleState.equals(IdleState.READER_IDLE)) { + // Sender读超时,表示在指定时间内未收到Receiver的应答 + // 此时关闭连接,自动调用重连机制,进行重连操作 + System.out.println("Long Time UnReceive HeartBeat Response, Close Connection !!!"); + ctx.close(); + } else if (idleState == IdleState.WRITER_IDLE) { + // Sender写超时,表示很长时间没有发送消息了,需要发送消息至Receiver + System.out.println("Read TimeOut Trigger, Send HeartBeat Request !!!"); + HeartBeatMessage.write(ctx); + } + // TODO 还有一种情况是读写超时,该情况暂不处理 + } else { + super.userEventTriggered(ctx, evt); + } + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/ReceiverHandler.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/ReceiverHandler.java new file mode 100644 index 00000000..0dcf6753 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/ReceiverHandler.java @@ -0,0 +1,345 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.ReceiverHandler + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/12 上午11:14 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.jd.blockchain.stp.communication.MessageExecutor; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.connection.Connection; +import com.jd.blockchain.stp.communication.connection.listener.ReplyListener; +import com.jd.blockchain.stp.communication.manager.ConnectionManager; +import com.jd.blockchain.stp.communication.message.SessionMessage; +import com.jd.blockchain.stp.communication.message.TransferMessage; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.apache.commons.codec.binary.Hex; + +import java.io.Closeable; +import java.util.Map; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 接收者消息处理Handler + * @author shaozhuguang + * @create 2019/4/12 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class ReceiverHandler extends ChannelInboundHandlerAdapter implements Closeable { + + /** + * 队列的最大容量设置,默认为256K(防止队列溢出) + */ + private static final int QUEUE_CAPACITY = 256 * 1024; + + /** + * 远端RemoteSession信息集合 + * Key为SessionId + * Sender发送的消息中会携带SessionId + * ReceiverHandler会根据不同的SessionId采用不同的MessageExecutor处理策略 + */ + private final Map remoteSessions = new ConcurrentHashMap<>(); + + /** + * 监听器集合 + * 对应Sender在发送请求之前会设置ReplyListener + * Key为每个请求消息的Hash,用于描述消息的唯一性 + * 应答一方会在应答中加入对应的key,用于消息的映射 + */ + private final Map allReplyListeners = new ConcurrentHashMap<>(); + + /** + * session控制锁 + * 用于防止对统一RemoteSession对象进行重复设置 + */ + private final Lock sessionLock = new ReentrantLock(); + + /** + * 当前节点(本地节点)的消息处理器对应Class + * 该信息用于发送至其他节点,向其他节点通知遇到本节点请求时该如何处理 + */ + private String localMsgExecutorClass; + + /** + * 连接控制器,用于与远端节点连接 + */ + private ConnectionManager connectionManager; + + /** + * 消息处理执行线程池 + * 防止执行内容过长,导致阻塞 + */ + private ExecutorService msgExecutorPool; + + /** + * 默认消息处理器 + * 当对应session获取到的RemoteSession中没有获取到指定MessageExecutor时,短时间内由其进行处理 + */ + private MessageExecutor defaultMessageExecutor; + + /** + * 本地节点 + */ + private LocalNode localNode; + + public ReceiverHandler(ConnectionManager connectionManager, String localMsgExecutorClass, + LocalNode localNode) { + this.connectionManager = connectionManager; + this.localMsgExecutorClass = localMsgExecutorClass; + this.defaultMessageExecutor = localNode.defaultMessageExecutor(); + this.localNode = localNode; + initMsgExecutorPool(); + } + + public void putRemoteSession(String sessionId, RemoteSession remoteSession) { + remoteSessions.put(sessionId, remoteSession); + } + + public void addListener(ReplyListener replyListener) { + allReplyListeners.put(replyListener.listenKey(), replyListener); + } + + public void removeListener(String key) { + this.allReplyListeners.remove(key); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + + System.out.printf("%s Receive Biz Message -> %s \r\n", this.localNode.toString(), msg.toString()); + // 有数据接入 + // 首先判断数据是否TransferMessage,当前Handler不处理非TransferMessage + TransferMessage tm = TransferMessage.toTransferMessage(msg); + if (tm == null) { + // 判断是否是SessionMessage + SessionMessage sm = SessionMessage.toSessionMessage(msg); + if (sm != null) { + executeSessionMessage(sm); + } else { + super.channelRead(ctx, msg); + } + } else { + TransferMessage.MESSAGE_TYPE messageType = TransferMessage.MESSAGE_TYPE.valueOf(tm.getType()); + // 对于请求和应答处理方式不同 + if (messageType.equals(TransferMessage.MESSAGE_TYPE.TYPE_REQUEST)) { + // 假设是请求消息 + executeRequest(tm); + } else if (messageType.equals(TransferMessage.MESSAGE_TYPE.TYPE_RESPONSE)) { + // 假设是应答消息 + executeResponse(tm); + } else { + // todo 其他消息只需要打印日志即可 + + + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.close(); + } + + /** + * 处理请求消息 + * + * @param transferMessage + * 接收到的请求消息 + */ + private void executeRequest(final TransferMessage transferMessage) { + msgExecutorPool.execute(() -> { + RemoteSession remoteSession = remoteSessions.get(transferMessage.getSessionId()); + if (remoteSession != null) { + MessageExecutor messageExecutor = remoteSession.messageExecutor(); + if (messageExecutor == null) { + // 采用默认处理器进行处理 + messageExecutor = defaultMessageExecutor; + } + MessageExecutor.REPLY replyType = messageExecutor.replyType(); + if (replyType != null) { + switch (replyType) { + case MANUAL: + messageExecutor.receive(transferMessage.loadKey(), transferMessage.load(), remoteSession); + break; + case AUTO: + String requestKey = transferMessage.loadKey(); + byte[] replyMsg = messageExecutor.receive(requestKey, transferMessage.load(), remoteSession); + // 应答 + remoteSession.reply(requestKey, () -> replyMsg); + break; + default: + break; + } + } + } + }); + } + + /** + * 处理应答消息 + * @param transferMessage + * 接收到的应答消息 + */ + private void executeResponse(final TransferMessage transferMessage) { + msgExecutorPool.execute(() -> { + // listenKey和msgKey是不一致的 + // msgKey是对消息本身设置key,listenKey是对整个消息(包括session信息) + String listenKey = transferMessage.toListenKey(); + + ReplyListener replyListener = allReplyListeners.get(listenKey); + + if (replyListener != null) { + // 填充对应的结果 + replyListener.replyData(transferMessage.load()); + + ReplyListener.MANAGE_TYPE manageType = replyListener.manageType(); + + if (manageType != null) { + switch (manageType) { + case REMOVE: + // 将对象从Map中移除 + removeListener(listenKey); + break; + case HOLD: + default: + // todo 打印日志 + + break; + } + } + } + }); + } + + /** + * 处理SessionMessage + * @param sessionMessage + * 描述Session的消息对象 + */ + private void executeSessionMessage(SessionMessage sessionMessage) { + // 处理SessionMessage + String sessionId = sessionMessage.sessionId(); + if (sessionId != null) { + // 对于含有的RemoteSession的Map,需要判断其MessageExecutor是否为NULL + RemoteSession remoteSession = remoteSessions.get(sessionId); + if (remoteSession == null) { + try { + sessionLock.lock(); + // 生成对应的MessageExecute对象 + String meClass = sessionMessage.getMessageExecutor(); + MessageExecutor messageExecutor = initMessageExecutor(meClass); + + // 说明尚未和请求来的客户端建立连接,需要建立连接 + Connection remoteConnection = this.connectionManager.connect(new RemoteNode( + sessionMessage.getLocalHost(), sessionMessage.getListenPort()), + this.localMsgExecutorClass); + // 假设连接失败的话,返回的Connection对象为null,此时不放入Map,等后续再处理 + if (remoteConnection != null) { + + remoteSession = new RemoteSession(this.localId(), remoteConnection, messageExecutor); + + // Double check !!! + if (!remoteSessions.containsKey(sessionId)) { + this.putRemoteSession(sessionId, remoteSession); + } + } + } finally { + sessionLock.unlock(); + } + } else { + // 需要判断MessageExecutor + MessageExecutor me = remoteSession.messageExecutor(); + if (me == null) { + try { + sessionLock.lock(); + // Double Check !!! + if (remoteSession.messageExecutor() == null) { + // 表明上次存储的MessageExecutor未创建成功,本次进行更新 + String meClass = sessionMessage.getMessageExecutor(); + MessageExecutor messageExecutor = initMessageExecutor(meClass); + + // 防止NULL将其他的进行覆盖 + if (messageExecutor != null) { + remoteSession.initExecutor(messageExecutor); + } + } + } finally { + sessionLock.unlock(); + } + } + } + } + } + + /** + * 初始化消息执行器 + * 根据消息执行器的Class字符串生成对应的消息处理对象 + * @param messageExecutorClass + * 消息执行器的Class字符串 + * @return + * 对应的消息处理对象,产生任何异常都返回NULL + */ + private MessageExecutor initMessageExecutor(String messageExecutorClass) { + // 生成对应的MessageExecute对象 + MessageExecutor messageExecutor = null; + if (messageExecutorClass != null && messageExecutorClass.length() > 0) { + try { + Class clazz = Class.forName(messageExecutorClass); + messageExecutor = (MessageExecutor) clazz.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + return messageExecutor; + } + + /** + * 初始化消息处理线程池 + */ + private void initMsgExecutorPool() { + + ThreadFactory msgExecuteThreadFactory = new ThreadFactoryBuilder() + .setNameFormat("msg-executor-pool-%d").build(); + + //Common Thread Pool + msgExecutorPool = new ThreadPoolExecutor(5, 10, + 60, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(QUEUE_CAPACITY), + msgExecuteThreadFactory, new ThreadPoolExecutor.AbortPolicy()); + } + + /** + * 返回本地节点 + * + * @return + */ + public LocalNode localNode() { + return localNode; + } + + /** + * 返回本地节点ID + * + * @return + */ + private String localId() { + return Hex.encodeHexString(localNode.toString().getBytes()); + } + + @Override + public void close() { + msgExecutorPool.shutdown(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/SenderHandler.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/SenderHandler.java new file mode 100644 index 00000000..ed763434 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/SenderHandler.java @@ -0,0 +1,67 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.handler.SenderHandler + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/16 下午2:00 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.jd.blockchain.stp.communication.message.SessionMessage; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + + +/** + * Sender对应Handler + * @author shaozhuguang + * @create 2019/4/16 + * @since 1.0.0 + */ +@ChannelHandler.Sharable +public class SenderHandler extends ChannelInboundHandlerAdapter { + + /** + * 本地session信息 + */ + private SessionMessage sessionMessage; + + /** + * 本地节点 + */ + private LocalNode localNode; + + /** + * 远端节点 + */ + private RemoteNode remoteNode; + + public SenderHandler(LocalNode localNode, RemoteNode remoteNode, SessionMessage sessionMessage) { + this.localNode = localNode; + this.remoteNode = remoteNode; + this.sessionMessage = sessionMessage; + } + + /** + * 连接远端节点成功时触发 + * + * @param ctx + * @throws Exception + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + + // 发送本机信息(包括IP、端口等)至对端 + System.out.printf("%s Connect %s Success, Send Local Node Information !!! \r\n", this.localNode, this.remoteNode); + ctx.writeAndFlush(sessionMessage.toTransferByteBuf()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.close(); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/WatchDogHandler.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/WatchDogHandler.java new file mode 100644 index 00000000..bdcb82de --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/handler/WatchDogHandler.java @@ -0,0 +1,267 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.SenderWatchDog + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/12 下午4:56 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.handler; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.jd.blockchain.stp.communication.message.HeartBeatMessage; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.timeout.IdleStateHandler; + +import java.io.Closeable; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 连接监听器 + * @author shaozhuguang + * @create 2019/4/12 + * @since 1.0.0 + * @date 2019-04-19 09:21 + */ +@ChannelHandler.Sharable +public class WatchDogHandler extends ChannelInboundHandlerAdapter implements Runnable, Closeable { + + /** + * 当前连接活跃状态 + */ + private final AtomicBoolean currentActive = new AtomicBoolean(false); + + /** + * 重连的控制锁 + * 防止重连过程中重复多次调用 + */ + private final Lock reconnectLock = new ReentrantLock(); + + /** + * 默认的最多重连次数 + */ + private final int maxReconnectSize = 16; + + /** + * 默认重连的时间,下次重连时间会变长 + */ + private final int defaultReconnectSeconds = 2; + + /** + * 标识是否正常工作中,假设不再工作则不再重连 + */ + private boolean isWorking = true; + + /** + * 重连调度器 + */ + private ScheduledExecutorService reconnectTimer; + + /** + * 远端的IP(域名)信息 + */ + private String hostName; + + /** + * 远端的端口 + */ + private int port; + + private Bootstrap bootstrap; + + /** + * 第一组Handler数组 + */ + private ChannelHandler[] frontHandlers; + + /** + * 后一组Handler数组 + */ + private ChannelHandler[] afterHandlers; + + /** + * 用于重连时对象重置 + */ + private ChannelFuture channelFuture; + + /** + * 构造器 + * @param hostName + * 远端Host + * @param port + * 远端端口 + * @param bootstrap + * Netty工作启动器 + */ + public WatchDogHandler(String hostName, int port, Bootstrap bootstrap) { + this.hostName = hostName; + this.port = port; + this.bootstrap = bootstrap; + } + + /** + * 构造器 + * @param remoteNode + * 远端节点 + * @param bootstrap + * Netty工作启动器 + */ + public WatchDogHandler(RemoteNode remoteNode, Bootstrap bootstrap) { + this(remoteNode.getHostName(), remoteNode.getPort(), bootstrap); + } + + /** + * 配置重连需要的Handler + * 主要是为了对象的复用,同时有些Handler无法复用,对于每次连接请求必须要new新的对象 + * @param frontHandlers + * @param afterHandlers + */ + public void init(ChannelHandler[] frontHandlers, ChannelHandler[] afterHandlers) { + this.frontHandlers = frontHandlers; + this.afterHandlers = afterHandlers; + initTimer(); + } + + /** + * 初始化ChannelFuture + * + * @param channelFuture + */ + public void initChannelFuture(ChannelFuture channelFuture) { + this.channelFuture = channelFuture; + } + + /** + * 返回ChannelFuture + * + * @return + * 该返回对象目前未处理是否连接成功的情况 + * 调用者可直接使用,但假设发送不成功的话会存在异常抛出 + * 调用者可手动处理异常 + */ + public ChannelFuture channelFuture() { + try { + // 使用锁防止在重连进行过程中互相竞争 + // 一定是等待本次重连完成才返回 + reconnectLock.lock(); + return this.channelFuture; + } finally { + reconnectLock.unlock(); + } + } + + /** + * 连接成功调用 + * 该连接成功表示完全连接成功,对于TCP而言就是三次握手成功 + * @param ctx + * @throws Exception + */ + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // 调用该方法表示连接成功 + connectSuccess(); + + // 连接成功后发送心跳消息至服务端 + HeartBeatMessage.write(ctx); + + ctx.fireChannelActive(); + } + + /** + * 连接失败时调用 + * 此处是触发重连的入口 + * @param ctx + * @throws Exception + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + + System.err.println("Connection Exception, Close And Reconnect !!!"); + // 调用该方法时表示连接关闭了(无论是什么原因) + // 连接关闭的情况下需要重新连接 + + connectFail(); + + ctx.close(); + + for (int i = 0; i < maxReconnectSize; i++) { + reconnectTimer.schedule(this, defaultReconnectSeconds << i, TimeUnit.SECONDS); + } + + ctx.fireChannelInactive(); + } + + @Override + public void run() { + if (isNeedReconnect()) { + // 重连 + try { + reconnectLock.lock(); + if (isNeedReconnect()) { + + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline() + .addLast(frontHandlers) + .addLast(new IdleStateHandler(10, 4, 0, TimeUnit.SECONDS)) + .addLast(new LineBasedFrameDecoder(1024)) + .addLast(afterHandlers) + ; + } + }); + + channelFuture = bootstrap.connect(hostName, port); + + // 增加监听器用于判断本次重连是否成功 + channelFuture.addListener((ChannelFutureListener) future -> { + boolean isReconnectSuccess = future.isSuccess(); + if (isReconnectSuccess) { + // 连接成功 + connectSuccess(); + } else { + connectFail(); + } + }); + + } + } finally { + reconnectLock.unlock(); + } + } + } + + private boolean isNeedReconnect() { + return isWorking && !currentActive.get(); + } + + private void connectSuccess() { + this.currentActive.set(true); + } + + private void connectFail() { + this.currentActive.set(false); + } + + @Override + public void close() { + this.isWorking = false; + this.reconnectTimer.shutdown(); + } + + /** + * 设置调度器 + */ + private void initTimer() { + ThreadFactory timerFactory = new ThreadFactoryBuilder() + .setNameFormat("reconnect-pool-%d").build(); + + reconnectTimer = new ScheduledThreadPoolExecutor(1, timerFactory, new ThreadPoolExecutor.AbortPolicy()); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/listener/ReplyListener.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/listener/ReplyListener.java new file mode 100644 index 00000000..ac9221d0 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/connection/listener/ReplyListener.java @@ -0,0 +1,87 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.connection.listener.ReplyListener + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/12 上午10:36 + * Description: + */ +package com.jd.blockchain.stp.communication.connection.listener; + +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.node.RemoteNode; + + +/** + * 应答监听器 + * @author shaozhuguang + * @create 2019/4/12 + * @since 1.0.0 + */ + +public class ReplyListener { + + /** + * 监听的Key,通常用于描述唯一的请求 + */ + private String listenKey; + + /** + * 消息处理类型 + * REMOVE:表示处理完该对象之后从缓存中清除 + * HOLD:表示处理完该对象之后仍在缓存中保存 + */ + private MANAGE_TYPE manageType = MANAGE_TYPE.REMOVE; + + /** + * 数据回调监听器 + */ + private CallBackDataListener callBackDataListener; + + /** + * 回调栅栏 + */ + private CallBackBarrier callBackBarrier; + + public ReplyListener(String listenKey, RemoteNode remoteNode) { + this(listenKey, remoteNode, null); + } + + public ReplyListener(String listenKey, RemoteNode remoteNode, CallBackBarrier callBackBarrier) { + this.listenKey = listenKey; + this.callBackDataListener = new CallBackDataListener(remoteNode); + this.callBackBarrier = callBackBarrier; + } + + public void setManageType(MANAGE_TYPE manageType) { + this.manageType = manageType; + } + + public String listenKey() { + return listenKey; + } + + public CallBackDataListener callBackDataListener() { + return this.callBackDataListener; + } + + public void replyData(byte[] reply) { + // 设置数据 + this.callBackDataListener.setCallBackData(reply); + if (this.callBackBarrier != null) { + // 同步释放对应的栅栏 + this.callBackBarrier.release(); + } + } + + public MANAGE_TYPE manageType() { + return this.manageType; + } + + public enum MANAGE_TYPE { + HOLD, + REMOVE, + ; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/ConnectionManager.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/ConnectionManager.java new file mode 100644 index 00000000..c8336284 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/ConnectionManager.java @@ -0,0 +1,175 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.ConnectionManager + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 下午6:11 + * Description: + */ +package com.jd.blockchain.stp.communication.manager; + +import com.jd.blockchain.stp.communication.callback.CallBackLauncher; +import com.jd.blockchain.stp.communication.connection.Receiver; +import com.jd.blockchain.stp.communication.connection.Connection; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 连接管理器 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + * @date 2019-04-18 15:11 + */ + +public class ConnectionManager { + + /** + * Connection对应Map + * RemoteNode唯一性:IP(HOST)+PORT + */ + private final Map connectionMap = new ConcurrentHashMap<>(); + + /** + * 连接管理器对应MAP + * 以监听端口(int)作为Key,进行唯一性约束 + */ + private static final Map connectionManagerMap = new ConcurrentHashMap<>(); + + /** + * connectionManagerMap控制锁 + */ + private static final Lock managerLock = new ReentrantLock(); + + /** + * connectionMap控制锁 + */ + private static final Lock connectionLock = new ReentrantLock(); + + /** + * 当前ConnectionManager对应的Receiver + */ + private Receiver receiver; + + /** + * 静态ConnectionManager构造器 + * + * @param localNode + * 本地节点 + * @return + * 优先返回Map中的对象 + */ + public static final ConnectionManager newConnectionManager(LocalNode localNode) { + int listenPort = localNode.getPort(); + if (!connectionManagerMap.containsKey(listenPort)) { + try { + managerLock.lock(); + if (!connectionManagerMap.containsKey(listenPort)) { + ConnectionManager connectionManager = newInstance(localNode); + connectionManagerMap.put(listenPort, connectionManager); + return connectionManager; + } + } finally { + managerLock.unlock(); + } + } + return connectionManagerMap.get(listenPort); + } + + /** + * 内部调用的静态构造器 + * + * @param localNode + * 本地节点 + * @return + */ + private static final ConnectionManager newInstance(LocalNode localNode) { + return new ConnectionManager(new Receiver(localNode)); + } + + /** + * 启动 + * 该启动是启动Receiver,返回启动的状态 + * + * @param messageExecutorClass + * 当前节点希望其他节点收到该节点信息时的处理Handler + * @return + * 回调执行器 + * @throws InterruptedException + */ + public final CallBackLauncher start(String messageExecutorClass) throws InterruptedException { + receiver.initReceiverHandler(this, messageExecutorClass); + receiver.startListen(); + // 判断是否启动完成,启动完成后再返回 + return receiver.waitBooted(); + } + + private ConnectionManager(Receiver receiver) { + this.receiver = receiver; + } + + /** + * 连接远端节点 + * + * @param remoteNode + * 远端节点信息 + * @param messageExecutorClass + * 希望远端节点接收到本节点消息时的处理Handler + * @return + */ + public Connection connect(RemoteNode remoteNode, String messageExecutorClass) { + if (!connectionMap.containsKey(remoteNode)) { + try { + connectionLock.lock(); + if (!connectionMap.containsKey(remoteNode)) { + Connection connection = init(remoteNode, messageExecutorClass); + if (connection != null) { + // 保证都是连接成功的 + connectionMap.put(remoteNode, connection); + return connection; + } else { + // 连接失败返回null + return null; + } + } + } finally { + connectionLock.unlock(); + } + } + return connectionMap.get(remoteNode); + } + + /** + * 关闭Receiver + * + */ + public void closeReceiver() { + this.receiver.close(); + } + + private Connection init(RemoteNode remoteNode, String messageExecutorClass) { + + // 初始化Connection + Connection remoteConnection = new Connection(this.receiver); + + try { + // 连接远端,需要发送当前节点处理的MessageExecuteClass + CallBackLauncher callBackLauncher = remoteConnection.connect(remoteNode, messageExecutorClass); + if (!callBackLauncher.isBootSuccess()) { + // TODO 打印错误日志 + callBackLauncher.exception().printStackTrace(); + return null; + } + return remoteConnection; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (RuntimeException e) { + throw e; + } + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/RemoteSessionManager.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/RemoteSessionManager.java new file mode 100644 index 00000000..1086fb33 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/manager/RemoteSessionManager.java @@ -0,0 +1,180 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.RemoteSessionManager + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午11:22 + * Description: + */ +package com.jd.blockchain.stp.communication.manager; + + +import com.jd.blockchain.stp.communication.MessageExecutor; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackLauncher; +import com.jd.blockchain.stp.communication.connection.Connection; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import org.apache.commons.codec.binary.Hex; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * 远端Session管理器 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public class RemoteSessionManager { + + /** + * 可监听的最大端口 + */ + private static final int MAX_PORT = 65535; + + /** + * 节点Session的集合信息 + */ + private Map nodeRemoteSessionMap = new ConcurrentHashMap<>(); + + /** + * nodeRemoteSessionMap的控制锁 + */ + private Lock lock = new ReentrantLock(); + + /** + * 连接管理器 + * 用于管理底层的通信连接 + */ + private ConnectionManager connectionManager; + + /** + * 本地节点信息 + */ + private LocalNode localNode; + + /** + * 本地节点ID + */ + private String localId; + + /** + * 构造器 + * @param localNode + * 本地节点信息 + */ + public RemoteSessionManager(LocalNode localNode) { + this.localNode = localNode; + this.localId = localId(); + // 校验本地节点的配置,防止异常 + check(); + this.connectionManager = ConnectionManager.newConnectionManager(this.localNode); + try { + CallBackLauncher callBackLauncher = start(); + if (!callBackLauncher.isBootSuccess()) { + // 启动当前端口连接必须要成功,否则则退出,交由应用程序处理 + throw new RuntimeException(callBackLauncher.exception()); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (RuntimeException e) { + throw e; + } + } + + /** + * RemoteSession对象生成器 + * @param remoteNode + * 远端节点信息 + * @return + */ + public RemoteSession newSession(RemoteNode remoteNode) { + + RemoteSession remoteSession = nodeRemoteSessionMap.get(remoteNode); + + if (remoteSession != null) { + return remoteSession; + } else { + try { + lock.lock(); + + // Double Check !!! + if (!nodeRemoteSessionMap.containsKey(remoteNode)) { + Connection remoteConnection = this.connectionManager.connect(remoteNode, localNode.messageExecutorClass()); + + if (remoteConnection == null) { + return null; + } + + remoteSession = new RemoteSession(localId, remoteConnection); + + remoteSession.init(); + + nodeRemoteSessionMap.put(remoteNode, remoteSession); + + return remoteSession; + } + } finally { + lock.unlock(); + } + } + return null; + } + + public RemoteSession[] newSessions(RemoteNode[] remoteNodes) { + List remoteSessionList = new ArrayList<>(); + + for (int i = 0; i < remoteNodes.length; i++) { + RemoteSession remoteSession = newSession(remoteNodes[i]); + if (remoteSession != null) { + remoteSessionList.add(remoteSession); + } + } + + if (remoteSessionList.isEmpty()) { + return null; + } + + RemoteSession[] remoteSessions = new RemoteSession[remoteSessionList.size()]; + + return remoteSessionList.toArray(remoteSessions); + } + + /** + * 返回底层通信管理器 + * + * @return + */ + public ConnectionManager connectionManager() { + return this.connectionManager; + } + + private void check() { + // 要求端口范围:1~65535,messageExecuteClass不能为null + int listenPort = this.localNode.getPort(); + if (listenPort <= 0 || listenPort > MAX_PORT) { + throw new IllegalArgumentException("Illegal Local Listen Port, Please Check !!!"); + } + + // 默认处理器必须包含,可不包含本机需要对端知晓的处理器 + MessageExecutor defaultMessageExecutor = this.localNode.defaultMessageExecutor(); + if (defaultMessageExecutor == null) { + throw new IllegalArgumentException("Illegal Default MessageExecutor, Please Check !!!"); + } + } + + private CallBackLauncher start() throws InterruptedException { + return this.connectionManager.start(this.localNode.messageExecutorClass()); + } + + private String localId() { + return Hex.encodeHexString(localNode.toString().getBytes()); + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/AbstractMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/AbstractMessage.java new file mode 100644 index 00000000..a20ef623 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/AbstractMessage.java @@ -0,0 +1,30 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.AbstractMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/17 下午4:00 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 抽象消息 + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + */ + +public abstract class AbstractMessage implements IMessage { + + @Override + public ByteBuf toTransferByteBuf() { + byte[] message = (toTransfer() + "\r\n").getBytes(); + ByteBuf byteBuf = Unpooled.buffer(message.length); + byteBuf.writeBytes(message); + return byteBuf; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/HeartBeatMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/HeartBeatMessage.java new file mode 100644 index 00000000..eba5e4b6 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/HeartBeatMessage.java @@ -0,0 +1,76 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.HeartBeatMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/12 下午4:55 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.CharsetUtil; + +/** + * 心跳消息 + * @author shaozhuguang + * @create 2019/4/12 + * @since 1.0.0 + */ + +public class HeartBeatMessage implements IMessage { + + /** + * 统一的心跳信息字符串 + */ + private static final String HEARTBEAT_STRING = "JDChainHeartBeat"; + + /** + * 统一的心跳消息字符串对一个的ByteBuf + */ + private static final ByteBuf HEARTBEAT_MESSAGE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer(HEARTBEAT_STRING + "\r\n", + CharsetUtil.UTF_8)); + + /** + * 将心跳消息写入Ctx + * @param ctx + */ + public static final void write(ChannelHandlerContext ctx) { + ctx.writeAndFlush(HEARTBEAT_MESSAGE.duplicate()); + } + + /** + * 判断接收的消息是否为心跳消息 + * + * @param msg + * @return + */ + public static final boolean isHeartBeat(Object msg) { + return isHeartBeat(msg.toString()); + } + + /** + * 判断接收的消息是否为心跳消息 + * + * @param msg + * @return + */ + public static final boolean isHeartBeat(String msg) { + if (HEARTBEAT_STRING.equals(msg)) { + return true; + } + return false; + } + + @Override + public String toTransfer() { + return HEARTBEAT_STRING; + } + + @Override + public ByteBuf toTransferByteBuf() { + return HEARTBEAT_MESSAGE; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/IMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/IMessage.java new file mode 100644 index 00000000..c43e8d84 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/IMessage.java @@ -0,0 +1,33 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.IMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/16 下午1:58 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +import io.netty.buffer.ByteBuf; + +/** + * 消息接口 + * @author shaozhuguang + * @create 2019/4/16 + * @since 1.0.0 + */ + +public interface IMessage { + + /** + * 消息转换为字符串 + * @return + */ + String toTransfer(); + + /** + * 消息转换为ByteBuf + * @return + */ + ByteBuf toTransferByteBuf(); +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/LoadMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/LoadMessage.java new file mode 100644 index 00000000..b8971293 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/LoadMessage.java @@ -0,0 +1,26 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.LoadMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午10:59 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +/** + * 负载消息 + * 该接口用于应用实现 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public interface LoadMessage { + + /** + * 将负载消息转换为字节数组 + * @return + */ + byte[] toBytes(); +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/SessionMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/SessionMessage.java new file mode 100644 index 00000000..b3ce460b --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/SessionMessage.java @@ -0,0 +1,112 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.SessionMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/16 上午10:40 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +import org.apache.commons.codec.binary.Hex; + +/** + * Session消息 + * 该消息用于发送至远端节点,告诉远端节点本地的信息 + * @author shaozhuguang + * @create 2019/4/16 + * @since 1.0.0 + */ + +public class SessionMessage extends AbstractMessage implements IMessage { + + /** + * 本地节点HOST + */ + private String localHost; + + /** + * 本地节点监听端口 + */ + private int listenPort; + + /** + * 远端接收到本地节点信息时处理的Class + */ + private String messageExecutor; + + public SessionMessage() { + } + + public SessionMessage(String localHost, int listenPort, String messageExecutor) { + this.localHost = localHost; + this.listenPort = listenPort; + this.messageExecutor = messageExecutor; + } + + public String getLocalHost() { + return localHost; + } + + public void setLocalHost(String localHost) { + this.localHost = localHost; + } + + public void setListenPort(int listenPort) { + this.listenPort = listenPort; + } + + + public int getListenPort() { + return listenPort; + } + + public String getMessageExecutor() { + return messageExecutor; + } + + public void setMessageExecutor(String messageExecutor) { + this.messageExecutor = messageExecutor; + } + + public String sessionId() { + return Hex.encodeHexString((this.localHost + ":" + this.listenPort).getBytes()); + } + + /** + * 将对象(或者说接收到的消息)转换为SessionMessage + * @param msg + * 接收到的消息对象 + * @return + * 可正确解析则返回,否则返回NULL + */ + public static SessionMessage toSessionMessage(Object msg) { + String msgString = msg.toString(); + try { + String[] msgArray = msgString.split("\\|"); + if (msgArray.length == 2 || msgArray.length == 3) { + String host = msgArray[0]; + int port = Integer.parseInt(msgArray[1]); + String msgExecutorClass = null; + if (msgArray.length == 3) { + msgExecutorClass = msgArray[2]; + } + return new SessionMessage(host, port, msgExecutorClass); + } + return null; + } catch (Exception e) { + return null; + } + } + + @Override + public String toTransfer() { + // 为区别于TransferMessage的JSON格式,该处使用字符串连接处理 + // 格式:localHost|port|class + if (this.messageExecutor == null) { + return this.localHost + "|" + this.listenPort; + } else { + return this.localHost + "|" + this.listenPort + "|" + this.messageExecutor; + } + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/TransferMessage.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/TransferMessage.java new file mode 100644 index 00000000..fb918295 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/message/TransferMessage.java @@ -0,0 +1,182 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.message.TransferMessage + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 上午11:00 + * Description: + */ +package com.jd.blockchain.stp.communication.message; + +import com.alibaba.fastjson.JSON; +import org.apache.commons.codec.binary.Base64; + +/** + * 底层传输协议 + * + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public class TransferMessage extends AbstractMessage implements IMessage{ + + /** + * sessionId(描述节点信息) + */ + private String sessionId; + + /** + * 本次消息的类型 + * 0:请求; + * 1:应答; + */ + private int type; + + /** + * 消息的Key + */ + private String key; + + /** + * 消息载体的内容 + * 本内容不可被序列化 + */ + private transient byte[] load; + + /** + * 消息载体的内容->Base64转换 + */ + private String loadBase64; + + public TransferMessage() { + } + + public TransferMessage(String sessionId, int type, String key, byte[] load) { + this.sessionId = sessionId; + this.type = type; + this.key = key; + this.load = load; + } + + /** + * 转换为TransferMessage对象 + * + * @param msg + * @return + */ + public static TransferMessage toTransferMessage(Object msg) { + if (msg == null) { + return null; + } + TransferMessage tm; + try { + tm = JSON.parseObject(msg.toString(), TransferMessage.class); + tm.initLoad(); + } catch (Exception e) { + return null; + } + return tm; + } + + public byte[] load() { + return load; + } + + public void initLoad() { + if (loadBase64 != null && loadBase64.length() > 0) { + load = Base64.decodeBase64(loadBase64); + } + } + + public void initLoadBase64() { + if (load != null && load.length > 0) { + loadBase64 = Base64.encodeBase64String(load); + } + } + + @Override + public String toTransfer() { + // 使用JSON的方式发送 + // 初始化load的base64转换 + initLoadBase64(); + + // 将字符串转换为JSON + return JSON.toJSONString(this); + } + + /** + * 转换为监听的Key + * 该Key可描述为从远端发送来消息及其内容的唯一性 + * + * @return + */ + public String toListenKey() { + return key; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String loadKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public String getLoadBase64() { + return loadBase64; + } + + public void setLoadBase64(String loadBase64) { + this.loadBase64 = loadBase64; + } + + public enum MESSAGE_TYPE { + + TYPE_REQUEST(0), + + TYPE_RESPONSE(1); + + private int code; + + MESSAGE_TYPE(int code) { + this.code = code; + } + + public int code() { + return code; + } + + public static MESSAGE_TYPE valueOf(int code) { + switch (code) { + case 0: + return TYPE_REQUEST; + case 1: + return TYPE_RESPONSE; + + } + return null; + } + + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/LocalNode.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/LocalNode.java new file mode 100644 index 00000000..60b9b11b --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/LocalNode.java @@ -0,0 +1,88 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.node.LocalNode + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/16 下午3:12 + * Description: + */ +package com.jd.blockchain.stp.communication.node; + +import com.jd.blockchain.stp.communication.MessageExecutor; + +/** + * 本地节点 + * @author shaozhuguang + * @create 2019/4/16 + * @since 1.0.0 + * @date 2019-04-19 09:28 + */ + +public class LocalNode extends RemoteNode { + + /** + * 当前节点消息处理器 + * 该消息处理器用于描述远端节点收到当前节点的消息该如何处理 + * 通常该消息处理器会以字符串的形式发送至远端节点 + */ + private Class messageExecutorClass; + + /** + * 当前节点接收消息默认处理器 + * 与messageExecutor不同,该字段描述的是当前节点接收到其他节点信息时的默认处理器 + * 该参数硬性要求必须不能为空 + */ + private MessageExecutor defaultMessageExecutor; + + /** + * 构造器 + * @param hostName + * 当前节点Host,该Host必须是一种远端节点可访问的形式 + * @param port + * 当前节点监听端口 + * @param defaultMessageExecutor + * 当前节点接收到远端消息无法处理时的消息处理器 + * + */ + public LocalNode(String hostName, int port, MessageExecutor defaultMessageExecutor) { + this(hostName, port, null, defaultMessageExecutor); + } + + /** + * 构造器 + * @param hostName + * 当前节点Host,该Host必须是一种远端节点可访问的形式 + * @param port + * 当前节点监听端口 + * @param messageExecutorClass + * 当前节点期望远端节点接收到消息后的处理器 + * @param defaultMessageExecutor + * 当前节点接收到远端消息无法处理时的消息处理器 + * + */ + public LocalNode(String hostName, int port, Class messageExecutorClass, MessageExecutor defaultMessageExecutor) { + super(hostName, port); + this.messageExecutorClass = messageExecutorClass; + this.defaultMessageExecutor = defaultMessageExecutor; + } + + /** + * 返回消息执行器的类对应的字符串 + * 该返回值通常用于消息传递 + * @return + */ + public String messageExecutorClass() { + if (this.messageExecutorClass == null) { + return null; + } + return this.messageExecutorClass.getName(); + } + + /** + * 返回默认的消息处理器 + * @return + */ + public MessageExecutor defaultMessageExecutor() { + return this.defaultMessageExecutor; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/RemoteNode.java b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/RemoteNode.java new file mode 100644 index 00000000..89287380 --- /dev/null +++ b/source/stp/stp-communication/src/main/java/com/jd/blockchain/stp/communication/node/RemoteNode.java @@ -0,0 +1,79 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.communication.RemoteNode + * Author: shaozhuguang + * Department: Jingdong Digits Technology + * Date: 2019/4/11 下午3:40 + * Description: + */ +package com.jd.blockchain.stp.communication.node; + +/** + * 节点信息 + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + * @date 2019-04-19 09:28 + */ + +public class RemoteNode { + + /** + * 监听端口 + */ + private int port; + + /** + * 当前节点域名 + */ + private String hostName; + + public RemoteNode(String hostName, int port) { + this.port = port; + this.hostName = hostName; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + /** + * 通过hostName+port形式作为判断节点的唯一标识 + * @return + */ + @Override + public int hashCode() { + return (hostName + ":" + port).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof RemoteNode) { + RemoteNode other = (RemoteNode) obj; + if (this.hashCode() == other.hashCode()) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return this.hostName + ":" + this.port; + } +} \ No newline at end of file diff --git a/source/stp/stp-communication/src/test/java/com/jd/blockchain/SessionMessageTest.java b/source/stp/stp-communication/src/test/java/com/jd/blockchain/SessionMessageTest.java new file mode 100644 index 00000000..b3cee4ab --- /dev/null +++ b/source/stp/stp-communication/src/test/java/com/jd/blockchain/SessionMessageTest.java @@ -0,0 +1,37 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.SessionMessageTest + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/17 下午3:24 + * Description: + */ +package com.jd.blockchain; + +import com.jd.blockchain.stp.communication.message.SessionMessage; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + */ + +public class SessionMessageTest { + + + @Test + public void test() { + SessionMessage message = new SessionMessage("127.0.0.1", 9001, "com.jd.blockchain.StpTest.StpMessageExecute"); + + String transMsg = message.toTransfer(); + System.out.println(transMsg); + + SessionMessage sm = SessionMessage.toSessionMessage(transMsg); + + assertEquals(transMsg, sm.toTransfer()); + } +} \ No newline at end of file diff --git a/source/test/pom.xml b/source/test/pom.xml index 4df214ad..ac3b0562 100644 --- a/source/test/pom.xml +++ b/source/test/pom.xml @@ -15,6 +15,7 @@ test-consensus-node test-ledger-core test-integration + test-stp-community diff --git a/source/test/test-stp-community/pom.xml b/source/test/test-stp-community/pom.xml new file mode 100644 index 00000000..12d031b1 --- /dev/null +++ b/source/test/test-stp-community/pom.xml @@ -0,0 +1,36 @@ + + + + + test + com.jd.blockchain + 0.9.0-SNAPSHOT + + 4.0.0 + + test-stp-community + + test-stp-community + + + UTF-8 + 1.8 + 1.8 + + + + + + com.jd.blockchain + stp-communication + ${project.version} + + + + junit + junit + test + + + diff --git a/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/MyMessageExecutor.java b/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/MyMessageExecutor.java new file mode 100644 index 00000000..26656a8b --- /dev/null +++ b/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/MyMessageExecutor.java @@ -0,0 +1,37 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.stp.commucation.MyMessageExecutor + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/17 下午3:38 + * Description: + */ +package com.jd.blockchain.stp.commucation; + +import com.jd.blockchain.stp.communication.MessageExecutor; +import com.jd.blockchain.stp.communication.RemoteSession; + +import java.nio.charset.Charset; + +/** + * + * @author shaozhuguang + * @create 2019/4/17 + * @since 1.0.0 + */ + +public class MyMessageExecutor implements MessageExecutor { + + @Override + public byte[] receive(String key, byte[] data, RemoteSession session) { + String receiveMsg = new String(data, Charset.defaultCharset()); + System.out.printf("receive client {%s} request {%s} \r\n", session.remoteNode().toString(), receiveMsg); + String msg = session.localId() + " -> received !!!"; + return msg.getBytes(Charset.defaultCharset()); + } + + @Override + public REPLY replyType() { + return REPLY.AUTO; + } +} \ No newline at end of file diff --git a/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/StpReceiversBoot.java b/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/StpReceiversBoot.java new file mode 100644 index 00000000..e725d77b --- /dev/null +++ b/source/test/test-stp-community/src/main/java/com/jd/blockchain/stp/commucation/StpReceiversBoot.java @@ -0,0 +1,75 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.StpReceiversBoot + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/18 下午3:44 + * Description: + */ +package com.jd.blockchain.stp.commucation; + +import com.jd.blockchain.stp.communication.MessageExecutor; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import com.jd.blockchain.stp.communication.node.LocalNode; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * + * @author shaozhuguang + * @create 2019/4/18 + * @since 1.0.0 + */ + +public class StpReceiversBoot { + + private int[] listenPorts; + + private final String remoteHost = "127.0.0.1"; + + private ExecutorService threadPool; + + public StpReceiversBoot(int... ports) { + listenPorts = ports; + threadPool = Executors.newFixedThreadPool(ports.length + 2); + } + + public RemoteSessionManager[] start(MessageExecutor messageExecutor) { + + final int totalSessionSize = listenPorts.length; + + CountDownLatch countDownLatch = new CountDownLatch(totalSessionSize); + + RemoteSessionManager[] sessionManagers = new RemoteSessionManager[totalSessionSize]; + for (int i = 0; i < totalSessionSize; i++) { + final int port = listenPorts[i], index = i; + threadPool.execute(() -> { + // 创建本地节点 + final LocalNode localNode = new LocalNode(remoteHost, port, messageExecutor); + try { + // 启动当前节点 + RemoteSessionManager sessionManager = new RemoteSessionManager(localNode); + sessionManagers[index] = sessionManager; + System.out.printf("Current Node {%s} start success !!! \r\n", localNode.toString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + countDownLatch.countDown(); + } + }); + } + + // 等待所有节点启动完成 + try { + countDownLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + + return sessionManagers; + } + + +} \ No newline at end of file diff --git a/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpReceiversBootTest.java b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpReceiversBootTest.java new file mode 100644 index 00000000..1cd6e985 --- /dev/null +++ b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpReceiversBootTest.java @@ -0,0 +1,43 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.StpReceiversBootTest + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/18 下午3:53 + * Description: + */ +package com.jd.blockchain; + +import com.jd.blockchain.stp.commucation.MyMessageExecutor; +import com.jd.blockchain.stp.commucation.StpReceiversBoot; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import org.junit.Test; + +/** + * + * @author shaozhuguang + * @create 2019/4/18 + * @since 1.0.0 + */ + +public class StpReceiversBootTest { + + public static final int[] localPorts = new int[]{9900, 9901}; + + @Test + public void test() { + StpReceiversBoot stpReceiversBoot = new StpReceiversBoot(9900, 9901); + RemoteSessionManager[] sessionManagers = stpReceiversBoot.start(new MyMessageExecutor()); + + try { + Thread.sleep(10000); + + // 关闭所有的监听器 + for (RemoteSessionManager sessionManager : sessionManagers) { + sessionManager.connectionManager().closeReceiver(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpSenderTest.java b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpSenderTest.java new file mode 100644 index 00000000..29741ae0 --- /dev/null +++ b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpSenderTest.java @@ -0,0 +1,96 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.StpSenderTest + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/18 下午3:56 + * Description: + */ +package com.jd.blockchain; + +import com.jd.blockchain.stp.commucation.MyMessageExecutor; +import com.jd.blockchain.stp.commucation.StpReceiversBoot; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import com.jd.blockchain.stp.communication.message.LoadMessage; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import org.junit.Test; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * + * @author shaozhuguang + * @create 2019/4/18 + * @since 1.0.0 + */ + +public class StpSenderTest { + + // 本地的端口 + private static final int localPort = 9800; + + // 连接的远端端口集合 + private static final int[] remotePorts = StpReceiversBootTest.localPorts; + + // 本地节点信息 + private static final String localHost = "127.0.0.1"; + + @Test + public void test() { + // 首先启动本地节点 + StpReceiversBoot stpReceiversBoot = new StpReceiversBoot(localPort); + RemoteSessionManager[] sessionManagers = stpReceiversBoot.start(new MyMessageExecutor()); + + // 本地节点启动完成后 + if (sessionManagers != null && sessionManagers.length > 0) { + RemoteSessionManager localSessionManager = sessionManagers[0]; + + // 连接远端的两个节点 + RemoteNode[] remoteNodes = new RemoteNode[]{ + new RemoteNode(localHost, remotePorts[0]), + new RemoteNode(localHost, remotePorts[1]) + }; + + RemoteSession[] remoteSessions = localSessionManager.newSessions(remoteNodes); + + // 生成请求对象 + LoadMessage loadMessage = new StpTest.StpLoadMessage(localHost + ":" + localPort); + + // 异步发送处理过程 + CallBackBarrier callBackBarrier = CallBackBarrier.newCallBackBarrier(remoteSessions.length, 10000); + + // 发送请求至remotes + LinkedList responses = new LinkedList<>(); + for (RemoteSession remoteSession : remoteSessions) { + CallBackDataListener response = remoteSession.asyncRequest(loadMessage, callBackBarrier); + responses.addLast(response); + } + + // 超时判断 + try { + if (callBackBarrier.tryCall()) { + + // 说明结果已经全部返回 + // 打印出所有的结果 + // 通过迭代器遍历链表 + Iterator iterator = responses.iterator(); + while (iterator.hasNext()) { + CallBackDataListener response = iterator.next(); + // 判断是否已完成,对于没有完成的直接放弃(因为已经超时) + if (response.isDone()) { + System.out.printf("Receive Response {%s} {%s} \r\n", + response.remoteNode().toString(), new String(response.getCallBackData())); + } + } + } + Thread.sleep(Integer.MAX_VALUE); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpTest.java b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpTest.java new file mode 100644 index 00000000..2e66c8e1 --- /dev/null +++ b/source/test/test-stp-community/src/test/java/com/jd/blockchain/StpTest.java @@ -0,0 +1,206 @@ +/** + * Copyright: Copyright 2016-2020 JD.COM All Right Reserved + * FileName: com.jd.blockchain.StpTest + * Author: shaozhuguang + * Department: Y事业部 + * Date: 2019/4/11 下午3:31 + * Description: + */ +package com.jd.blockchain; + +import com.jd.blockchain.stp.commucation.MyMessageExecutor; +import com.jd.blockchain.stp.communication.RemoteSession; +import com.jd.blockchain.stp.communication.callback.CallBackBarrier; +import com.jd.blockchain.stp.communication.callback.CallBackDataListener; +import com.jd.blockchain.stp.communication.manager.RemoteSessionManager; +import com.jd.blockchain.stp.communication.message.LoadMessage; +import com.jd.blockchain.stp.communication.node.LocalNode; +import com.jd.blockchain.stp.communication.node.RemoteNode; +import org.junit.Before; +import org.junit.Test; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertNull; + +/** + * + * @author shaozhuguang + * @create 2019/4/11 + * @since 1.0.0 + */ + +public class StpTest { + + private int maxWaitTime = 2000; + + private final String remoteHost = "127.0.0.1"; + + private final int localPort = 9001; + + private final int[] listenPorts = new int[]{9001, 9002, 9003, 9004}; + + private final RemoteSessionManager[] sessionManagers = new RemoteSessionManager[listenPorts.length]; + + private final ExecutorService threadPool = Executors.newFixedThreadPool(6); + + private RemoteSession[] remoteSessions; + + @Before + public void init() { + + System.out.println("---------- listenStart -----------"); + listenStart(); + System.out.println("---------- listenComplete -----------"); + System.out.println("---------- ConnectionStart ----------"); + connectOneOther(); + System.out.println("---------- ConnectionComplete ----------"); + } + + private void listenStart() { + CountDownLatch countDownLatch = new CountDownLatch(listenPorts.length); + + for (int i = 0; i < listenPorts.length; i++) { + final int port = listenPorts[i], index = i; + threadPool.execute(() -> { + // 创建本地节点 + final LocalNode localNode = new LocalNode(remoteHost, port, new MyMessageExecutor()); + try { + // 启动当前节点 + RemoteSessionManager sessionManager = new RemoteSessionManager(localNode); + sessionManagers[index] = sessionManager; + System.out.printf("Current Node {%s} start success !!! \r\n", localNode.toString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + countDownLatch.countDown(); + } + }); + } + + // 等待所有节点启动完成 + try { + countDownLatch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void connectAllOthers() { + // 所有节点完成之后,需要启动 + // 启动一个节点 + RemoteSessionManager starter = sessionManagers[0]; + + // 当前节点需要连接到其他3个节点 + RemoteNode[] remoteNodes = new RemoteNode[listenPorts.length - 1]; + int index = 0; + for (int port : listenPorts) { + if (port != localPort) { + remoteNodes[index++] = new RemoteNode(remoteHost, port); + } + } + + remoteSessions = starter.newSessions(remoteNodes); + } + + private void connectOneOther() { + // 所有节点完成之后,需要启动 + // 启动一个节点 + RemoteSessionManager starter = sessionManagers[0]; + + // 当前节点需要连接到其他3个节点 + RemoteNode[] remoteNodes = new RemoteNode[1]; + int index = 0; + for (int port : listenPorts) { + if (port != localPort && index < 1) { + remoteNodes[index++] = new RemoteNode(remoteHost, port); + } + } + + remoteSessions = starter.newSessions(remoteNodes); + } + + private void connectOneErrorNode() { + // 所有节点完成之后,需要启动 + // 启动一个节点 + RemoteSessionManager starter = sessionManagers[0]; + + // 当前节点需要连接到其他3个节点 + RemoteNode[] remoteNodes = new RemoteNode[1]; + + remoteNodes[0] = new RemoteNode(remoteHost, 10001); + + remoteSessions = starter.newSessions(remoteNodes); + + assertNull(remoteSessions); + } + + + @Test + public void test() { + + try { + Thread.sleep(3000); + + } catch (Exception e) { + e.printStackTrace(); + } + + // 生成请求对象 + LoadMessage loadMessage = new StpLoadMessage(remoteHost + ":" + localPort); + + // 异步发送处理过程 + CallBackBarrier callBackBarrier = CallBackBarrier.newCallBackBarrier(remoteSessions.length, 10000); + + // 发送请求至remotes + LinkedList responses = new LinkedList<>(); + for (RemoteSession remoteSession : remoteSessions) { + CallBackDataListener response = remoteSession.asyncRequest(loadMessage, callBackBarrier); + responses.addLast(response); + } + + // 超时判断 + try { + if (callBackBarrier.tryCall()) { + + // 说明结果已经全部返回 + // 打印出所有的结果 + // 通过迭代器遍历链表 + Iterator iterator = responses.iterator(); + while (iterator.hasNext()) { + CallBackDataListener response = iterator.next(); + // 判断是否已完成,对于没有完成的直接放弃(因为已经超时) + if (response.isDone()) { + System.out.printf("Receive Response {%s} {%s} \r\n", + response.remoteNode().toString(), new String(response.getCallBackData())); + } + } + } + Thread.sleep(Integer.MAX_VALUE); + } catch (Exception e) { + e.printStackTrace(); + } + + + } + + public static class StpLoadMessage implements LoadMessage { + + private String localInfo; + + public StpLoadMessage(String localInfo) { + this.localInfo = localInfo; + } + + @Override + public byte[] toBytes() { + String msg = localInfo + " -> Send !!!"; + return msg.getBytes(Charset.defaultCharset()); + } + } +} \ No newline at end of file