网络篇 - rpc协议的应用web3j

 

最近的公司区块链钱包,用到了以太坊的官方开发库 web3j。web3j 是Java版本的以太坊 rpc-json 接口协议封装实现,如果需要将你的 Java 或安卓应用接入以太坊,或者希望用 Java 开发一个钱包应用,那么 web3j 完全能满足你的需求。

 

目录:

  1. rpc 简介
  2. rpc 和 http 对比
  3. json-rpc 简介
  4. web3j 调用
  5. web3j json-rpc 实现

 

 

1. rpc 简介

rpc ( Remote Procedure Call) — 远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。rpc 协议假定某些传输协议的存在,如 tcp 或 udp,为通信程序之间传输信息数据。在 OSI 网络通信模型中,rpc 跨越了传输层和应用层。rpc 使得开发包括网络分布式多程序在内的应用程序更加容易。

我从 google 上找了一张 rpc 模型图,帮助大家理解下:

网络篇 - rpc协议的应用web3j_第1张图片

 

 

 

2. rpc 和 http 对比

目前有很多 Java 的 rpc 框架,有基于 Json 的,有基于 XML,也有基于二进制对象的。rpc 和 平常用的 http/https 到底有什么区别,不都是写一个服务然后在客户端调用吗?

在 http 和 rpc 的选择上可能有些人是迷惑的,主要是因为,有些 rpc 框架配置复杂,如果走 http 也能完成同样的功能,那么为什么要选择 rpc,而不是更容易上手的 http 来实现?

从以下几个点来分析:

(1) 传输协议

  • rpc 可以基于 tcp 协议 (可以省去了 http 报头等一系列东西,rpc 并没有规定数据传输格式,这个格式可以任意指定,不同的 rpc 协议,数据格式不一定相同),也可以基于 http 协议。
  • http 只能基于 http 协议。

(2) 传输效率

  • rpc 使用自定义的 tcp 协议,可以让请求报文体积更小,或者使用 http 2.0协议,也可以很好的减少报文的体积,提高传输效率。
  • http 如果是基于 http 1.1的协议,请求中会包含很多无用的内容,如果是基于 http 2.0,那么简单的封装以下是可以作为一个 rpc 来使用的,这时标准 rpc 框架更多的是服务治理。

(3) 性能消耗,主要在于序列化和反序列化的耗时

  • rpc 可以基于 thrift 实现高效的二进制传输。
  • http 大部分是通过 json 来实现的,字节大小和序列化耗时都比 thrift 要更消耗性能。

(3) 负载均衡

  • rpc 基本都自带了负载均衡策略。
  • http 需要配置 Nginx,HAProxy来实现。

(3) 服务治理 (下游服务新增,重启,下线时如何不影响上游调用者)

  • rpc 能做到自动通知,不影响上游。
  • http 需要事先通知,修改 Nginx/HAProxy 配置。

早期的 webservice,现在热门的 dubbo,都是 rpc 的典型。rpc 服务主要是针对大型企业的,而 http 服务主要是针对小企业的,因为 rpc 效率更高,而 http 服务开发迭代会更快。

 

 

 

3. json-rpc 简介

json-rpc 是基于 json 的跨语言远程调用协议,比 xml-rpc、webservice 等基于文本的协议数据传输格小,相对 hessian、java-rpc 等二进制协议便于调试、实现、扩展,是很优秀的一种远程调用协议。眼下主流语言都已有 json-rpc 的实现框架,Java 语言中较好的 json-rpc 实现框架有 jsonrpc4j、jpoxy、json-rpc。

json-rpc 协议很简单,发起远程调用时向服务端数据传输格式例如以下: 

{ "method": "helloCoder", "params": ["Hello JSON-RPC"], "id": 1}

参数说明:

  • method:调用的方法名。
  • params:方法传入的參数,若无參数则传入 []。
  • id: 调用标识符,用于标示一次远程调用过程。

服务端收到调用请求,处理方法调用,将方法调用结果返回给调用方,返回数据格式:

{
	"result": "Hello JSON-RPC",
	"error": null,
	"id": 1
}

参数说明: 

  • result: 方法返回值,若无返回值,则返回 null。若调用错误,返回 null。
  • error :调用时错误,无错误返回 null。 
  • id : 调用标识符,与调用方传入的标识符一致。 

以上就是 json-rpc 协议规范,很简单,便于各种语言实现。

 

 

 

4. web3j 调用

在 gradle 中添加依赖:

// 以太坊开发库
compile 'org.web3j:core:3.3.1-android'

拿我们公司的区块链钱包为例,获取余额和转币调用了 web3j 的方法,它封装了 json-rpc 的调用过程,先看看获取余额的代码:

    @Override
    public 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;
    }

再看看转账的调用:

  @Override
    public 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 的调用过程,在以太坊的开发文档上,对获取余额的 rpc 调用是这样的:

网络篇 - rpc协议的应用web3j_第2张图片

可以看到,正是上面说的 json-rpc 协议的格式。

 

 

 

5. web3j json-rpc 实现

以 getWeb3j().ethGetBalance(address, PENDING).send() 作为入口分析。

实现在 JsonRpc2_0Web3j 这个类:

    public Request 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 将参数转换成 List 传入,返回一个 Request 对象:

public class Request {
    private static AtomicLong nextId = new AtomicLong(0L);

    // jsonrpc 版本
    private String jsonrpc = "2.0";
    // 方法名
    private String method;
    // 参数列表
    private List params;
    // jsonrpc 参数 id
    private long id;
    private Web3jService web3jService;
    private Class responseType;

    public Request() {
    }

    public Request(String method, List params, Web3jService web3jService, Class 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 getParams() {
        return this.params;
    }

    public void setParams(List 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 sendAsync() {
        return this.web3jService.sendAsync(this, this.responseType);
    }

    public Observable observable() {
        return (new RemoteCall(new Callable() {
            public T call() throws Exception {
                return Request.this.send();
            }
        })).observable();
    }
}

这个类封装了请求参数和行为,接着看里面的 send() 方法:

    public T send() throws IOException {
        return this.web3jService.send(this, this.responseType);
    }

调用 web3jService的 send() 方法进行 json-rpc 调用,传入 request 对象和返回类型。Web3jService 是一个接口,那这边调的是哪个实现类呢?看看 web3j 对象的构造代码:

web3j = Web3jFactory.build(new HttpService(web3jRpcURL, true));

我这边传入的是 HttpService (实现 Web3jService 接口):

    protected abstract InputStream performIO(String var1) throws IOException;

    public  T send(Request request, Class 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 协议的请求格式:

{ "method": "helloCoder", "params": ["Hello JSON-RPC"], "id": 1}

然后执行 performIO() 进行 rpc 调用,下面是 HttpService 的 performIO() 的实现:

    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 协议,这边的 httpClient 需要传入一个 http 协议版本,这边用的是默认的:

DEFAULT_PROTOCOLS = Util.immutableList(new Protocol[]{Protocol.HTTP_2, Protocol.HTTP_1_1});

而以太坊官方的 web3j json-rpc 的文档中也写到:

网络篇 - rpc协议的应用web3j_第3张图片

可以看到它们的服务提供的 json-rpc 支持 http 进行消息传递。

 

 

你可能感兴趣的:(网络篇,网络,rpc,web3j)