搞懂跨系统通信SOA架构设计

811 阅读14分钟

前言

  今天就拿车载来讲,各汽车主机厂家会根据自身的设计理念差异将整车划分成不同的域。博世、大陆等传统 Tier1 划分了5个域:动力域、底盘域、车身域、座舱域和自动驾驶域,也有的(如大众MEB平台、东软睿驰等)把动力域、底盘域和车身域融合为整车控制域,基于安全性和独立性要求,不同的系统之间数据不能直接访问。但是不同域下的不同系统之间,同一域下的不同系统之间的通信需求现实存在。现有的解决方案主要是基于TCP/IP协议栈的Socket(套接字)通讯。

一、SOA架构设计

  SOA,或Service-Oriented Architecture(面向服务的架构),是一种软件架构模式,旨在促进系统中不同组件之间的松耦合和重用性。在 SOA 中,软件系统被划分为多个服务,每个服务代表一个具体的业务功能或操作。这些服务通过标准化的接口进行通信,可以独立部署、维护和扩展:

  • 服务:服务是 SOA 架构的基本构建块。每个服务代表一个独立的业务功能或操作。服务通常具有清晰的接口,定义了其提供的功能和数据格式。

  • 松耦合:SOA 旨在实现松耦合,这意味着系统的不同部分可以独立开发、部署和维护,而不会对其他部分造成影响。这使得系统更加灵活且容易扩展。

  • 标准化接口:每个服务都通过标准化的接口提供其功能。这些接口通常是基于开放标准的,如Web服务描述语言(WSDL)或RESTful API。

  • 服务注册与发现:在 SOA 中,服务通常需要在注册表或目录中进行注册,以便其他组件能够找到并使用它们。服务发现机制用于在运行时查找服务的位置和接口。

  • 重用性:SOA 提倡服务的重用,这意味着相同的服务可以在不同的应用程序或系统中使用。这有助于减少重复开发和提高效率。

  • 跨平台通信:SOA 允许不同的系统和应用程序在不同的平台和编程语言中进行通信。这通过使用标准的协议和数据格式来实现。

  • 安全性:SOA 架构强调安全性,确保只有经过身份验证和授权的组件才能访问服务。

  • 错误处理:SOA 考虑了错误处理和异常处理机制,以确保系统的可靠性和稳定性。

SOA.png

二、Android安全机制

2.1、权限分类

  DAC(Discretionary Access Control)和 MAC(Mandatory Access Control)是两种不同的访问控制模型,用于管理操作系统和应用程序对系统资源的访问权限。它们之间的主要区别在于授权的方式和级别:

2.1.1 DAC(Discretionary Access Control)

  • DAC 是一种相对较简单的访问控制模型,它授权的权力由资源的所有者决定。每个资源(如文件、目录、进程)都有一个所有者,所有者可以自行决定谁可以访问这些资源,以及如何访问。

  • 在 DAC 模型中,资源的所有者可以分配权限给其他用户或组。这些权限通常包括读取、写入和执行等。其他用户可以请求访问资源,但只有资源的所有者或被赋予适当权限的用户才能获得访问权限。

  • DAC 模型适用于多用户系统,但容易受到滥用和误用。因为权限由资源的所有者决定,所以如果资源的所有者不谨慎,可能会导致系统安全性问题。

2.1.2 MAC(Mandatory Access Control)

  • MAC 是一种更严格的访问控制模型,它不由资源的所有者决定,而是由系统管理员或安全政策规定。在 MAC 模型中,资源的访问权限是强制执行的,不受资源的所有者控制。

  • MAC 模型通常使用标签或安全上下文来标识资源和用户。每个资源和用户都分配有一个唯一的安全标签。然后,系统根据预定义的安全政策来决定哪些标签可以访问哪些资源。

  • MAC 模型适用于需要严格安全控制的环境,如政府、军事和高度敏感的系统。它可以防止滥用权限,但也需要更复杂的配置和管理。

