【遠程調用框架】如何實現一個簡單的RPC框架(二)實現與使用

【如何實現一個簡單的RPC框架】系列文章:java

【遠程調用框架】如何實現一個簡單的RPC框架(一)想法與設計 
【遠程調用框架】如何實現一個簡單的RPC框架(二)實現與使用 
【遠程調用框架】如何實現一個簡單的RPC框架(三)優化一:利用動態代理改變用戶服務調用方式 
【遠程調用框架】如何實現一個簡單的RPC框架(四)優化二:改變底層通訊框架 
【遠程調用框架】如何實現一個簡單的RPC框架(五)優化三:軟負載中心設計與實現 
第一個優化以及第二個優化修改後的工程代碼可下載資源 如何實現一個簡單的RPC框架web

 

 

參考【遠程調用框架】如何實現一個簡單的RPC框架(一)想法與設計,對應四個模塊一共建立了四個Java工程,他們分別是:spring

  • 【ServiceAccessCenter】:一個Java Web應用,服務註冊查找中心apache

  • 【LCRPC】:LCRPC服務框架的核心部分,最終利用Maven生成一個jar包提供服務發佈以及遠程調用功能json

  • 【LCRPCTest】:服務端的測試部分,發佈一個計算器服務數組

  • 【LCRPCClientTest】:客戶端的測試部分,利用服務發佈者提供的二方包,遠程調用該計算器服務緩存

1. 服務註冊查找中心

一個Java Web應用,服務註冊查找中心tomcat

1.1 接口設計

