伴随 SOFARPC 的开源,我们也开源了 sofa-bolt-node 和 sofa-rpc-node 两个 Nodejs RPC 基础模块。但细心的用户可能注意了我们在文档里面写到并不希望大家直接使用它们,并预告会在 Eggjs 里提供 RPC 最佳实践。现在这个最佳实践来了,它就是:
本文通过 Step by Step 的形式介绍了 Eggjs 和 SOFA(Java)是如何进行互联互通的,涵盖了 RPC 的服务发现、接口定义、本地代理生成、服务端实现等各方面,期望展现给你一个相对完整的 Nodejs RPC 解决方案。考虑到社区的接受度、多语言友好性等因素,接下来的示例采用 protobuf 作为 RPC 的序列化方式。
一、准备工作
注意: 本文以 macOS 为例,其他操作系统的安装、使用方法请自行 google。
安装
nodejs >= 8.0.0执行安装
安装
zookeeper
$ brew install zookeeper
- 启动
zookeeper服务
$ zkServer startZooKeeper JMX enabled by defaultUsing config: /usr/local/etc/zookeeper/zoo.cfgStarting zookeeper ... STARTED
- 克隆 SOFARPC Java 的示例仓库
SOFARPC 的更多信息可以参考官方文档
git clone git@github.com:gxcsoccer/sofa-rpc-java-demo.git
- 安装
egg-init
$ npm i egg-init -g
二、创建工程
- 通过
egg-init初始化项目脚手架,选择 simple 模板,接下来根据实际情况填写必要信息
$ egg-init? Please select a boilerplate type (Use arrow keys)──────────────❯ simple - Simple egg app boilerplatets - Simple egg && typescript app boilerplateempty - Empty egg app boilerplateplugin - egg plugin boilerplateframework - egg framework boilerplate
- 进入生成好的项目目录,并安装依赖
$ cd /rpc-demo$ npm i
- 安装
egg-sofa-rpc插件和egg-rpc-generator工具
$ npm i egg-sofa-rpc --save$ npm i egg-rpc-generator --save-dev
- 配置
package.json的 scripts 节点,增加一个命令 rpc 如下
{"scripts": {"start": "egg-scripts start --daemon --title=egg-server-rpc-demo","stop": "egg-scripts stop --title=egg-server-rpc-demo","dev": "egg-bin dev","debug": "egg-bin debug","test": "npm run lint -- --fix && npm run test-local","test-local": "egg-bin test","cov": "egg-bin cov","lint": "eslint .","ci": "npm run lint && npm run cov","autod": "autod","rpc": "egg-rpc-generator"}}
- 配置
config/plugin.js开启egg-sofa-rpc插件
// config/plugin.jsexports.sofaRpc = {enable: true,package: 'egg-sofa-rpc',};
三、定义接口
protobuf 有自己的接口定义语言,详细可以参考官方文档。
# ProtoService.protosyntax = "proto3";package com.alipay.sofa.rpc.protobuf;option java_multiple_files = true; // 可选option java_outer_classname = "ProtoServiceModels"; // 可选service ProtoService {rpc echoObj (EchoRequest) returns (EchoResponse) {}}message EchoRequest {string name = 1;Group group = 2;}message EchoResponse {int32 code = 1;string message = 2;}enum Group {A = 0;B = 1;}
上面这个 ProtoService.proto 文件定义了一个服务:com.alipay.sofa.rpc.protobuf.ProtoService,它有一个叫 echoObj 的方法,入口参数类型是 EchoRequest,返回值类型是 EchoResponse。
四、调用 Java 暴露的 RPC 服务
1、启动 Java 服务端
进入上面克隆的 Java 示例仓库,运行 ProtobufServiceServerMain
2、配置服务发现参数
我们默认的服务发现依赖于 zookeeper,所以需要配置一个 zk 的地址。在 config/config.{env}.js 中配置如下:
// config/config.default.js'use strict';exports.sofaRpc = {registry: {address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口},};
3、获取接口定义
在 egg 项目根目录下创建 proto 目录,然后将上面定义的 ProtoService.proto 文件放到里
.├── app│ ├── controller│ │ └── home.js│ └── router.js├── config│ ├── config.default.js│ └── plugin.js├── package.json└── proto└── ProtoService.proto
4、配置要调用的接口
在 config/proxy.js 中配置要调用的服务信息
'use strict';module.exports = {services: [{appName: 'sofarpc',api: {ProtoService: 'com.alipay.sofa.rpc.protobuf.ProtoService',},}],};
appName(必选): 服务提供方的应用名,如果没有可以任意起一个api(必选): 接口列表,是一个 key-value 键值对,key 是生成的 proxy 文件名,value 是接口名(如果要跟精细的配置也可以是一个对象)
config/proxy.js 详细的配置说明可以参考文档
5、生成调用代理
在根目录下运行 npm run rpc,生成调用的 proxy 文件
$ npm run rpc> rpc-demo@1.0.0 rpc /egg-rpc-demo> egg-rpc-generator[EggRpcGenerator] framework: /egg-rpc-demo/node_modules/egg, baseDir: /egg-rpc-demo[ProtoRPCPlugin] found "com.alipay.sofa.rpc.protobuf.ProtoService" in proto file[ProtoRPCPlugin] save all proto info into "/egg-rpc-demo/run/proto.json"
运行成功以后,会发现生成了两个文件
app/proxy/ProtoService.js- 调用服务的代理文件run/proto.json- 从 .proto 文件中导出的接口信息,是一个 json 格式文件
.├── app│ ├── controller│ │ └── home.js│ ├── proxy│ │ └── ProtoService.js│ └── router.js├── config│ ├── config.default.js│ ├── plugin.js│ └── proxy.js├── package.json├── proto│ └── ProtoService.proto└── run└── proto.json
生成的 app/proxy/ProtoService.js 文件内容如下(注意:不要手动去改这个文件):
// Don't modified this file, it's auto created by egg-rpc-generator'use strict';const path = require('path');/* eslint-disable *//* istanbul ignore next */module.exports = app => {const consumer = app.sofaRpcClient.createConsumer({interfaceName: 'com.alipay.sofa.rpc.protobuf.ProtoService',targetAppName: 'sofarpc',version: '1.0',group: 'SOFA',proxyName: 'ProtoService',responseTimeout: 3000,});if (!consumer) {// `app.config['sofarpc.rpc.service.enable'] = false` will disable this consumerreturn;}app.beforeStart(async() => {await consumer.ready();});class ProtoService extends app.Proxy {constructor(ctx) {super(ctx, consumer);}async echoObj(req) {return await consumer.invoke('echoObj', [ req ], {ctx: this.ctx,codecType: 'protobuf',});}}return ProtoService;};/* eslint-enable */
6、调用代理类,实现业务逻辑
上面定义的这个 ProtoService 这个类,会挂载在 app.proxyClasses 上。通过 ctx.proxy.protoService(注意这里是小驼峰)可以访问它的实例,这样我们就可以在业务中调用 RPC 的服务了,例如:下面我们在 home controller 调用 ProtoService 的 echoObj 方法
// app/controller/home.js'use strict';const Controller = require('egg').Controller;class HomeController extends Controller {async index() {const { ctx } = this;const res = await ctx.proxy.protoService.echoObj({name: 'gxcsoccer',group: 'A',});ctx.body = res;}}module.exports = HomeController;
7、启动应用,调试
$ npm run dev
在浏览器中访问 http://127.0.0.1:7001/,得到下面的结果,说明成功了