2.2、SEAndroid管理策略(MAC)

  Android中使用的MAC机制就是SEAndroid。SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)在Linux社区的帮助下设计的一个Linux历史上最杰出的安全系统,是一种MAC机制(Mandatory Access Control,强制访问控制)。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。   SEAndroid安全机制又称为是基于TE(Type Enforcement)策略的安全机制。在/system/sepolicy目录中,所有以.te为后缀的文件均为策略配置文件。 策略编写流程:

2.2.1 编写.te文件

我们来看看zxx-server进程,因此在/system/sepolicy/private目录下会有一个zxx-server.te作为该进程的策略文件。文件内容如下:

# 声明zxx-server类型,并将domain属性关联到该类型 (进程)
#必须具备mlstrustedsubject属性才具备unix_stream_socket {connectto}
#参考:https://blog.csdn.net/a572423926/article/details/123209225
typeattribute zxx-server, domain,coredomain,mlstrustedsubject;
#  声明zxx-server_exec类型(可执行文件,类型要与file_contexts中的相同)
typeattribute zxx-server_exec, exec_type, file_type, system_file_type;

init_daemon_domain(zxx-server)

#allow语句表示允许的权限。
allow zxx-server self:tcp_socket { read write getattr getopt setopt shutdown create bind connect name_connect };
allow zxx-server self:netlink_route_socket {create};
allow zxx-server fwmarkd_socket:sock_file {write};
allow zxx-server port:tcp_socket {name_connect};
allow zxx-server netd:unix_stream_socket {connectto};
allow zxx-server self:capability {net_raw};

2.2.2 执行文件分配上下文

为文件分配安全上下文:file_contexts 文件列出了 Android 系统中的各种文件以及它们的安全上下文标签。这有助于 SELinux 确定哪些进程(主体)可以访问这些文件以及以何种方式访问,需要我们在olicy/private/file_contexts中声明SEAndroid系统文件的安全上下文:

//在Android.bp编译底下在/system/bin/有个可执行程序
/system/bin/zxx-server u:object_r:name-server_exec:s0                     

2.2.3 检查缺少权限

运行某个进程,如果该进程不具备对应的权限,则可以在logcat 或者在执行 adb root 后执行 adb shell dmesg查看:

avc: denied { bind } for pid=417 comm="zxx-server" scontext=u:r:name-server:s0 tcontext=u:r:name-server:s0 tclass=tcp_socket permissive=0
    
avc: denied { connectto } for pid=417 comm="zxx-server" scontext=u:r:name-server:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket permissive=0 
说明案例
缺少什么权限{ bind }
谁缺少权限scontext=u:r:name-server:s0
对谁缺少权限tcontext=u:r:name-server:s0
什么类型的权限tclass=tcp_socket

此时就需要在TE文件中声明:

#allow [谁缺少权限]  [对谁缺少权限]:[什么类型的权限] [缺少什么权限]
#当sconext与tcontext都是自己时候,可以将 [对谁缺少权限]写为:self
allow zxx-server self:tcp_socket {bind}
allow zxx-server netd:unix_stream_socket {connectto};

三、跨系统通信方案

大众在FDBUS基础上开发通信中间件完成系统间的通信。

FDBus:gitee.com/jeremyczhen…

Fast Distributed Bus,基于Socket(Unix Domain和TCP)的快速分布式总线。

UDS:Unix Domain Socket,专用于IPC。Zygote中的Local Socket即为UDS。

FDBUS为了更方便的进行寻址,允许不通过IP+端口或者固定UDS地址,而是采用域名的方式进行寻址。类似于在浏览器输入www.baidu.com,与百度完成通信,则需要进行DNS解析,将域名转化为IP地址。

FDBUS同样提供了name-server ,负责管理server名字(域名)与地址(IP)的映射,和DNS服务器一样,完成server名字到IP的解析。

host-server.png

3.1 部署name-server

name--server和Android中的ServiceManager一样,都是负责完成服务的注册与查询。与ServiceManager一样,系统启动第一时间就需要启动该服务进程,以便于其他进程进行服务注册与查询。因此我们需要将name-server部署到init.rc中,让其第一时间随系统而启动。

系统启动.png

3.2 name-server编译

将FDBUS放入Android AOSP源码 /frameworks/native/services 中:

