网络篇 - rpc协议的应用web3j
?
最近的公司區(qū)塊鏈錢包,用到了以太坊的官方開發(fā)庫 web3j。web3j?是Java版本的以太坊 rpc-json?接口協(xié)議封裝實現(xiàn),如果需要將你的 Java 或安卓應用接入以太坊,或者希望用 Java 開發(fā)一個錢包應用,那么 web3j 完全能滿足你的需求。
?
目錄:
?
?
1.?rpc 簡介
rpc (?Remote Procedure Call)?— 遠程過程調用,它是一種通過網(wǎng)絡從遠程計算機程序上請求服務,而不需要了解底層網(wǎng)絡技術的協(xié)議。rpc 協(xié)議假定某些傳輸協(xié)議的存在,如 tcp?或 udp,為通信程序之間傳輸信息數(shù)據(jù)。在 OSI 網(wǎng)絡通信模型中,rpc?跨越了傳輸層和應用層。rpc 使得開發(fā)包括網(wǎng)絡分布式多程序在內的應用程序更加容易。
我從 google 上找了一張 rpc 模型圖,幫助大家理解下:
?
?
?
2.?rpc 和 http 對比
目前有很多 Java 的 rpc?框架,有基于 Json 的,有基于 XML,也有基于二進制對象的。rpc 和 平常用的 http/https 到底有什么區(qū)別,不都是寫一個服務然后在客戶端調用嗎?
在 http 和 rpc?的選擇上可能有些人是迷惑的,主要是因為,有些 rpc?框架配置復雜,如果走 http 也能完成同樣的功能,那么為什么要選擇 rpc,而不是更容易上手的 http 來實現(xiàn)?
從以下幾個點來分析:
(1)?傳輸協(xié)議
- rpc?可以基于 tcp 協(xié)議 (可以省去了 http 報頭等一系列東西,rpc 并沒有規(guī)定數(shù)據(jù)傳輸格式,這個格式可以任意指定,不同的 rpc 協(xié)議,數(shù)據(jù)格式不一定相同),也可以基于 http 協(xié)議。
- http 只能基于 http 協(xié)議。
(2) 傳輸效率
- rpc?使用自定義的 tcp 協(xié)議,可以讓請求報文體積更小,或者使用 http 2.0協(xié)議,也可以很好的減少報文的體積,提高傳輸效率。
- http?如果是基于 http 1.1的協(xié)議,請求中會包含很多無用的內容,如果是基于 http 2.0,那么簡單的封裝以下是可以作為一個 rpc 來使用的,這時標準 rpc?框架更多的是服務治理。
(3) 性能消耗,主要在于序列化和反序列化的耗時
- rpc?可以基于 thrift 實現(xiàn)高效的二進制傳輸。
- http 大部分是通過 json 來實現(xiàn)的,字節(jié)大小和序列化耗時都比 thrift 要更消耗性能。
(3) 負載均衡
- rpc 基本都自帶了負載均衡策略。
- http 需要配置 Nginx,HAProxy來實現(xiàn)。
(3) 服務治理 (下游服務新增,重啟,下線時如何不影響上游調用者)
- rpc 能做到自動通知,不影響上游。
- http 需要事先通知,修改 Nginx/HAProxy 配置。
早期的 webservice,現(xiàn)在熱門的 dubbo,都是 rpc?的典型。rpc?服務主要是針對大型企業(yè)的,而 http 服務主要是針對小企業(yè)的,因為 rpc 效率更高,而 http 服務開發(fā)迭代會更快。
?
?
?
3.?json-rpc 簡介
json-rpc 是基于 json 的跨語言遠程調用協(xié)議,比 xml-rpc、webservice 等基于文本的協(xié)議數(shù)據(jù)傳輸格小,相對 hessian、java-rpc 等二進制協(xié)議便于調試、實現(xiàn)、擴展,是很優(yōu)秀的一種遠程調用協(xié)議。眼下主流語言都已有 json-rpc 的實現(xiàn)框架,Java 語言中較好的 json-rpc 實現(xiàn)框架有 jsonrpc4j、jpoxy、json-rpc。
json-rpc 協(xié)議很簡單,發(fā)起遠程調用時向服務端數(shù)據(jù)傳輸格式例如以下:?
{ "method": "helloCoder", "params": ["Hello JSON-RPC"], "id": 1}參數(shù)說明:
- method:調用的方法名。
- params:方法傳入的參數(shù),若無參數(shù)則傳入 []。
- id: 調用標識符,用于標示一次遠程調用過程。
服務端收到調用請求,處理方法調用,將方法調用結果返回給調用方,返回數(shù)據(jù)格式:
{"result": "Hello JSON-RPC","error": null,"id": 1 }參數(shù)說明:?
- result: 方法返回值,若無返回值,則返回 null。若調用錯誤,返回 null。
- error :調用時錯誤,無錯誤返回 null。?
- id : 調用標識符,與調用方傳入的標識符一致。?
以上就是 json-rpc 協(xié)議規(guī)范,很簡單,便于各種語言實現(xiàn)。
?
?
?
4.?web3j 調用
在 gradle 中添加依賴:
// 以太坊開發(fā)庫 compile 'org.web3j:core:3.3.1-android'拿我們公司的區(qū)塊鏈錢包為例,獲取余額和轉幣調用了 web3j 的方法,它封裝了 json-rpc 的調用過程,先看看獲取余額的代碼:
@Overridepublic BigInteger getBalance(String address) throws CTXCException {CTXCException.checkNetwork();EthGetBalance ethGetBalance;try {ethGetBalance = getWeb3j().ethGetBalance(address, PENDING).send();if (ethGetBalance.hasError())throw new CTXCException(true, ethGetBalance.getError().getMessage());} catch (Exception e) {throw new CTXCException(true, e);}String response = ethGetBalance.getRawResponse();Web3jResponseProcessor processor = new Web3jResponseProcessor(response).process();if (processor.isSuccess())return TokenBigConverter.toBigInteger(processor.getRawResponse().result);return BigInteger.ZERO;}再看看轉賬的調用:
@Overridepublic String sendTransaction(String fromAddress, String password, String toAddress, BigInteger gasPrice,BigInteger gasLimit, BigInteger amount, String payload) throws CTXCException {CTXCException.checkNetwork();try {EthGetTransactionCount ethGetTransactionCount = getWeb3j().ethGetTransactionCount(fromAddress, PENDING).sendAsync().get();BigInteger nonce = ethGetTransactionCount.getTransactionCount();payload = payload == null ? "" : payload;RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, toAddress, amount, payload);Credentials credentials = Credentials.create(WalletManagerService.instance().exportPrivateKey(fromAddress, password));byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);String hexValue = Numeric.toHexString(signedMessage);EthSendTransaction ethSendTransaction = getWeb3j().ethSendRawTransaction(hexValue).send();if (ethSendTransaction.hasError())throw new CTXCException(true, ethSendTransaction.getError().getMessage());Web3jResponseProcessor processor = new Web3jResponseProcessor(ethSendTransaction.getRawResponse()).process();if (processor.isSuccess())return processor.getRawResponse().result;} catch (Exception e) {throw new CTXCException(true, e.getMessage());}return "";}比如:getWeb3j().ethGetBalance(address, PENDING).send(),這樣就能獲取到某個 erc20地址的余額。而 web3j 封裝了 json-rpc 的調用過程,在以太坊的開發(fā)文檔上,對獲取余額的 rpc 調用是這樣的:
可以看到,正是上面說的 json-rpc 協(xié)議的格式。
?
?
?
5.?web3j json-rpc 實現(xiàn)
以?getWeb3j().ethGetBalance(address, PENDING).send() 作為入口分析。
實現(xiàn)在?JsonRpc2_0Web3j 這個類:
public Request<?, EthGetBalance> ethGetBalance(String address, DefaultBlockParameter defaultBlockParameter) {return new Request("eth_getBalance", Arrays.asList(address, defaultBlockParameter.getValue()), this.web3jService, EthGetBalance.class);}將 json-rpc 方法名"eth-getBalance"和Array.asList 將參數(shù)轉換成 List 傳入,返回一個 Request 對象:
public class Request<S, T extends Response> {private static AtomicLong nextId = new AtomicLong(0L);// jsonrpc 版本private String jsonrpc = "2.0";// 方法名private String method;// 參數(shù)列表private List<S> params;// jsonrpc 參數(shù) idprivate long id;private Web3jService web3jService;private Class<T> responseType;public Request() {}public Request(String method, List<S> params, Web3jService web3jService, Class<T> type) {this.method = method;this.params = params;this.id = nextId.getAndIncrement();this.web3jService = web3jService;this.responseType = type;}public String getJsonrpc() {return this.jsonrpc;}public void setJsonrpc(String jsonrpc) {this.jsonrpc = jsonrpc;}public String getMethod() {return this.method;}public void setMethod(String method) {this.method = method;}public List<S> getParams() {return this.params;}public void setParams(List<S> params) {this.params = params;}public long getId() {return this.id;}public void setId(long id) {this.id = id;}public T send() throws IOException {return this.web3jService.send(this, this.responseType);}public Future<T> sendAsync() {return this.web3jService.sendAsync(this, this.responseType);}public Observable<T> observable() {return (new RemoteCall(new Callable<T>() {public T call() throws Exception {return Request.this.send();}})).observable();} }這個類封裝了請求參數(shù)和行為,接著看里面的?send() 方法:
public T send() throws IOException {return this.web3jService.send(this, this.responseType);}調用 web3jService的 send() 方法進行 json-rpc 調用,傳入 request 對象和返回類型。Web3jService 是一個接口,那這邊調的是哪個實現(xiàn)類呢?看看?web3j 對象的構造代碼:
web3j = Web3jFactory.build(new HttpService(web3jRpcURL, true));我這邊傳入的是 HttpService (實現(xiàn)?Web3jService 接口):
protected abstract InputStream performIO(String var1) throws IOException;public <T extends Response> T send(Request request, Class<T> responseType) throws IOException {String payload = this.objectMapper.writeValueAsString(request);InputStream result = this.performIO(payload);return result != null ? (Response)this.objectMapper.readValue(result, responseType) : null;}可以看到 send() 方法:
String payload = this.objectMapper.writeValueAsString(request);先將 request 對象轉換成 json,即上面 json-rpc 協(xié)議的請求格式:
{ "method": "helloCoder", "params": ["Hello JSON-RPC"], "id": 1}然后執(zhí)行?performIO() 進行 rpc 調用,下面是 HttpService 的 performIO() 的實現(xiàn):
protected InputStream performIO(String request) throws IOException {RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, request);Headers headers = this.buildHeaders();Request httpRequest = (new okhttp3.Request.Builder()).url(this.url).headers(headers).post(requestBody).build();Response response = this.httpClient.newCall(httpRequest).execute();if (response.isSuccessful()) {ResponseBody responseBody = response.body();return responseBody != null ? this.buildInputStream(responseBody) : null;} else {throw new ClientConnectionException("Invalid response received: " + response.body());}}rpc 的過程用的是 okhttp3,上面說過 rpc 可以基于 http 協(xié)議,這邊的 httpClient 需要傳入一個 http 協(xié)議版本,這邊用的是默認的:
DEFAULT_PROTOCOLS = Util.immutableList(new Protocol[]{Protocol.HTTP_2, Protocol.HTTP_1_1});而以太坊官方的 web3j json-rpc 的文檔中也寫到:
可以看到它們的服務提供的 json-rpc 支持 http 進行消息傳遞。
?
?
總結
以上是生活随笔為你收集整理的网络篇 - rpc协议的应用web3j的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑显示器如何实现分屏 一台主机两台显
- 下一篇: 通过图像相机进行mark定位,校正角度