五、暴露 RPC 服务给 Java 调用
这回换做 Nodejs 来暴露同样的服务,Java 端作为消费者
1、配置服务发现参数
和上面作为调用者的配置一样
// config/config.default.js'use strict';exports.sofaRpc = {registry: {address: '127.0.0.1:2181', // zk 地址指向本地 2181 端口},};
2、定义接口
同样需要先定义接口,然后将 .proto 文件放到 proto 目录下,然后运行 npm run rpc,这些和上面作为调用者时都一样
3、配置 RPC 服务端的参数
通过 config/config.{env}.js 配置 RPC 服务端的参数
// config/config.default.js'use strict';exports.sofaRpc = {server: {namespace: 'com.alipay.sofa.rpc.protobuf',},};
其中最主要的配置就是 namespace,其他配置都可以缺省:
namespace(必选): 接口的命名空间,所有的暴露的接口默认都在该命名空间下selfPublish(可选): 是否每个 worker 进程独立暴露服务。nodejs 多进程模式下,如果多个进程共享一个端口,在 RPC 这种场景可能造成负载不均,所以 selfPublish 默认为 true,代表每个进程独立监听端口和发布服务port(可选): 服务监听的端口(注意:在 selfPublish=true 时,监听的端口是基于这个配置生成的)maxIdleTime(可选): 客户端连接如果在该配置时长内没有任何流量,则主动断开连接responseTimeout(可选): 服务端建议的超时时长,具体的超时还是以客户端配置为准codecType(可选): 推荐的序列化方式,默认为 protobuf
4、实现接口逻辑
在 app/rpc 目录下创建 ProtoService.js 文件,用于实现接口逻辑
'use strict';exports.echoObj = async function(req) {return {code: 200,message: 'hello ' + req.name + ', you are in ' + req.group,};};
5、启动应用,发布服务
$ npm run dev
6、Java 作为客户端调用服务
进入上面克隆的 Java 示例仓库,运行 ProtobufServiceClientMain
执行的结果如下:
Sofa-Middleware-Log SLF4J : Actual binding is of type [ com.alipay.sofa.rpc Log4j2 ]2018-06-11 18:07:59,977 INFO [main] com.alipay.sofa.common.log:report:30 - Sofa-Middleware-Log SLF4J : Actual binding is of type [ com.alipay.sofa.rpc Log4j2 ]2018-06-11 18:08:00,419 INFO [main] org.apache.curator.framework.imps.CuratorFrameworkImpl:start:234 - Starting2018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT2018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:host.name=30.23.232.62018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.version=1.8.0_1712018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.vendor=Oracle Corporation2018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre2018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.class.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/lib/tools.jar:/Users/gaoxiaochen/projj/github.com/gxcsoccer/sofa-rpc-java-demo/target/classes:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/sofa-rpc-all/5.4.0/sofa-rpc-all-5.4.0.jar:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/bolt/1.4.1/bolt-1.4.1.jar:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/common/sofa-common-tools/1.0.12/sofa-common-tools-1.0.12.jar:/Users/gaoxiaochen/.m2/repository/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar:/Users/gaoxiaochen/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/Users/gaoxiaochen/.m2/repository/io/netty/netty-all/4.1.25.Final/netty-all-4.1.25.Final.jar:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/hessian/3.3.0/hessian-3.3.0.jar:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/tracer-core/2.1.1/tracer-core-2.1.1.jar:/Users/gaoxiaochen/.m2/repository/io/opentracing/opentracing-api/0.22.0/opentracing-api-0.22.0.jar:/Users/gaoxiaochen/.m2/repository/io/opentracing/opentracing-noop/0.22.0/opentracing-noop-0.22.0.jar:/Users/gaoxiaochen/.m2/repository/io/opentracing/opentracing-mock/0.22.0/opentracing-mock-0.22.0.jar:/Users/gaoxiaochen/.m2/repository/io/opentracing/opentracing-util/0.22.0/opentracing-util-0.22.0.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/resteasy/resteasy-netty4/3.0.12.Final/resteasy-netty4-3.0.12.Final.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/resteasy/resteasy-jaxrs/3.0.12.Final/resteasy-jaxrs-3.0.12.Final.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/resteasy/jaxrs-api/3.0.12.Final/jaxrs-api-3.0.12.Final.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/spec/javax/annotation/jboss-annotations-api_1.1_spec/1.0.1.Final/jboss-annotations-api_1.1_spec-1.0.1.Final.jar:/Users/gaoxiaochen/.m2/repository/javax/activation/activation/1.1.1/activation-1.1.1.jar:/Users/gaoxiaochen/.m2/repository/org/apache/httpcomponents/httpclient/4.3.6/httpclient-4.3.6.jar:/Users/gaoxiaochen/.m2/repository/org/apache/httpcomponents/httpcore/4.3.3/httpcore-4.3.3.jar:/Users/gaoxiaochen/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/Users/gaoxiaochen/.m2/repository/commons-codec/commons-codec/1.6/commons-codec-1.6.jar:/Users/gaoxiaochen/.m2/repository/commons-io/commons-io/2.1/commons-io-2.1.jar:/Users/gaoxiaochen/.m2/repository/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/resteasy/resteasy-client/3.0.12.Final/resteasy-client-3.0.12.Final.jar:/Users/gaoxiaochen/.m2/repository/org/jboss/resteasy/resteasy-jackson-provider/3.0.12.Final/resteasy-jackson-provider-3.0.12.Final.jar:/Users/gaoxiaochen/.m2/repository/org/codehaus/jackson/jackson-core-asl/1.9.12/jackson-core-asl-1.9.12.jar:/Users/gaoxiaochen/.m2/repository/org/codehaus/jackson/jackson-mapper-asl/1.9.12/jackson-mapper-asl-1.9.12.jar:/Users/gaoxiaochen/.m2/repository/org/codehaus/jackson/jackson-jaxrs/1.9.12/jackson-jaxrs-1.9.12.jar:/Users/gaoxiaochen/.m2/repository/org/codehaus/jackson/jackson-xc/1.9.12/jackson-xc-1.9.12.jar:/Users/gaoxiaochen/.m2/repository/com/alipay/sofa/lookout/lookout-api/1.4.0/lookout-api-1.4.0.jar:/Users/gaoxiaochen/.m2/repository/org/apache/curator/curator-recipes/2.9.1/curator-recipes-2.9.1.jar:/Users/gaoxiaochen/.m2/repository/org/apache/curator/curator-framework/2.9.1/curator-framework-2.9.1.jar:/Users/gaoxiaochen/.m2/repository/org/apache/curator/curator-client/2.9.1/curator-client-2.9.1.jar:/Users/gaoxiaochen/.m2/repository/org/apache/zookeeper/zookeeper/3.4.6/zookeeper-3.4.6.jar:/Users/gaoxiaochen/.m2/repository/log4j/log4j/1.2.16/log4j-1.2.16.jar:/Users/gaoxiaochen/.m2/repository/jline/jline/0.9.94/jline-0.9.94.jar:/Users/gaoxiaochen/.m2/repository/io/netty/netty/3.7.0.Final/netty-3.7.0.Final.jar:/Users/gaoxiaochen/.m2/repository/com/google/guava/guava/16.0.1/guava-16.0.1.jar:/Users/gaoxiaochen/.m2/repository/com/google/protobuf/protobuf-java/3.1.0/protobuf-java-3.1.0.jar:/Users/gaoxiaochen/.m2/repository/org/apache/logging/log4j/log4j-core/2.3/log4j-core-2.3.jar:/Users/gaoxiaochen/.m2/repository/org/apache/logging/log4j/log4j-api/2.3/log4j-api-2.3.jar:/Users/gaoxiaochen/.m2/repository/com/lmax/disruptor/3.3.7/disruptor-3.3.7.jar:/Users/gaoxiaochen/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.3/log4j-slf4j-impl-2.3.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar2018-06-11 18:08:00,432 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.library.path=/Users/gaoxiaochen/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.io.tmpdir=/var/folders/q4/4nwl16wn32ndm69rzh1zyvhh0000gn/T/2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:java.compiler=<NA>2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:os.name=Mac OS X2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:os.arch=x86_642018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:os.version=10.13.42018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:user.name=gaoxiaochen2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:user.home=/Users/gaoxiaochen2018-06-11 18:08:00,433 INFO [main] org.apache.zookeeper.ZooKeeper:logEnv:100 - Client environment:user.dir=/Users/gaoxiaochen/projj/github.com/gxcsoccer/sofa-rpc-java-demo2018-06-11 18:08:00,434 INFO [main] org.apache.zookeeper.ZooKeeper:<init>:438 - Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@2e005c4b2018-06-11 18:08:00,459 INFO [main-SendThread(127.0.0.1:2181)] org.apache.zookeeper.ClientCnxn:logStartConnect:975 - Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)2018-06-11 18:08:00,547 INFO [main-SendThread(127.0.0.1:2181)] org.apache.zookeeper.ClientCnxn:primeConnection:852 - Socket connection established to 127.0.0.1/127.0.0.1:2181, initiating session2018-06-11 18:08:00,555 INFO [main-SendThread(127.0.0.1:2181)] org.apache.zookeeper.ClientCnxn:onConnected:1235 - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x1000894bb75004f, negotiated timeout = 400002018-06-11 18:08:00,558 INFO [main-EventThread] org.apache.curator.framework.state.ConnectionStateManager:postState:228 - State change: CONNECTED2018-06-11 18:08:00,594 WARN [main] org.apache.curator.utils.ZKPaths:<clinit>:76 - The version of ZooKeeper being used doesn't support Container nodes. CreateMode.PERSISTENT will be used instead.Sofa-Middleware-Log SLF4J : Actual binding is of type [ com.alipay.remoting Log4j2 ]2018-06-11 18:08:00,645 INFO [SOFA-CLI-CONN-com.alipay.sofa.rpc.protobuf.ProtoService-3-T1] com.alipay.sofa.common.log:report:30 - Sofa-Middleware-Log SLF4J : Actual binding is of type [ com.alipay.remoting Log4j2 ]2018-06-11 18:08:00,703 WARN No Root logger was configured, creating default ERROR-level Root logger with Console appender/Users/gaoxiaochen/logs/tracelog/rpc-client-digest.log -> /Users/gaoxiaochen/logs/tracelog/rpc-client-digest.log.2018-06-09200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0200: hello zhang, you are in 0