不是必须放入该目录,这里以此目录为例。

nameserver目录.png

打开Android.bp文件,将其修改为:

//=====================================================================================
//                   makefile to build fdbus in aosp source tree                      |
//=====================================================================================

//=====================================================================================
//                           build libfdbus.so                                  |
//=====================================================================================

SRC_FILES = [
    "fdbus/CBaseClient.cpp",
    "fdbus/CFdbBaseObject.cpp",
    "fdbus/CFdbMessage.cpp",
    "fdbus/CFdbSimpleSerializer.cpp",
    "fdbus/CBaseEndpoint.cpp",
    "fdbus/CFdbCJsonMsgBuilder.cpp",
    "fdbus/CFdbSessionContainer.cpp",
    "log/CLogProducer.cpp",
    "fdbus/CBaseServer.cpp",
    "fdbus/CFdbContext.cpp",
    "fdbus/CFdbBaseContext.cpp",
    "fdbus/CFdbSession.cpp",
    "fdbus/CFdbMsgDispatcher.cpp",
    "fdbus/CEventSubscribeHandle.cpp",
    "fdbus/CFdbUDPSession.cpp",
    "fdbus/CBaseSession.cpp",
    "fdbus/CFdbWatchdog.cpp",
    "fdbus/CFdbEventRouter.cpp",
    "platform/CEventFd_eventfd.cpp",
    "platform/linux/CBaseMutexLock.cpp",
    "platform/linux/CBasePipe.cpp",
    "platform/linux/CBaseSysDep.cpp",
    "platform/linux/CBaseThread.cpp",
    "platform/socket/CBaseSocketFactory.cpp",
    "platform/socket/linux/CLinuxSocket.cpp",
    "platform/socket/sckt-0.5/sckt.cpp",
    "platform/socket/CGenericClientSocket.cpp",
    "platform/socket/CGenericServerSocket.cpp",
    "platform/socket/CGenericSession.cpp",
    "platform/socket/CGenericSocket.cpp",
    "platform/socket/CGenericTcpSession.cpp",
    "platform/socket/CGenericUdpSession.cpp",
    "platform/socket/CGenericUdpSocket.cpp",
    "security/CApiSecurityConfig.cpp",
    "security/CFdbToken.cpp",
    "security/CFdbusSecurityConfig.cpp",
    "security/CHostSecurityConfig.cpp",
    "security/CServerSecurityConfig.cpp",
    "utils/fdb_option_parser.cpp",
    "worker/CBaseEventLoop.cpp",
    "worker/CBaseWorker.cpp",
    "worker/CFdEventLoop.cpp",
    "worker/CThreadEventLoop.cpp",
    "worker/CSysFdWatch.cpp",
    "utils/CBaseNameProxy.cpp",
    "fdbus/CIntraNameProxy.cpp",
    "server/CAddressAllocator.cpp",
    "log/CLogPrinter.cpp",
    "log/CFdbLogCache.cpp",
    "utils/cJSON/cJSON.c",
    "fdbus/CFdbAFComponent.cpp",
    "datapool/CDataPool.cpp",
    "datapool/CDpClient.cpp",
    "datapool/CDpServer.cpp",
]


cc_library_shared {
    name: "libfdbus",
    vendor_available: true,

    cppflags: [
    	"-Wno-non-virtual-dtor",
        "-frtti",
        "-fexceptions",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
        "-DCONFIG_SOCKET_CONNECT_TIMEOUT=0",
        "-DCONFIG_LOG_TO_STDOUT",
        "-DCONFIG_FDB_NO_RTTI",
        "-DCONFIG_FDB_MESSAGE_METADATA",
        "-DFDB_CONFIG_UDS_ABSTRACT",
        "-DCFG_ALLOC_PORT_BY_SYSTEM",
    ],
    cflags: [
    	"-Wno-non-virtual-dtor",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
        "-DCONFIG_SOCKET_CONNECT_TIMEOUT=0",
        "-DCONFIG_LOG_TO_STDOUT",
        "-DCONFIG_FDB_MESSAGE_METADATA",
        "-DFDB_CONFIG_UDS_ABSTRACT",
        "-DCFG_ALLOC_PORT_BY_SYSTEM",
    ],

    shared_libs: [
        "liblog",
        "libutils",
    ],

    srcs: SRC_FILES,

    export_include_dirs: ["public"],

    local_include_dirs: [],
}

