整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          如何通俗易懂的向面試官解釋什么是 RPC 框架

          注于Java領(lǐng)域優(yōu)質(zhì)技術(shù),歡迎關(guān)注

          關(guān)于RPC

          首先了解什么叫RPC,為什么要RPC,RPC是指遠(yuǎn)程過程調(diào)用,也就是說(shuō)兩臺(tái)服務(wù)器A,B,一個(gè)應(yīng)用部署在A服務(wù)器上,想要調(diào)用B服務(wù)器上應(yīng)用提供的函數(shù)/方法,由于不在一個(gè)內(nèi)存空間,不能直接調(diào)用,需要通過網(wǎng)絡(luò)來(lái)表達(dá)調(diào)用的語(yǔ)義和傳達(dá)調(diào)用的數(shù)據(jù)。

          比如說(shuō),一個(gè)方法可能是這樣定義的:

          Employee getEmployeeByName(String fullName)

          那么:

          • 首先,要解決通訊的問題,主要是通過在客戶端和服務(wù)器之間建立TCP連接,遠(yuǎn)程過程調(diào)用的所有交換的數(shù)據(jù)都在這個(gè)連接里傳輸。連接可以是按需連接,調(diào)用結(jié)束后就斷掉,也可以是長(zhǎng)連接,多個(gè)遠(yuǎn)程過程調(diào)用共享同一個(gè)連接。
          • 第二,要解決尋址的問題,也就是說(shuō),A服務(wù)器上的應(yīng)用怎么告訴底層的RPC框架,如何連接到B服務(wù)器(如主機(jī)或IP地址)以及特定的端口,方法的名稱名稱是什么,這樣才能完成調(diào)用。比如基于Web服務(wù)協(xié)議棧的RPC,就要提供一個(gè)endpoint URI,或者是從UDDI服務(wù)上查找。如果是RMI調(diào)用的話,還需要一個(gè)RMI Registry來(lái)注冊(cè)服務(wù)的地址。
          • 第三,當(dāng)A服務(wù)器上的應(yīng)用發(fā)起遠(yuǎn)程過程調(diào)用時(shí),方法的參數(shù)需要通過底層的網(wǎng)絡(luò)協(xié)議如TCP傳遞到B服務(wù)器,由于網(wǎng)絡(luò)協(xié)議是基于二進(jìn)制的,內(nèi)存中的參數(shù)的值要序列化成二進(jìn)制的形式,也就是序列化(Serialize)或編組(marshal),通過尋址和傳輸將序列化的二進(jìn)制發(fā)送給B服務(wù)器。
          • 第四,B服務(wù)器收到請(qǐng)求后,需要對(duì)參數(shù)進(jìn)行反序列化(序列化的逆操作),恢復(fù)為內(nèi)存中的表達(dá)方式,然后找到對(duì)應(yīng)的方法(尋址的一部分)進(jìn)行本地調(diào)用,然后得到返回值。
          • 第五,返回值還要發(fā)送回服務(wù)器A上的應(yīng)用,也要經(jīng)過序列化的方式發(fā)送,服務(wù)器A接到后,再反序列化,恢復(fù)為內(nèi)存中的表達(dá)方式,交給A服務(wù)器上的應(yīng)用

          (圖片來(lái)源:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html)

          為什么RPC呢?就是無(wú)法在一個(gè)進(jìn)程內(nèi),甚至一個(gè)計(jì)算機(jī)內(nèi)通過本地調(diào)用的方式完成的需求,比如比如不同的系統(tǒng)間的通訊,甚至不同的組織間的通訊。由于計(jì)算能力需要橫向擴(kuò)展,需要在多臺(tái)機(jī)器組成的集群上部署應(yīng)用,

          RPC的協(xié)議有很多,比如最早的CORBA,Java RMI,Web Service的RPC風(fēng)格,Hessian,Thrift,甚至Rest API。

          關(guān)于Netty

          而Netty框架不局限于RPC,更多的是作為一種網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)框架,比如HTTP,由于RPC需要高效的網(wǎng)絡(luò)通信,就可能選擇以Netty作為基礎(chǔ)。除了網(wǎng)絡(luò)通信,RPC還需要有比較高效的序列化框架,以及一種尋址方式。如果是帶會(huì)話(狀態(tài))的RPC調(diào)用,還需要有會(huì)話和狀態(tài)保持的功能。

          大體上來(lái)說(shuō),Netty就是提供一種事件驅(qū)動(dòng)的,責(zé)任鏈?zhǔn)剑ㄒ部梢哉f(shuō)是流水線)的網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)方式。網(wǎng)絡(luò)協(xié)議包含很多層次,很多部分組成,如傳輸層協(xié)議,編碼解碼,壓縮解壓,身份認(rèn)證,加密解密,請(qǐng)求的處理邏輯,怎么能夠更好的復(fù)用,擴(kuò)展,業(yè)界通用的方法就是責(zé)任鏈,

          一個(gè)請(qǐng)求應(yīng)答網(wǎng)絡(luò)交互通常包含兩條鏈,一條鏈(Upstream)是從傳輸層,經(jīng)過一系列步驟,如身份認(rèn)證,解密,日志,流控,最后到達(dá)業(yè)務(wù)層,一條鏈(DownStream)是業(yè)務(wù)層返回后,又經(jīng)過一系列步驟,如加密等,又回到傳輸層。

          (圖片來(lái)源:ChannelPipeline (The Netty Project API Reference (3.2.6.Final)))

          這樣每一層都有一個(gè)處理接口,都可以進(jìn)行不同的操作,比如身份認(rèn)證,加解密,日志,流控,將不同的處理實(shí)現(xiàn)像拼積木那樣插接起來(lái)就可以實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)協(xié)議了(快速開發(fā))。每一層都有自己的實(shí)現(xiàn),上層不需要關(guān)注面向網(wǎng)絡(luò)的操作(可維護(hù))。Netty已經(jīng)提供了很多實(shí)現(xiàn)。

          (圖片來(lái)源:http://docs.jboss.org/netty/3.1/guide/html/architecture.html)

          當(dāng)然Netty還有許多好處,比如對(duì)非阻塞IO(NIO)的支持,比如在鏈上傳遞時(shí)最大程度的減少buffer的copy(高性能)。

          面試的時(shí)候經(jīng)常被問到RPC相關(guān)的問題,例如:你說(shuō)說(shuō)RPC實(shí)現(xiàn)原理、讓你實(shí)現(xiàn)一個(gè)RPC框架應(yīng)該考慮哪些地方、RPC框架基礎(chǔ)上發(fā)起一個(gè)請(qǐng)求是怎樣一個(gè)流程等等。所以這次我就總結(jié)一波RPC的相關(guān)知識(shí)點(diǎn),提前說(shuō)明一下,本篇文章只是為了回答一些面試問題,所以只是解釋原理,并不會(huì)深入挖掘細(xì)節(jié)。

          注冊(cè)中心

          RPC(Remote Procedure Call)翻譯成中文就是$\color{red}{遠(yuǎn)程過程調(diào)用}$。RPC框架起到的作用就是為了實(shí)現(xiàn),調(diào)用遠(yuǎn)程方法時(shí),能夠做到和調(diào)用本地方法一樣,讓開發(fā)人員更專注于業(yè)務(wù)開發(fā),不用去考慮網(wǎng)絡(luò)編程等細(xì)節(jié)。

          RPC框架怎么就實(shí)現(xiàn)不讓開發(fā)人員關(guān)注網(wǎng)絡(luò)編程等細(xì)節(jié)呢?

          首先我們區(qū)分兩個(gè)角色一個(gè)服務(wù)提供方,一個(gè)是服務(wù)調(diào)用方。服務(wù)調(diào)用方其實(shí)是通過動(dòng)態(tài)代理、負(fù)載均衡、網(wǎng)絡(luò)調(diào)用等機(jī)制去服務(wù)提供方的機(jī)器上去執(zhí)行對(duì)應(yīng)的方法。服務(wù)提供方將方法執(zhí)行完成后,將執(zhí)行結(jié)果再通過網(wǎng)絡(luò)傳輸返回到服務(wù)提供方。
          大致過程如下:

          但是現(xiàn)在的服務(wù)都是集群部署,那么服務(wù)調(diào)用方怎么應(yīng)該實(shí)時(shí)的知道服務(wù)提供方的集群中的變化,例如服務(wù)提供方的IP地址變了,或者是服務(wù)重啟時(shí)怎么能夠及時(shí)的切換流量呢?

          這就需要$\color{red}{注冊(cè)中心}$起作用了,我們可以把注冊(cè)中心看作服務(wù)端,然后每個(gè)服務(wù)都看成客戶端,每個(gè)客戶端都需要將自己注冊(cè)到注冊(cè)中心,然后一個(gè)服務(wù)調(diào)用方要調(diào)用另一個(gè)服務(wù)時(shí),需要從注冊(cè)中心獲取服務(wù)提供方的信息,主要是獲取服務(wù)提供方的服務(wù)器IP地址列表和端口信息

          服務(wù)調(diào)用方獲取到這些信息后緩存到自己本地,并且跟注冊(cè)中心保持一個(gè)長(zhǎng)連接當(dāng)服務(wù)提供方有任何變化時(shí),注冊(cè)中心能夠?qū)崟r(shí)的通知給服務(wù)調(diào)用方,調(diào)用方能夠及時(shí)更新自己本地緩存的信息(也可以采用定時(shí)輪詢的方式)。

          服務(wù)調(diào)用方獲取到服務(wù)器IP地址信息后,根據(jù)自己的負(fù)載均衡策略選擇一個(gè)IP地址然后發(fā)起網(wǎng)絡(luò)調(diào)用的請(qǐng)求。

          那么網(wǎng)絡(luò)客戶端是通過什么發(fā)起的網(wǎng)絡(luò)調(diào)用呢?

          可以自己使用JDK原生的BIO活NIO來(lái)實(shí)現(xiàn)一套網(wǎng)絡(luò)通信模塊,但是這里我們建議直接使用強(qiáng)大的網(wǎng)絡(luò)通信框架Netty。它是基于NIO的網(wǎng)絡(luò)通信框架,支持高并發(fā),封裝完善,而且性能好傳輸快。

          Netty不是我們本文的主要內(nèi)容,這里就不展開說(shuō)了。

          客戶端調(diào)用過程

          因?yàn)槲覀冎罃?shù)據(jù)在網(wǎng)絡(luò)中傳輸?shù)臅r(shí)候都是以二進(jìn)制的形式的,所以在調(diào)用方將調(diào)用的參數(shù)進(jìn)行傳遞的時(shí)候是需要進(jìn)行序列化的。服務(wù)提供方在接收到參數(shù)時(shí)也是需要進(jìn)行反序列化的。

          網(wǎng)絡(luò)協(xié)議

          調(diào)用方既然需要序列化,服務(wù)提供方又要進(jìn)行反序列化,這樣雙方就要確定好一個(gè)協(xié)議,調(diào)用方傳輸什么參數(shù),服務(wù)提供方就按照這個(gè)協(xié)議去進(jìn)行解析,而且在返回結(jié)果的時(shí)候也是按照這個(gè)協(xié)議進(jìn)行結(jié)果解析。

          那么這個(gè)協(xié)議應(yīng)該是怎么樣的結(jié)構(gòu),都是什么樣子的呢?
          因?yàn)檫@個(gè)協(xié)議可以自定義,我們?yōu)榱朔奖憔鸵訨SON的形式給舉個(gè)例子:

          {
          	"interfaces": "interface=com.jimoer.rpc.test.producer.TestService;method=printTest;parameter=com.jiomer.rpc.test.producer.TestArgs",
          	"requestId": "3",
          	"parameter": {
          		"com.jiomer.rpc.test.producer.TestArgs": {
          			"age": 20,
          			"name": "Jimoer"
          		}
          	}
          }

          首先第一個(gè)參數(shù)interfaces是,我們要讓服務(wù)提供方知道調(diào)用方要調(diào)用哪個(gè)接口,以及接口中的哪個(gè)方法,并且方法的參數(shù)是什么類型的。

          第二個(gè)參數(shù)是當(dāng)前一次請(qǐng)求的一個(gè)唯一標(biāo)識(shí),在多個(gè)線程同時(shí)請(qǐng)求一個(gè)方法時(shí),用這個(gè)id來(lái)進(jìn)行區(qū)分,以后無(wú)論是做鏈路追蹤還是日志管理都可以以此id為依據(jù)。

          第三個(gè)參數(shù)就是 實(shí)際的調(diào)用方法中的參數(shù)值。具體是什么類型的,每個(gè)屬性值都是什么。

          調(diào)用

          下面也是舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明一下調(diào)用的過程。我們一部分采用代碼的形式一部分采用文字的形式來(lái)將整個(gè)調(diào)用過程串起來(lái)。

          // 定義請(qǐng)求的URL
          String tcpURL = "tcp://testProducer/TestServiceImpl";
          // 定義接口請(qǐng)求
          TestService testService = ProxyFactory.create(TestService.class, tcpURL);
          // 組裝請(qǐng)求參數(shù)
          TestArgs testArgs = new TestArgs(20,"Jimoer");
          // 通過動(dòng)態(tài)代理執(zhí)行請(qǐng)求
          String result = testService.printTest(testArgs);

          通過查看上面的代碼我們可以看到整個(gè)調(diào)用過程最核心的地方在ProxyFactory.create()方法里,這個(gè)方法里面主要的過程是,動(dòng)態(tài)代理生成接口的實(shí)際代理對(duì)象,然后使用Netty的接口發(fā)起網(wǎng)絡(luò)請(qǐng)求。

          Proxy.newProxyInstance(getClass().getClassLoader(), interfaces.getClass().getInterfaces(), new InvocationHandler() {
                      @Override
                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          
                          // 第一步:獲取調(diào)用服務(wù)的地址列表
                          ListregistryInfos = interfacesMethodRegistryList.get(clazz);
          
                          if (registryInfos == null) {
                              throw new RuntimeException("無(wú)法找到服務(wù)提供者");
                          }
          
                          // 第二步: 通過自身的負(fù)載均衡策略選擇一個(gè)地址
                          RegistryInfo registryInfo = loadBalancer.choose(registryInfos);
          
                          // 第三步:Netty的網(wǎng)絡(luò)請(qǐng)求處理
                          ChannelHandlerContext ctx = channels.get(registryInfo);
                          // 第四步:根據(jù)接口類的全路徑名和方法生成唯一標(biāo)識(shí)
                          String identify = InvokeUtils.buildInterfaceMethodIdentify(clazz, method);
                          String requestId;
                          // 第五步:通過加鎖的方式保證生成的requestId的唯一性
                          synchronized (ApplicationContext.this) {
                              requestIdWorker.increment();
                              requestId = String.valueOf(requestIdWorker.longValue());
                          }
                          // 第六步: 組織參數(shù)
                          JSONObject jsonObject = new JSONObject();
                          jsonObject.put("interfaces", identify);
                          jsonObject.put("parameter", param);
                          jsonObject.put("requestId", requestId);
                          System.out.println("發(fā)送給服務(wù)端JSON為:" + jsonObject.toJSONString());
                          // $$ 多條消息之間的分隔符
                          String msg = jsonObject.toJSONString() + "$$";
                          ByteBuf byteBuf = Unpooled.buffer(msg.getBytes().length);
                          byteBuf.writeBytes(msg.getBytes());
                          // 第七步:這里發(fā)起調(diào)用
                          ctx.writeAndFlush(byteBuf);
                          // 這里會(huì)將線程進(jìn)行阻塞,知道服務(wù)提供方將請(qǐng)求處理好之后返回結(jié)果,再喚醒。
                          waitForResult();
                          return result;
          
                      }
                  });

          執(zhí)行過程大致分為這幾步:

          1. 獲取調(diào)用服務(wù)的地址列表。
          2. 通過自身的負(fù)載均衡策略選擇一個(gè)地址。
          3. Netty的網(wǎng)絡(luò)請(qǐng)求處理(選擇一個(gè)渠道Channel)。
          4. 根據(jù)接口類的全路徑名和方法生成唯一標(biāo)識(shí)。
          5. 通過加鎖的方式保證生成的requestId的唯一性。
          6. 組織請(qǐng)求參數(shù)。
          7. 發(fā)起調(diào)用。
          8. 線程阻塞,直到服務(wù)提供方返回結(jié)果。
          9. 填充返回結(jié)果,返回到調(diào)用方。

          服務(wù)端處理過程

          上面也說(shuō)了,服務(wù)調(diào)用方發(fā)起網(wǎng)絡(luò)請(qǐng)求后,會(huì)阻塞住,直到服務(wù)提供方返回?cái)?shù)據(jù),所以服務(wù)提供方處理完調(diào)用方法的邏輯后,還是要喚醒阻塞的調(diào)用線程的。

          服務(wù)提供方在處理請(qǐng)求時(shí)也是先通過Netty獲取到數(shù)據(jù),然后再進(jìn)行反序列化,然后再根據(jù)協(xié)議獲取到需要調(diào)用的方法,然后通過反射去進(jìn)行調(diào)用。

          Netty的返回入口在下面這部分邏輯里

          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
              try {
                  String message = (String) msg;
                  if (messageCallback != null) {
                  	// 將接收到的消息放到回調(diào)方法中
                      messageCallback.onMessage(message);
                  }
              } finally {
                  ReferenceCountUtil.release(msg);
              }
          }

          Netty的client接收到響應(yīng)的消息后,先將結(jié)果返回到調(diào)用方,處理完成之后再去釋放之前的阻塞調(diào)用線程。

          client.setMessageCallback(message -> {
              // 這里收單服務(wù)端返回的消息,先壓入隊(duì)列
              RpcResponse response = JSONObject.parseObject(message, RpcResponse.class);
          	System.out.println("收到一個(gè)響應(yīng):" + response);
              String interfaceMethodIdentify = response.getInterfaceMethodIdentify();
              String requestId = response.getRequestId();
              // 設(shè)定唯一標(biāo)識(shí)
              String key = interfaceMethodIdentify + "#" + requestId;
              Invoker invoker = inProgressInvoker.remove(key);
              // 將結(jié)果設(shè)置到代理對(duì)象中
              invoker.setResult(response.getResult());
          	// 加鎖再釋放之前的阻塞線程。
              synchronized (ApplicationContext.this) {
                  ApplicationContext.this.notifyAll();
              }
          });

          setResult()方法

          @Override
          public void setResult(String result) {
              synchronized (this) {
                  this.result = JSONObject.parseObject(result, returnType);
                  notifyAll();
              }
          }

          上面的步驟就是這樣,按照之前請(qǐng)求的唯一標(biāo)識(shí)放入到返回的信息中,然后將結(jié)果設(shè)置到代理對(duì)象中,再通過返回結(jié)果,然后喚醒之前的調(diào)用阻塞線程。

          總結(jié)

          其實(shí)整個(gè)RPC的請(qǐng)求過程就是如下(不含異步調(diào)用):


          做一個(gè)總結(jié),用大白話把一個(gè)RPC請(qǐng)求流程描述出來(lái):
          首先無(wú)論是調(diào)用方還是服務(wù)提供方都要注冊(cè)到注冊(cè)中心;

          1. 服務(wù)調(diào)用方把請(qǐng)求參數(shù)對(duì)象序列化成二進(jìn)制數(shù)據(jù),通過動(dòng)態(tài)代理生成代理對(duì)象,通過代理對(duì)象,使用Netty選擇一個(gè)從注冊(cè)中心拉取到的服務(wù)提供方的地址,然后發(fā)起網(wǎng)絡(luò)請(qǐng)求。
          2. 服務(wù)提供方從TCP通道中接收到二進(jìn)制數(shù)據(jù),根據(jù)定義的RPC網(wǎng)絡(luò)協(xié)議,從二進(jìn)制數(shù)據(jù)中反序列化后,分割出接口地址和參數(shù)對(duì)象,再通過反射找到接口執(zhí)行調(diào)用。
          3. 然后服務(wù)提供方再把調(diào)用執(zhí)行結(jié)果序列化后,回傳到TCP通道中。
          4. 服務(wù)調(diào)用方獲取到應(yīng)答二進(jìn)制數(shù)據(jù)后,再反序列化成結(jié)果對(duì)象。

          這樣就完成了一次RPC網(wǎng)絡(luò)調(diào)用,其實(shí)后面框架擴(kuò)展后,還要考慮限流、熔斷、服務(wù)降級(jí)、序列化多樣性擴(kuò)展,服務(wù)監(jiān)控、鏈路追蹤等等功能。這些就要后面再擴(kuò)展的講了,這次就先到這了。

          原文鏈接:https://www.cnblogs.com/jimoer/p/15511954.html

          :今年3月開放聯(lián)盟鏈“中移鏈”在區(qū)塊鏈服務(wù)網(wǎng)絡(luò)(BSN[1])中完成適配并上線發(fā)布,吸引了大批開發(fā)者,部分開發(fā)者提出了一些共性問題

          目的:本篇文章是為了讓讀者了解如何在中移鏈(基于EOS)上調(diào)用RPC接口組裝交易、簽名、上鏈以及查詢上鏈結(jié)果

          適用對(duì)象:適用于BSN開放聯(lián)盟鏈--中移鏈(基于EOS)開發(fā)者

          1. 準(zhǔn)備

          ?需要現(xiàn)在bsn[2]創(chuàng)建項(xiàng)目和創(chuàng)建一個(gè)賬戶,可以參考:https://bsnbase.com/static/tmpFile/bzsc/openper/7-3-6.html

          ?可以獲取到eos 端點(diǎn):https://opbningxia.bsngate.com:18602/api/aecb28acfd154cfeb90d0b6a8ecab1e7/rpc ?eos賬戶/合約地址:helailiang14

          ?為賬戶購(gòu)買足夠的資源(cpu、net、ram)

          ?安裝EOSIO開發(fā)環(huán)境:需要安裝cleos和keosd, 可以參考:https://developers.eos.io/welcome/latest/getting-started-guide/local-development-environment/installing-eosio-binaries ?部署eos合約: hello.cpp

          #include <eosio/eosio.hpp>
          #include <eosio/transaction.hpp>
          
          using namespace eosio;
          // 通過[[eosio::contract]]標(biāo)注這個(gè)類是一個(gè)合約
          class [[eosio::contract]] hello : public contract
          {
          public:
              using contract::contract;
              // 在構(gòu)造函數(shù)進(jìn)行表對(duì)象的實(shí)例化, 標(biāo)準(zhǔn)合約構(gòu)造函數(shù),receiver也就是我們的合約賬號(hào)(一般情況下),code就是我們的action名稱,ds就是數(shù)據(jù)流
              // get_self() 合約所在的賬號(hào)  get_code() 當(dāng)前交易請(qǐng)求的action方法名 get_datastream() 當(dāng)前數(shù)據(jù)流
              hello(name receiver, name code, datastream<const char *> ds) : contract(receiver, code, ds), friend_table(get_self(), get_self().value)
              {
              }
              // 用[[eosio::action]]標(biāo)注這個(gè)方法是一個(gè)合約action就行
              // 注意:action的名稱要求符合name類型的規(guī)則
              [[eosio::action]] void hi(name user)
              {
                  print("Hello, ", user);
                  print("get_self,", get_self().value);
                  // print("get_code,", get_code().value);
                  uint32_t now = current_time_point().sec_since_epoch();
          
                  auto friend_itr = friend_table.find(user.value);
                  // 數(shù)據(jù)不存在 
                  if (friend_itr == friend_table.end()) 
                  {
                      // 第一個(gè)參數(shù)就是內(nèi)存使用的對(duì)象,第二個(gè)參數(shù)就是添加表對(duì)象時(shí)的委托方法。
                      friend_table.emplace(get_self(), [&](auto &f) {
                          f.friend_name = user;
                          f.visit_time = now;
                      });
                  }
                  else
                  {
                      // 第一個(gè)參數(shù)是傳遞需要修改的數(shù)據(jù)指針,第二個(gè)參數(shù)是內(nèi)存使用的對(duì)象,第二個(gè)參數(shù)就是表對(duì)象修改時(shí)的委托方法
                      friend_table.modify(friend_itr, get_self(), [&](auto &f) {
                          f.visit_time = now;
                      });
                  }
              }
          
              [[eosio::action]] void nevermeet(name user)
              {
                  print("Never see you again, ", user);
          
                  auto friend_itr = friend_table.find(user.value);
                  check(friend_itr != friend_table.end(), "I don't know who you are.");
                  // 只有一個(gè)參數(shù),就是要?jiǎng)h除的對(duì)象指針
                  friend_table.erase(friend_itr);
              }
          
              [[eosio::action]] void meetagain()
              {
              uint32_t now = current_time_point().sec_since_epoch();
          
              auto time_idx = friend_table.get_index<"time"_n>();
              auto last_meet_itr = time_idx.begin();
              check(last_meet_itr != time_idx.end(), "I don't have a friend.");
          
              time_idx.modify(last_meet_itr, get_self(), [&](auto &f) {
                      f.visit_time = now;
              });
              }
          
          private:
              // 定義一個(gè)結(jié)構(gòu)體,然后用[[eosio::table]]標(biāo)注這個(gè)結(jié)構(gòu)體是一個(gè)合約表。在結(jié)構(gòu)體里定義一個(gè)函數(shù)名primary_key,返回uint64_t類型,作為主鍵的定義
              struct [[eosio::table]] my_friend
              {
                  name friend_name;
                  uint64_t visit_time;
          
                  uint64_t primary_key() const { return friend_name.value; }
                  double by_secondary() const { return -visit_time; }
              };
              // 定義表名和查詢索引  "friends"_n就是定義表名,所以使用了name類型,之后my_friend是表的結(jié)構(gòu)類
              typedef eosio::multi_index<"friends"_n, my_friend> friends;
              friends friend_table;
          };
          




          2. rpc調(diào)用流程


          1. 將交易信息由JSON格式序列化為BIN格式字符串


          curl   -X POST   'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/abi_json_to_bin' \
          -d '{
              "code": "helailiang14",
              "action": "hi",
              "args": {
                  "user": "helloworld"
              }
          }'
          ------------------
          return
          
          {"binargs":"00408a97721aa36a"}
          


          2. 獲取當(dāng)前最新的區(qū)塊編號(hào)


          curl   GET 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_info'
          
          ------------------
          return
          
          {
              "server_version":"11d35f0f",
              "chain_id":"9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1",
              "head_block_num":15134328,
              "last_irreversible_block_num":15134262,
              "last_irreversible_block_id":"00e6ee360b5e7680a526ddea45db1be15c4be2cd2389020688218fe765be6db7",
              "head_block_id":"00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
              "head_block_time":"2022-04-27T09:08:08.500",
              "head_block_producer":"prod.b",
              "virtual_block_cpu_limit":200000000,
              "virtual_block_net_limit":1048576000,
              "block_cpu_limit":199900,
              "block_net_limit":1048576,
              "server_version_string":"v3af0a20",
              "fork_db_head_block_num":15134328,
              "fork_db_head_block_id":"00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
              "server_full_version_string":"v3af0a20",
              "last_irreversible_block_time":"2022-04-27T09:07:35.500"
          }


          獲取到head_block_num : 15134328

          獲取 head_block_id:00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824

          獲取 chain_id: 9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1

          3. 根據(jù)區(qū)塊編號(hào)獲取區(qū)塊詳情


          curl   -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_block' \
          -d '{
              "block_num_or_id": "15130610"
          }'
          ------------------
          return
          
          {
              "timestamp": "2022-04-27T09:08:08.500",
              "producer": "prod.b",
              "confirmed": 0,
              "previous": "00e6ee77f2655528739622d2c9235026d4f10138b9821e46ea35165cb086d12d",
              "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
              "action_mroot": "665584b582b234bf58d3708b31da20e14d266713e3bc6ce79ea3187cc2ffa5a4",
              "schedule_version": 2,
              "new_producers": null,
              "producer_signature": "SIG_K1_KiYCDLMgE6gE1nNqQQL2jEEF3VVd6iaspAePvvJMjKwgg2Yf6GiTYcznrkymAdtZUAUFh28N8r9RzX936cASKDB6JW6ga3",
              "transactions": [],
              "id": "00e6ee7889523875a28284effecdd1199cc960adb14c14c36cd1bd52afed6824",
              "block_num": 15134328,
              "ref_block_prefix": 4018438818
          }


          獲取到timestamp : 2022-04-27T09:08:08

          4. 簽署交易

          使用EOSIO提供的簽名工具實(shí)現(xiàn)簽名:啟動(dòng)keosd后,才會(huì)提供簽名服務(wù)


          curl -X POST POST 'http://192.168.1.46:8800/v1/wallet/sign_transaction' \
          -d '[
              {
                  "expiration": "2022-04-27T10:08:08",
                  "ref_block_num": 61048,
                  "ref_block_prefix": 4018438818,
                  "max_net_usage_words": 0,
                  "max_cpu_usage_ms": 0,
                  "delay_sec": 0,
                  "context_free_actions": [],
                  "actions": [
                      {
                          "account": "helailiang14",
                          "name": "hi",
                          "authorization": [
                              {
                                  "actor": "helailiang14",
                                  "permission": "active"
                              }
                          ],
                          "data": "00408a97721aa36a"
                      }
                  ],
                  "transaction_extensions": [],
                  "signatures": [],
                  "context_free_data": []
              },
              [
                  "EOS6F6PRkSaPyijTDBYskFbsxpGz53JMTFbEhua94fQEyf7pAMc7Y"
              ],
              "9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1"
          ]'
          ------------------
          return :
          
          {
              "expiration":"2022-04-27T10:08:08",
              "ref_block_num":61048,
              "ref_block_prefix":4018438818,
              "max_net_usage_words":0,
              "max_cpu_usage_ms":0,
              "delay_sec":0,
              "context_free_actions":[
          
              ],
              "actions":[
                  {
                      "account":"helailiang14",
                      "name":"hi",
                      "authorization":[
                          {
                              "actor":"helailiang14",
                              "permission":"active"
                          }
                      ],
                      "data":"00408a97721aa36a"
                  }
              ],
              "transaction_extensions":[
          
              ],
              "signatures":[
                  "SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo"
              ],
              "context_free_data":[
          
              ]
          }


          ?expiration 過期時(shí)間。這里將timestamp加上了60分鐘.?ref_block_num: 由第二步返回的head_block_id 16進(jìn)制的 然后字段截取前八位低4位 轉(zhuǎn)10進(jìn)制, ee78 ==> 61048?ref_block_prefix:由第二步返回的head_block_id 16進(jìn)制的 然后字段截取16到24 然后兩個(gè)字節(jié)反轉(zhuǎn)(a28284ef反轉(zhuǎn)為ef8482a2) 轉(zhuǎn)10進(jìn)制, ef8482a2 ==> 4018438818?account 合約名稱,即部署合約的賬戶?name 調(diào)用的合約方法。?actor 調(diào)用者。簽名者?data :第一步生成的bin字符串?permission 使用的權(quán)限類型?EOS6F6PRkSaPyijTDBYskFbsxpGz53JMTFbEhua94fQEyf7pAMc7Y :簽署此交易的公鑰。實(shí)際上是由錢包中對(duì)應(yīng)的私鑰來(lái)簽?9b4c6015f8b73b2d7ee3ebd92d249a1aba06a614e9990dcf54f7cf2e3d5172e1: 是第二步獲取的chain_id??signatures: 簽名結(jié)果 SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo

          5. 打包交易

          使用 cleos convert pack_transaction將交易報(bào)文轉(zhuǎn)換成 packed 格式


          cleos convert pack_transaction  '{
          "expiration":"2022-04-27T10:08:08",
              "ref_block_num":61048,
              "ref_block_prefix":4018438818,
              "max_net_usage_words": 0,
              "max_cpu_usage_ms": 0,
              "delay_sec": 0,
              "context_free_actions": [],
              "actions": [
                  {
                      "account": "helailiang14",
                      "name": "hi",
                      "authorization": [
                          {
                              "actor": "helailiang14",
                              "permission": "active"
                          }
                      ],
                      "data": "00408a97721aa36a"
                  }
              ],
              "transaction_extensions": []
          }'
          
          ------------------
          return:
          
          {
            "signatures": [],
            "compression": "none",
            "packed_context_free_data": "",
            "packed_trx": "0816696278eea28284ef000000000140029bc64567a26a000000000000806b0140029bc64567a26a00000000a8ed32320800408a97721aa36a00"
          }
          


          packed_trx: 為交易報(bào)文的 packed 格式

          6. 提交交易


          curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/push_transaction' \
          -d '{
            "signatures": [
              "SIG_K1_K2AzV2Pk4SP3PQhcdQ1bYgGZgr7PUUcJkAGowvncFV1ngrZufeCQpveAUBRYvNA5uyxFk2hKiot3Mu7FCW5rqqeoU5SVTo"
            ],
            "compression": "none",
            "packed_context_free_data": "",
            "packed_trx": "0816696278eea28284ef000000000140029bc64567a26a000000000000806b0140029bc64567a26a00000000a8ed32320800408a97721aa36a00"
          }'
          
          ------------------
          return:
          
          {
              "transaction_id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
              "processed":{
                  "id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
                  "block_num":15136664,
                  "block_time":"2022-04-27T09:27:36.500",
                  "producer_block_id":null,
                  "receipt":{
                      "status":"executed",
                      "cpu_usage_us":272,
                      "net_usage_words":13
                  },
                  "elapsed":272,
                  "net_usage":104,
                  "scheduled":false,
                  "action_traces":[
                      {
                          "action_ordinal":1,
                          "creator_action_ordinal":0,
                          "closest_unnotified_ancestor_action_ordinal":0,
                          "receipt":{
                              "receiver":"helailiang14",
                              "act_digest":"31ff1ecb2b0b0c89911b74c7930f08ecfefbd24ba59ef30a905d44068d2d8910",
                              "global_sequence":15199315,
                              "recv_sequence":2,
                              "auth_sequence":[
                                  [
                                      "helailiang14",
                                      4
                                  ]
                              ],
                              "code_sequence":1,
                              "abi_sequence":1
                          },
                          "receiver":"helailiang14",
                          "act":{
                              "account":"helailiang14",
                              "name":"hi",
                              "authorization":[
                                  {
                                      "actor":"helailiang14",
                                      "permission":"active"
                                  }
                              ],
                              "data":{
                                  "user":"helloworld"
                              },
                              "hex_data":"00408a97721aa36a"
                          },
                          "context_free":false,
                          "elapsed":63,
                          "console":"Hello, helloworldget_self,7683817463629939264",
                          "trx_id":"a69d03f6b1bab4bd8908124eef5e59d3e47df4063e697a07487308cde63a9f79",
                          "block_num":15136664,
                          "block_time":"2022-04-27T09:27:36.500",
                          "producer_block_id":null,
                          "account_ram_deltas":[
          
                          ],
                          "account_disk_deltas":[
          
                          ],
                          "except":null,
                          "error_code":null,
                          "return_value_hex_data":"",
                          "inline_traces":[
          
                          ]
                      }
                  ],
                  "account_ram_delta":null,
                  "except":null,
                  "error_code":null
              }
          }


          signatures: 為第4步簽名的結(jié)果

          packed_trx: 為第5步的報(bào)文轉(zhuǎn)換結(jié)果

          7. 查詢表數(shù)據(jù)


          curl -X POST 'https://opbningxia.bsngate.com:18602/api/{您的開放聯(lián)盟鏈項(xiàng)目ID/rpc/v1/chain/get_table_rows' \
          -d'{
            "code": "helailiang14",
            "table": "friends",
            "scope": "helailiang14",
             "json": true
          }'
          ------------------
          return:
          
          {
              "rows":[
                  {
                      "friend_name":"helloworld",
                      "visit_time":1651051656
                  }
              ],
              "more":false,
              "next_key":"",
              "next_key_bytes":""
          }


          數(shù)據(jù)[helloworld]已經(jīng)寫到table中

          References

          [1] BSN: https://bsnbase.com/
          [2] bsn: https://bsnbase.com/p/home/Openalliance/projectManagement


          主站蜘蛛池模板: 精品视频午夜一区二区| 亚洲宅男精品一区在线观看| 亚洲高清日韩精品第一区| 国模一区二区三区| 久久国产视频一区| 日韩一区二区三区免费体验| 亚洲福利一区二区精品秒拍| www一区二区www免费| 天美传媒一区二区三区| 亚洲天堂一区在线| 久久一本一区二区三区| 国模吧无码一区二区三区| 动漫精品一区二区三区3d| 国产乱人伦精品一区二区| 色偷偷一区二区无码视频| 国精产品一区一区三区| 精品久久国产一区二区三区香蕉| 日韩高清一区二区| 亚洲日韩中文字幕一区| 亚洲AV日韩综合一区尤物| 精品无码国产一区二区三区AV| 国产视频一区二区在线播放| 国产韩国精品一区二区三区| 国产福利电影一区二区三区,亚洲国模精品一区 | 国精品无码一区二区三区左线| 激情啪啪精品一区二区| 国产人妖在线观看一区二区| 日韩精品午夜视频一区二区三区| 亚洲精品国产suv一区88| 另类国产精品一区二区| 成人国产精品一区二区网站公司| 日本一区二区三区不卡视频| 在线观看精品一区| 女人和拘做受全程看视频日本综合a一区二区视频 | 精品人妻一区二区三区四区| 午夜无码视频一区二区三区| 亚洲一区二区三区无码影院| 国产丝袜美女一区二区三区| 日韩亚洲AV无码一区二区不卡| 日韩免费无码一区二区三区| 色噜噜狠狠一区二区三区|