1.1.1 服務註冊接口

  • (1)url:localhost:8080/ServiceAccessCenter/serviceRegistry.do POST請求
  • (2)參數:JSON格式的字符串,以下:
{
"interfaceName":"interfaceName",
"version":"version",
"implClassName":"imlClassName",
"ip":"ip"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(3)響應結果:true(表明註冊成功)false(表明註冊失敗)多線程

1.1.2 服務ip地址列表查詢接口

  • (1)url: 
    localhost:8080/ServiceAccessCenter/queryServiceIPsByID.do?serviceID=interfaceName_version GET請求
  • (2)參數:serviceID 服務的惟一標識
  • (3)響應結果:該服務對應的ip列表數組/空字符串,以下:
["ip","ip"]
  • 1

1.1.3 服務信息查詢接口

  • (1)url: 
    localhost:8080/ServiceAccessCenter/queryServiceInfoByID.do?serviceID=interfaceName_version GET請求
  • (2)參數:serviceID 服務的惟一標識
  • (3)響應結果:該服務的全部信息,以下:
{"interfaceName":"interfaceName","version":"version","implClassName":"imlClassName","ips":["ip","ip"],"ip":"ip"}
  • 1

1.2 類設計

1.2.1 UML類圖

這裏寫圖片描述

1.2.2 核心代碼

核心代碼主要是對註冊服務集合的管理,包括增長以及查詢。注意多線程操做的問題。 
- (1)描述服務信息的DO:ServiceInfoDOmvc

package whu.edu.lcrpc.servicecenter.entity;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 服務的描述信息,包括:
 * 服務惟一標識\實現類全限定名\ip地址列表等
 */
@Data
public class ServiceInfoDO {

    private String interfaceName;//服務對應接口名稱
    private String version;//版本號
    private String implClassName;//實現該接口的類
    private Set<String> ips = new HashSet<>();//該服務的地址
    private String ip;//某一次註冊服務的地址
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • (2)以一個單例類保存服務信息的集合:
public class ServicesSingle {
    private static ServicesSingle servicesSingle = null;
    private Map<String,ServiceInfoDO> services = null;

    private ServicesSingle(){
        services = new ConcurrentHashMap<>();
    }

    public static ServicesSingle getServiceSingle(){
        synchronized (ServicesSingle.class){
            if (servicesSingle == null){
                servicesSingle = new ServicesSingle();
            }
        }
        return servicesSingle;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • (3)對服務信息集合操做的接口:
public interface IServiceAccess {
    /**
     * 根據用戶提供的服務信息,進行服務的註冊
     * @param serviceInfo  要註冊的服務信息
     * @return
     */
    public boolean serviceRegistry(ServiceInfoDO serviceInfo);

    /**
     * 根據服務的惟一標識ID查詢服務的地址列表
     * @param serviceID
     * @return
     */
    public Set<String> queryServiceIPsByID(String serviceID);

    /**
     * 根據服務的惟一標識ID查詢服務的信息
     * @param serviceID
     * @return
     */
    public ServiceInfoDO queryServiceInfoByID(String serviceID);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • (4)接口的實現類:
/**
 * 完成服務的管理操做:註冊\查詢
 */
public class ServiceAccessImpl implements IServiceAccess{
    @Override
    public boolean serviceRegistry(ServiceInfoDO serviceInfo) {
        if (serviceInfo.getInterfaceName() == null || serviceInfo.getInterfaceName().length() ==0 ||
                serviceInfo.getImplClassName() == null || serviceInfo.getImplClassName().length() ==0 ||
                serviceInfo.getVersion() == null || serviceInfo.getVersion().length() ==0 ||
                serviceInfo.getIp() == null || serviceInfo.getIp().length() ==0)
            return false;
        String serviceID = serviceInfo.getInterfaceName() + "_" + serviceInfo.getVersion();
        if (ServicesSingle.getServiceSingle().getServices().containsKey(serviceID)){
            ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps().add(serviceInfo.getIp());
        }else {
            serviceInfo.getIps().add(serviceInfo.getIp());
            ServicesSingle.getServiceSingle().getServices().put(serviceID,serviceInfo);
        }
        return true;
    }

    @Override
    public Set<String> queryServiceIPsByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps();
    }

    @Override
    public ServiceInfoDO queryServiceInfoByID(String serviceID) {
        if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
            return null;


        return ServicesSingle.getServiceSingle().getServices().get(serviceID);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

2. LCRPC服務框架核心部分

LCRPC服務框架的核心部分,最終利用Maven生成一個jar包提供服務發佈以及遠程調用功能 
整個工程是由maven以及spring開發構建的,是一個Java工程,最終利用maven構建jar包提供用戶使用。 
pom依賴配置以下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc.</groupId>
  <artifactId>lcrpc-core</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-core</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--gson依賴-->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.2.4</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--json解析依賴-->
    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.4</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

2.1 服務框架UML類圖

這裏寫圖片描述

(看不清可訪問服務框架UML類圖

2.2 服務調用部分

服務調用端提供LCRPCConsumerImpl類給用戶使用,該類中包含兩個必要的屬性變量分別是interfaceNeme(接口名)、version(版本號),用來標識某一個LCRPCConsumer實例對象是對哪個服務的調用,同時提供方法serviceConsumer,用戶經過傳入要調用的方法名稱以及參數信息,實現對該接口某一個方法的遠程調用。LCRPCConsumer類使用一個幫助類ConsumerServiceImpl來實現整個調用過程。同時,設計了一些自定義異常,用於在程序出錯時拋出給用戶進行捕獲。

核心代碼

  • (1)類LCRPCConsumerImpl提供用戶使用,主要實現方法的遠程調用
@Data
public class LCRPCConsumerImpl implements ILCRPCConsumer {

    /**
     * 如下兩個變量共同組成服務的惟一標識
     */
    private String interfaceName;//服務對應接口的全限定名
    private String version;//服務版本號
    private IConsumerService consumerService;//初始化客戶端輔助類

    public LCRPCConsumerImpl(){
        consumerService = new ConsumerServiceImpl();
    }



    @Override
    public Object serviceConsumer(String methodName, List<Object> params) throws LCRPCServiceIDIsIllegal,LCRPCServiceNotFound,LCRPCRemoteCallException {

        //若服務惟一標識沒有提供,則拋出異常
        if (interfaceName == null || interfaceName.length() == 0
                || version == null || version.length() == 0)
            throw new LCRPCServiceIDIsIllegal();
        //step1. 根據服務的惟一標識獲取該服務的ip地址列表
        String serviceID = interfaceName + "_" + version;
        Set<String> ips = consumerService.getServiceIPsByID(serviceID);
        if (ips == null || ips.size() == 0)
            throw new LCRPCServiceNotFound();

        //step2. 路由,獲取該服務的地址,路由的結果會返回至少一個地址,因此這裏不須要拋出異常
        String serviceAddress = consumerService.getIP(serviceID,methodName,params,ips);

        //step3. 根據傳入的參數,拼裝Request對象,這裏必定會返回一個合法的request對象,因此不須要拋出異常
        LCRPCRequestDO requestDO = consumerService.getRequestDO(interfaceName,version,methodName,params);

        //step3. 傳入Request對象,序列化並傳入服務端,拿到響應後,反序列化爲object對象
        Object result = null;
        try {
            result = consumerService.sendData(serviceAddress,requestDO);
        }catch (Exception e){
            //在服務調用的過程種出現問題
            throw new LCRPCRemoteCallException(e.getMessage());
        }
        if (result == null)throw new LCRPCRemoteCallException(Constant.SERVICEUNKNOWNEXCEPTION);
        //step4. 返回object對象
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • (2)幫助類 ConsumerServiceImpl,主要實現一些輔助函數,包括服務ip列表的查詢,路由,數據的發送等。
public class ConsumerServiceImpl implements IConsumerService {
    @Override
    public Set<String> getServiceIPsByID(String serviceID) {

        //調用服務註冊查找中心的服務,獲取ip列表
        Set<String> ips = new HashSet<>();
        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.QUERYSERVICEIPSBYID + "?serviceID=" + serviceID;

        Set status = new HashSet<Integer>();
        status.add(200);
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"GET",null,null,response,status);
        if (response.length() == 0)return ips;
        ips = new Gson().fromJson(response.toString(),ips.getClass());
        return ips;
    }

    @Override
    public String getIP(String serviceID, String methodName,List<Object> params, Set<String> ips) {
        //能夠根據接口\方法\參數進行路由,這裏咱們先簡單實現,選出列表的第一個,模擬路由的過程
        String[] temparr = new String[ips.size()];
        ips.toArray(temparr);
        return temparr[0];
    }

    @Override
    public LCRPCRequestDO getRequestDO(String interfaceName, String version, String methodName, List<Object> params) {
        LCRPCRequestDO requestDO = new LCRPCRequestDO();
        requestDO.setInterfaceName(interfaceName);
        requestDO.setMethodName(methodName);
        requestDO.setParams(params);
        requestDO.setVersion(version);
        return requestDO;
    }

    @Override
    public Object sendData(String ip,LCRPCRequestDO requestDO) throws IOException, ClassNotFoundException {
        ObjectOutputStream objectOutputStream = null;
        Socket socket = null;
        ObjectInputStream objectInputStream = null;
        try {
            socket = new Socket(ip, Constant.PORT);//向遠程服務端創建鏈接
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//得到輸出流
            objectOutputStream.writeObject(requestDO);//發送序列化結果
            objectOutputStream.flush();
            socket.shutdownOutput();
            //等待響應
            objectInputStream = new ObjectInputStream(socket.getInputStream());//得到輸入流
            Object result = objectInputStream.readObject();//序列化爲Object對象
            objectInputStream.close();
            objectOutputStream.close();
            socket.close();
            return result;
        }catch (Exception e){
            throw e;
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }

        }


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

2.3 服務發佈部分

服務發佈部分,主要包括兩部分,第一是服務的監聽,第二是服務的註冊與處理。服務的監聽,經過開啓一個socket監聽線程ServerThread來實現。當有客戶端請求後,開啓新的處理線程ServerProcessThread,來進行數據的解析、服務的調用、數據響應等操做。提供給用戶使用的是類LCRPCProviderImpl,主要包含四個必要屬性變量(interfaceName、version、implClassName、ip),對要發佈的服務進行描述。該類主要提供的方法是服務的註冊。同時還設計了自定義異常類,用於在出錯時拋出,他們分別是:LCRPCServiceInfoNotComplete(要發佈的服務的信息不完整)、LCRPCServiceListenFailed(服務監聽失敗)、LCRPCServiceRegistryFailed(服務註冊失敗)。 
核心代碼

  • (1)LCRPCProviderImpl提供給用戶使用,用於服務的發佈與註冊
@Data
public class LCRPCProviderImpl implements ILCRPCProvider{

    /**
     * 如下變量爲發佈一個服務的必要變量
     */
    private String interfaceName;//服務對應接口的全限定名
    private String version;//服務版本號
    private String implClassName;//實現該服務接口的類
    private String ip;//發佈該服務的地址

    private static boolean isListened = false;//是否已經開啓監聽

    private IProviderService providerService;
    public LCRPCProviderImpl(){
        providerService = new ProviderServiceImpl();


        //開啓服務監聽端口
        if (!isListened ){
            if (providerService.startListen())
            isListened = true;
            else throw new LCRPCServiceListenFailed();
        }
    }

    public void checkInfo(){
        //先判斷服務參數信息是否完整
        if (interfaceName == null || interfaceName.length() == 0 ||
                version == null || version.length() == 0 ||
                implClassName == null || implClassName.length() ==0 ||
                ip == null || ip.length() ==0)
            throw new LCRPCServiceInfoNotComplete();
    }

    @Override
    public boolean servicePublish() {
        checkInfo();
        //step1. 註冊服務.註冊服務以前先判斷服務是否開啓,若沒有開啓,則首先開啓服務
        synchronized (LCRPCProviderImpl.class){
            if (!isListened){
                if (providerService.startListen())
                    isListened = true;
                else throw new LCRPCServiceListenFailed();
            }
            providerService.serviceregistry(interfaceName,version,implClassName,ip);
        }
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • (2)線程類ServerThread開啓socket監聽,等待客戶端的請求,開啓處理線程進行處理
public class ServerThread implements Runnable{

    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            System.out.println("已經開始監聽,能夠註冊服務了");
            while (true){
                Socket socket = serverSocket.accept();
                new Thread(new ServerProcessThread(socket)).start();//開啓新的線程進行鏈接請求的處理
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • (3)服務端鏈接處理線程ServerProcessThread,負責解析客戶端的請求,利用反射對響應服務進行方法調用,將調用結果序列化發送給調用端
public class ServerProcessThread implements Runnable {

    private Socket socket;
    public ServerProcessThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {

        //獲取客戶端的數據,並寫回
        //等待響應
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {

            //step1. 將請求數據反序列化爲request對象
            objectInputStream = new ObjectInputStream(socket.getInputStream());//得到輸入流
            LCRPCRequestDO requestDO = (LCRPCRequestDO) objectInputStream.readObject();//序列化爲Object對象

            //step2. 獲取服務接口實現類的信息
            ServiceInfoDO serviceInfoDO = ServicesSingle.getServiceSingle().getServices().get(requestDO.getInterfaceName() + "_" + requestDO.getVersion());

            //step3.利用反射建立對象,調用方法,獲得結果
            Class clz = Class.forName(serviceInfoDO.getImplClassName());
            Method methodethod = null;
            Object result = null;
            if (requestDO.getParams() != null && requestDO.getParams().size() > 0){
                Class []classes = new Class[requestDO.getParams().size()];
                Object []obj = requestDO.getParams().toArray();
                int i = 0;
                for (Object object:requestDO.getParams()){
                    if(object instanceof Integer){
                        classes[i] = Integer.TYPE;
                    }else if(object instanceof Byte){
                        classes[i] = Byte.TYPE;
                    }else if(object instanceof Short){
                        classes[i] = Short.TYPE;
                    }else if(object instanceof Float){
                        classes[i] = Float.TYPE;
                    }else if(object instanceof Double){
                        classes[i] = Double.TYPE;
                    }else if(object instanceof Character){
                        classes[i] = Character.TYPE;
                    }else if(object instanceof Long){
                        classes[i] = Long.TYPE;
                    }else if(object instanceof Boolean){
                        classes[i] = Boolean.TYPE;
                    }else {
                        classes[i] = object.getClass();
                    }

                    i++;
                }
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName(),classes);
                result = methodethod.invoke(clz.newInstance(),obj);
            }else {
                methodethod = clz.getDeclaredMethod(requestDO.getMethodName());
                result = methodethod.invoke(clz.newInstance());
            }

            //step4.將結果序列化,寫回調用端

            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//得到輸出流
            objectOutputStream.writeObject(result);//發送序列化結果
            objectOutputStream.flush();
            socket.shutdownOutput();
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(objectInputStream != null)objectInputStream.close();
                if (objectOutputStream != null)objectOutputStream.close();
                if (socket !=null )socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • (4)服務發佈的幫助類ProviderServiceImpl
public class ProviderServiceImpl implements IProviderService {
    @Override
    public boolean startListen() {
        new Thread(new ServerThread()).start();
        return true;
    }

    @Override
    public boolean serviceregistry(String interfaceName, String version, String implClassName, String ip) {
        //註冊到服務註冊查找中心的同時也要緩存到內存services
        //step1. 註冊到服務中心

        String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
                + Constant.SERVICEREGISTRY;
        Map<String,String> headers = new HashMap();
        headers.put("Content-Type","application/json");
        JSONObject param = new JSONObject();
        param.put("interfaceName",interfaceName);
        param.put("version",version);
        param.put("implClassName",implClassName);
        param.put("ip",ip);
        Set status = new HashSet<Integer>();
        status.add(200);
        StringBuilder response = new StringBuilder();
        GetRemoteInfo.getRemoteInfo(url,"POST",headers,param.toString(),response,status);

        if (response.equals("false")) throw new LCRPCServiceRegistryFailed();

        //step2. 存入到緩存
        if (ServicesSingle.getServiceSingle().getServices().containsKey(interfaceName + "_" + version)){
            ServicesSingle.getServiceSingle().getServices().get(interfaceName + "_" + version).getIps().add(ip);
        }
        else {
            ServiceInfoDO serviceInfoDO = new ServiceInfoDO();
            serviceInfoDO.setInterfaceName(interfaceName);
            serviceInfoDO.setVersion(version);
            serviceInfoDO.setImplClassName(implClassName);
            serviceInfoDO.getIps().add(ip);
            ServicesSingle.getServiceSingle().getServices().put(interfaceName + "_" + version,serviceInfoDO);
        }
        System.out.println("成功註冊服務:[" + interfaceName  + "]");

        return true;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

3. LCRPC服務框架使用

3.1 服務發佈

服務端的測試部分,發佈一個計算器服務; 
該部分爲一個基本Java應用,採用spring+maven的開發方式。

  • (1)pom文件以下,包括對LCRPC服務框架提供jar包的依賴、對spring的依賴等
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-test</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>
    <!--對LCRPC服務框架的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • (2)spring的配置文件: 
    主要是配置LCRPC的服務發佈端的bean,經過該bean來進行服務的發佈,配置以下:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--ILCRPCProvider的bean 進行配置後 自動發佈服務-->
    <bean id="lcrpcProvider" class="whu.edu.lcrpc.server.impl.LCRPCProviderImpl" init-method="servicePublish">
        <property name="implClassName" value="whu.edu.lcrpc.service.impl.CaculatorImpl"></property>
        <property name="version" value="0.1"></property>
        <property name="ip" value="127.0.0.1"></property>
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator"></property>
    </bean>
</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其中四個屬性均是必要的。同時init-method的配置也是必要的,該函數經過用戶配置爲屬性信息進行服務的發佈。 
若是是在web容器中進行服務的發佈,例如tomcat,則只須要在web.xml中配置spring的監聽,就能夠進行服務的發佈。或者在main函數中利用ApplicationContext進行配置文件的讀取。 
在本例中,爲了簡單,採用main函數的方式。代碼以下:

  • (3)main函數
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 服務端測試類,調用LCRPC接口,進行服務的發佈
 */
public class ProviderTest {

    public static void main(String[] args) {
        //直接讀取spring的配置文件就好
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • (4)下面就是須要發佈的服務對應的接口,以及接口的實現類。咱們爲了進行不一樣狀況的測試,所以寫了四個函數,參數和返回值分別是Java中的基本類型、引用類型。 
    接口代碼以下:
package whu.edu.lcrpc.service;

/**
 * Created by apple on 17/3/26.
 */

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;

/**
 * 服務對應接口
 * 該服務實現一個簡單的計算器服務,實現加減乘除四個基本功能
 */
public interface ICaculator {

    /**
     * 加
     * @param n1
     * @param n2
     * @return
     */
    public double add(double n1,double n2);

    /**
     * 減
     * @param n1
     * @param n2
     * @return
     */
    public MyResultDO minus(double n1, double n2);


    /**
     * 乘
     * @param myParamDO
     * @return
     */
    public MyResultDO multiply(MyParamDO myParamDO);

    /**
     * 除
     * @param myParamDO
     * @return
     */
    public double divide(MyParamDO myParamDO);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

接口實現類的代碼以下:

package whu.edu.lcrpc.service.impl;

import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.service.ICaculator;

/**
 * Created by apple on 17/3/26.
 */

/**
 * 服務對應接口的實現類
 */
public class CaculatorImpl implements ICaculator {
    @Override
    public double add(double n1, double n2) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return n1 + n2;
    }

    @Override
    public MyResultDO minus(double n1, double n2) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(n1 - n2);
        return myResultDO;
    }

    @Override
    public MyResultDO multiply(MyParamDO myParamDO) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyResultDO myResultDO = new MyResultDO();
        myResultDO.setResult(myParamDO.getN1() * myParamDO.getN2());
        return myResultDO;
    }

    @Override
    public double divide(MyParamDO myParamDO) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return myParamDO.getN2() / myParamDO.getN1();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

最終該工程會生成一個二方包提供給服務調用端使用。

3.2 服務調用

客戶端的測試部分,利用LCRPC提供的包以及服務發佈者提供的二方包,遠程調用該計算器服務。

  • (1)pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>whu.edu.lcrpc</groupId>
  <artifactId>lcrpc-clienttest</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lcrpc-clienttest</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.3.3.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--lombok依賴-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>

    <!--對服務發佈者二方包的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-test</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--對LCRPC服務框架的依賴-->
    <dependency>
      <groupId>whu.edu.lcrpc</groupId>
      <artifactId>lcrpc-core</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • (2)spring配置文件,主要是配置LCRPC提供的客戶端接口,利用該bean,進行方法的調用,每個consumer的bean表明對一個服務接口的調用
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="rpcConsumer" class="whu.edu.lcrpc.server.impl.LCRPCConsumerImpl">
        <property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator" ></property>
        <property name="version" value="0.1"></property>
    </bean>
    <bean id="ConsumerTest" class="whu.edu.lcrpc.app.ConsumerTest"></bean>


</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

屬性interfaceName以及version是必要的,標識該bean是對哪個遠程服務的調用。

  • (3)服務調用幫助類,在該類中咱們利用LCRPC提供的接口,對計算器服務進行調用,分別調用該服務的加減乘除四個方法
package whu.edu.lcrpc.app;

/**
 * Created by apple on 17/3/26.
 */

import lombok.Data;
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.server.ILCRPCConsumer;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 客戶端測試類,調用LCRPC接口,進行服務的調用
 */
@Data
public class ConsumerTest {

    @Resource
    ILCRPCConsumer rpcConsumer;
    public void add() {
        //調用接口 進行加減乘除
        List<Object> params = new ArrayList<>();
        params.add(1.0);
        params.add(2.0);
        Double result = (Double) rpcConsumer.serviceConsumer("add",params);
        System.out.println("add: " + result);

    }

    public void minus(){
        List<Object> params = new ArrayList<>();
        params.add(1.0);
        params.add(2.0);
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("minus",params);
        System.out.println("minus: " + result.getResult());
    }

    public void multiply(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1(1.0);
        myParamDO.setN2(2.0);
        params.add(myParamDO);
        MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("multiply",params);
        System.out.println("multiply: " + result.getResult());
    }

    public void divide(){
        List<Object> params = new ArrayList<>();
        MyParamDO myParamDO = new MyParamDO();
        myParamDO.setN1(1.0);
        myParamDO.setN2(2.0);
        params.add(myParamDO);
        Double result = (Double) rpcConsumer.serviceConsumer("divide",params);
        System.out.println("divide: " + result);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • (4)運行主類,開啓四個線程,同時對四個方法進行遠程調用
package whu.edu.lcrpc.app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by apple on 17/3/27.
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ConsumerTest consumerTest = (ConsumerTest) context.getBean("ConsumerTest");
        new Thread(()->{
            consumerTest.add();
        }).start();
        new Thread(()->{
            consumerTest.minus();
        }).start();
        new Thread(()->{
            consumerTest.multiply();
        }).start();
        new Thread(()->{
            consumerTest.divide();
        }).start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3.3 效果

  • (1)首先開啓服務註冊查找中心
  • (2)服務測試端,運行main函數進行服務的發佈,運行後,服務發佈成功,以下圖所示: 
    這裏寫圖片描述
  • (3)服務調用端測試,運行main函數進行服務遠程調用,運行後,服務調用成功,以下圖所示,對1,2兩個數字開啓四個線程分別進行了加減乘除運算,四個結果同時輸出:

這裏寫圖片描述

注意:全部工程代碼可訪問資源:實現本身的遠程調用框架,進行下載

未完待續。。。。這只是服務框架的初步實現版本,期待優化提高後的第二個版本~

值得期待的是:  (1)怎樣利用動態代理,使得用戶與訪問本地接口同樣調用遠程服務  (2)當鏈接增多,服務是否能夠支撐?須要改變IO模式。

相關文章
相關標籤/搜索