//=====================================================================================
//                             build libfdbus-jni.so                                  |
//=====================================================================================
cc_library_shared {
    name: "libfdbus-jni",

    cppflags: [
    	"-Wno-non-virtual-dtor",
        "-frtti",
        "-fexceptions",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
        "-DCFG_JNI_ANDROID",
        "-DFDB_CFG_KEEP_ENV_TYPE",
    ],
    cflags: [
    	"-Wno-non-virtual-dtor",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
        "-DCFG_JNI_ANDROID",
    ],
    srcs: [
        "jni/src/cpp/CJniClient.cpp",
        "jni/src/cpp/CJniMessage.cpp",
        "jni/src/cpp/CJniServer.cpp",
        "jni/src/cpp/FdbusGlobal.cpp",
        "jni/src/cpp/CJniAFComponent.cpp",
    ],

    shared_libs: ["libfdbus"],

    include_dirs: [
        "frameworks/base/core/jni",
        "frameworks/base/core/jni/include",
    ],
}


//=====================================================================================
//                                  build name-server                                 |
//=====================================================================================
cc_binary {
    name: "name-server",
    vendor_available: true,
    //init_rc: ["fdbus-name-server.rc"],
    cppflags: [
        "-Wno-non-virtual-dtor",
        "-frtti",
        "-fexceptions",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
    ],
    cflags: [
    	"-Wno-non-virtual-dtor",
        "-Wno-unused-parameter",
        "-D__LINUX__",
        "-DFDB_CFG_SOCKET_PATH=\"/data/misc/fdbus\"",
        "-DCONFIG_DEBUG_LOG",
    ],
    srcs: [
        "server/main_ns.cpp",
        "server/CNameServer.cpp",
        "server/CInterNameProxy.cpp",
        "server/CIntraHostProxy.cpp",
        "server/CBaseHostProxy.cpp",
        "server/CSvcAddrUtils.cpp",
        "server/CNameProxyContainer.cpp",
        "security/CServerSecurityConfig.cpp",
    ],

    shared_libs: [
        "libfdbus",
        "liblog",
        "libutils",
    ],

}

上述脚本会编译出libfdbus.so、libfdbus-jni.so与name-server可执行文件。其中so需要放入编译出的系统ROM中的/system/lib目录,而name-server需要放入/system/bin。

为了在编译ROM时能编译fdbus,需要在 /build/target/product/base.mk 中的中加入一个base_system_custom.mk文件专门中的中加入:libfdbus、libfdbus-jni与name-server。

basemk.jpg

PRODUCT_PACKAGES += \
    libfdbus \
    libfdbus-jni \
    name-server \

3.3 name-server启动

为了让name-server开机启动,需要在 init.rc 中配置启动脚本。

把FDBUS源码根目录下的fdbus-name-server.rc 删掉!

打开 /system/core/rootdir/init.rc 在文件最后加入:

#1、在挂载 /data 后创建/data/misc/fdbus 并设置system用户组权限
on post-fs-data
    mkdir /data/misc/fdbus 0755 system system
#2、启动name-server
service name-server /system/bin/name-server -u tcp://139.224.136.101:60000 -n android
    class core
    user root
    group system root inet
    writepid /dev/cpuset/system-background/tasks

1、因为FDBUS支持uds与tcp。在当前系统中其他进程需要连接name-server会采用UDS的方式,/data/misc/fdbus 则为name-server的uds固定地址。

mkdir /data/misc/fdbus 0755 system system:创建文件并设置权限。

2、-u 参数后文解释,-n 参数为当前name-server的别名可以随意传递。

class core:当前服务属主,系统能根据属主统一管理同属主下所有的进程,同一个class的所有服务必须同时启动或者停止。 group inet:当前服务的用户组。 inet 组表示允许网络访问!

在/framework/base/data/etc/platform.xml 中可以查看权限对应的用户组:

package权限.png

3.4 name-server权限

更详细内容见《SEAndroid安全机制》

完成上述配置后,系统启动就会拉起name-server,但是此时name-server还无法正常工作。Android是建立在标准的Linux Kernel基础上,通过Linux的 SELinux(SEAndroid)进行访问权限控制。name-server需要访问tcp_socket、unix_stream_socket则必须开启SElinux的访问限制。

system/sepolicy/private/file_contexts 的最后一行加入:

/system/bin/name-server     u:object_r:name-server_exec:s0

接着在system/sepolicy/private/下创建name-server.te 在文件中写入:

type name-server, domain, mlstrustedsubject;

type name-server_exec, exec_type, file_type;

allow name-server self:tcp_socket { read write getattr getopt setopt shutdown create bind connect name_connect };
allow name-server self:netlink_route_socket {create};
allow name-server fwmarkd_socket:sock_file {write};
allow name-server port:tcp_socket {name_connect};
allow name-server netd:unix_stream_socket {connectto};
allow name-server self:capability {net_raw};

init_daemon_domain(name-server)

同时name-server需要与netd进程(网络管理进程)交互,还需要在当前目录下的netd.te 增加:

typeattribute netd coredomain;
typeattribute netd domain_deprecated;

#增加==============
allow netd name-server:fd {use};
allow netd name-server:tcp_socket {getopt};
allow netd name-server:tcp_socket {setopt};
allow netd name-server:tcp_socket {read write};
#==============
init_daemon_domain(netd)

# Allow netd to spawn dnsmasq in it's own domain
domain_auto_trans(netd, dnsmasq_exec, dnsmasq)

# Allow netd to start clatd in its own domain
domain_auto_trans(netd, clatd_exec, clatd)

如果需要普通APP使用name-server, 还需要在system/sepolicy/private/untrusted_app.te中加入:

# ......
create_pty(untrusted_app)
# 加入此处规则
allow untrusted_app name-server:unix_stream_socket{connectto};

3.5 FDBUS API

在上一步,我们不仅完成了name-server的部署,同时编译出libfdbus-jni.so,该动态库为FDBUS的Java层API的JNI封装。为了在Java中完成FDBUS的使用,可以将FDBUS集成进入Framework与SDK中。

3.5.1 Framework/SDK 集成

将FDBUS中的jni\src\java 下的Java代码放入AOSP源码中的/frameworks/base/core/java

fdbus-java-api.png

并修改AOSP源码中的`/build/soong/scripts/check_boot_jars/package_allowed_list.txt

接着在/framework/base/Android.dp 中找到 **packages_to_document **,并在其中加入ipc.fdbus 1694098955411.jpg

3.5.2 系统内IPC通信

按照《Android源码编译》课程,编译Android源码,并将ROM刷至设备。然后按照《自定义系统服务》将SDK制作完成,接下来就可以在APP中使用FDBUS完成IPC通信:

服务端:

public class IPCServer implements FdbusServerListener {
    private static final String TAG = "IPCServer";
    private FdbusServer server;

    public IPCServer() {
        server = new FdbusServer();
        server.setListener(this);
        server.bind("svc://enjoy");
    }

    public void destroy() {
        server.unbind();
        server.destroy();
    }

    @Override
    public void onOnline(int sid, boolean is_first, int qos) {
        Log.i(TAG, "onOnline: " + sid);
    }

    @Override
    public void onOffline(int sid, boolean is_last, int qos) {
        Log.i(TAG, "onOffline: " + sid);
    }

    @Override
    public void onInvoke(FdbusMessage fdbusMessage) {
        String s = new String(fdbusMessage.byteArray());
        Log.i(TAG, "onInvoke: " + fdbusMessage.code() + " " + s);
        fdbusMessage.reply(("服务端响应 ==>" + s).getBytes());
    }

    @Override
    public void onSubscribe(FdbusMessage msg, ArrayList<SubscribeItem> sub_list) {
        for (SubscribeItem subscribeItem : sub_list) {
            Log.i(TAG, "客户端注册事件监听: " + subscribeItem.code() + " " + subscribeItem.topic());
        }
    }

    public FdbusServer getService() {
        return server;
    }


}

客户端:

public class IPCClient implements FdbusClientListener {

    private static final String TAG = "IPCClient";
    private final FdbusClient mClient;

    public IPCClient() {
        mClient = new FdbusClient();
        mClient.setListener(this);

    }

    public FdbusClient getClient() {
        return mClient;
    }

    @Override
    public void onOnline(int sid, int i1) {
        Log.i(TAG, "onOnline: " + sid);

        ArrayList<SubscribeItem> subscribe_items = new ArrayList<SubscribeItem>();
        subscribe_items.add(SubscribeItem.newEvent(100));
        mClient.subscribe(subscribe_items);
    }

    @Override
    public void onOffline(int sid, int i1) {
        Log.i(TAG, "onOffline: " + sid);
    }

    @Override
    public void onReply(FdbusMessage fdbusMessage) {
        Log.i(TAG, "onReply: " + new String(fdbusMessage.byteArray()));
    }

    @Override
    public void onGetEvent(FdbusMessage fdbusMessage) {
//        server.initEventCache()
//
        Log.i(TAG, "onGetEvent: " +new String(fdbusMessage.byteArray()));
    }

    @Override
    public void onBroadcast(FdbusMessage fdbusMessage) {
        Log.i(TAG, "onBroadcast: " + new String(fdbusMessage.byteArray()));
        fdbusMessage.destroy();
    }

}


//......
IPCClient ipcClient = new IPCClient();
ipcClient.getClient().connect("svc://enjoy");
// 发送消息给服务端
FdbusMessage fdbusMessage = ipcClient.getClient().invokeSync(1, "我是客户端1".getBytes());
ipcClient.getClient().invokeAsync(2, "我是客户端2".getBytes(), new FdbusAppListener.Action() {
            @Override
            public void handleMessage(FdbusMessage fdbusMessage) {
                Log.i(TAG, "ipc: " + new String(fdbusMessage.byteArray()));
            }
});
ipcClient.getClient().send(3, "我是客户端3".getBytes());

3.5.3 跨系统通信

车载系统中,由于Hypervisor的采用,有的域内可能会有多个节点。如智能座舱域,一个SOC芯片上可能会同时运行QNX和Android,虽然位于同一个SOC上,但还是被认为是两个节点。这两个节点必须打通,才能相互通信!

跨系统.png

在Android与QNX系统中各自部署name-server,Android与QNX的name-server只能管理系统自身内部服务,为了打通两个系统,FDBUS提供了host-server。

就好像DNS解析,本地DNS服务器无法解析,就会请求远程服务器。如果说name-server是本地DNS服务器,那么host-server就是远程DNS服务器。

host-server.png

3.6 host-server部署

host-server可以搭建在域内,也可以搭建在域外,只要保证Android与QNX都能够访问即可。

我们以阿里云服务器为例,现在让Android与阿里云使用FDBUS完成通信。Android端的name-server已经搭建完成,我们还需要在阿里云上搭建name-server与host-server。

按照:fdbus.readthedocs.io/en/latest/r… 中的介绍,在阿里云中编译fdbus:

阿里云.png

然后启动服务:

#若没有执行权限,则先执行:sudo chmod +x host_server
./host_server & 
./name_server &

服务启动后随时可以执行 lssvc 查看: lssvc.png

可以看到host-server端口为:60000,配置阿里云安全规则,打开阿里云60000端口访问。在Android端的init.rc中配置的name-server填写阿里云的IP与60000端口:

#1、在挂载 /data 后创建/data/misc/fdbus 并设置system用户组权限
on post-fs-data
    mkdir /data/misc/fdbus 0755 system system
#2、启动name-server
service name-server /system/bin/name-server -u tcp://139.224.136.101:60000 -n android
    class core
    group system inet
    writepid /dev/cpuset/system-background/tasks

-u 参数就是阿里云host-server的地址

-n 则为在当前服务端别名,相当于当前的name-server在host_server中的用户名

至此,基于host-server即可完成跨系统的通信!

感谢

享学课堂 码牛学院