Skip to content

srpc小工具:快速构建脚手架

liyingxin edited this page May 13, 2023 · 2 revisions

srpc小工具

一个帮你快速生成Workflow和SRPC项目的脚手架小工具。

1. 基本介绍

这个小工具可以帮你快速构建你的WorkflowSRPC项目,目的是为了让开发者可以快速构建出一个简单而完整的项目,降低编译和运行起来的门槛,让开发者可以伴随着修改->执行->验证效果,一步步深入了解WorkflowSRPC项目的基本功能,是一个极佳的学习工具

构建出的项目内包括:

  • 源代码:可以直接编译,编译出来的可执行文件可以直接跑起来使用。我们也可以进行简单地修改,从而快速验证我们想要的功能。
  • 配置文件:json格式,用于指定项目的基本配置,我们也可以快速通过配置文件了解到项目相关的信息。
  • CMakeLists.txt:写好了依赖库位置,编译与链接方式等,供开发者用cmake进行编译。内部有简单的注释,我们可以根据注释一点点学习如何写基本的cmake。
  • GNUmakefile:只是为了方便开发者可以直接执行make命令进行编译的文件,内部封装了cmake命令。
  • config目录:内部有读取配置文件的代码,以及一些开发者暂时无需关心的工具代码比如util.h。

整个项目构建出来之后,可以拷走拿到别的地方使用。

2. 快速开始

2.1 源码位置

我们把上述的项目clone下来,并打开tools目录,就可以编译出我们的srpc小工具。工具名字也叫srpc,但是是小写的~

git clone https://github.com/sogou/srpc.git
cd srpc/tools && make

这个小工具和SRPC框架目前还没有关系,所以即使本地没有安装SRPC所需要的protobuf或者没有--recursive拉submodule下来,也依然可以编译。唯一需要的是cmake 3.6及以上的版本。

2.2 运行工具

我们先把这个srpc小工具运行起来,可以看到它第二个参数COMMAND:表示支持什么命令

./srpc
Description:
    Simple generator for building Workflow and SRPC projects.

Usage:
    ./srpc <COMMAND> <PROJECT_NAME> [FLAGS]

Available Commands:
    http    - create project with both client and server
    redis   - create project with both client and server
    rpc     - create project with both client and server
    api     - create protobuf or thrift IDL api
    proxy   - create proxy for some client and server protocol
    file    - create project with asynchronous file service
    compute - create project with asynchronous computing service

这些COMMAND包括了我们最常用的场景,适合入门了解服务器编程最简单的内容。

2.3 一行命令构建项目

第三个参数是项目名,我们先用一行简单的命令,构建出一个Http服务器与客户端。

./srpc http my_project
Success:
      make project path " my_project/ " done.

Commands:
      cd my_project/
      make -j

Execute:
      ./server
      ./client

可以看到提示Success!

2.4 第一个项目的编译和运行

我们按照上述的提示看到:my_project目录已经在本地目录下创建,并给出了:

  • 编译的命令:make
  • 运行的命令:分别在两个终端上执行./server 和 ./client
cd my_project
make

执行ls -all 一下可以看到,两个可执行文件已经编译出来了。我们分别在两个终端运行./server./client

./server 
Http server started, port 8080
http server get request_uri: /client_request
peer address: 127.0.0.1:65313, seq: 0.

client运行起来后会给server发一个请求,然后server会打印出上面显示的最后两行,然后client收到回复之后也会打印下面的两行:

./client 
Http client state = 0 error = 0
<html>Hello from server!</html>

2.5 模块依赖,是C++项目的第一道门槛

即使刚才git clone项目时没有加--recursive拉取依赖的submodule,或者srpc的lib没有编译(按上述的步骤的话,是还没有的~),那么工具会自动做一些初始化的工作

当然,目前C++跟GO等其他语言比起来在构建方面还是薄弱了一点。如果大家还没有安装protobuf,或者系统的版本太旧、导致编译SRPC时所依赖的protobuf版本与链接时不一样,那么可以先使用源码编译protobuf

这里找了一个不太新也不太旧的版本,大家有需要可以按照以下步骤先源码编译和安装protobuf:

git clone -b 3.20.x https://github.com/protocolbuffers/protobuf.git protobuf.3.20
cd protobuf.3.20
sh autogen.sh
./configure
make -j4
make install

然后我们就可以愉快再试一下上述步骤了~

小工具整体需要执行的命令与需要安装的依赖提前一览:

3. 一个脚手架项目包含的元素

我们执行tree命令,查看这个项目里的文件结构。

需要我们关注的有这些:

3.1 编译文件

脚手架小工具目前还是使用cmake进行编译,后续计划支持bazel和xmake。

GNUmakefile包了一层cmake命令,让我们可以执行make就编译出项目,这个文件我们不需要关心。

打开CMakeLists.txt,可以看到一共32行,包括了:

  1. 寻找依赖路径的写法
  2. include和link的写法
  3. 编出执行文件

开发者可以根据里边的注释自行修改,即使不常用C++的开发者也可以边试边学

3.2 client

client会读取client.conf作为它的配置文件,主要是指定要访问的目标是什么。

我们打开client_main.cc,可以看到脚手架默认生成的client只有60多行,一共做了3件事:

int main()
{
    // 1. 初始化,这个实现也在源码中,主要是调用config.load("./client.conf")
    init();

    std::string url = std::string("http://") + config.client_host() +
                      std::string(":") + std::to_string(config.client_port());
 
    // 2. 构造一个http task并且填回调函数
    WFHttpTask *task = WFTaskFactory::create_http_task(url,                        
                                                        config.redirect_max(),  
                                                        config.retry_max(),        
                                                        callback);
    // 3. 把task运行起来
    task->start();                                                                 

    wait_group.wait();
    return 0;
}

可以看到,这个和workflow的tutorial中的例子是一样的,需要填的callback函数也在文件中。

3.4 server

我们打开server_main.cc,50多行的代码,也是做了3件事,可以看到和上面的client是非常对称的

int main()
{
    // 1. 初始化,这个实现也在源码中,主要是调用config.load(“./server.conf")
    init(); 

    // 2. 造一个server,填好处理函数
    WFHttpServer server(process);                                                  

    // 3. 把server运行起来
    if (server.start(config.server_port()) == 0)                                   
    {
        fprintf(stderr, "Http server started, port %u\n", config.server_port());
        wait_group.wait();
        server.stop();
    }
    else
        perror("server start");

    return 0;
}

process函数也在源码中,开发者可以尝试修改,进行不同的行为处理。示例中的行为就是回复一个 " Hello from server! "

3.5 配置文件

配置解析并不是Workflow和SRPC项目自带的,但是脚手架项目增加了这个功能。

我们目前使用的配置文件都是json格式,和配置解析相关的都放到了config目录中。除了client.confserver.conf以外,我们还多加了一份full.conf,用来指引Workflow和SRPC目前支持的配置项,开发者可以通配置文件,快速了解我们还可以用什么功能。

比如框架的全局配置:

{                                                                                  
  "server":                                                                        
  {
    "port": 8080                                                                   
  },
                                                                                   
  "client":                                                                        
  {
    "remote_host": "127.0.0.1",
    "remote_port": 8080,
    "retry_max": 1,
  },

  "global":
  {
    "poller_threads": 4,
    "handler_threads": 20
  }
}

熟悉的开发者可能接触过WorkflowUpstream,以及tracemetrics等监控数据的上报插件,这些都可以在配置文件中指定并一键加载,帮开发者接管外部生态,真正实现脚手架的能力。

4. 命令大全

经过以上介绍,应该可以基本掌握怎么快速构建和运行一个自己的小项目。接下来我们进一步解锁这个srpc小工具,每个COMMAND都是一个二级命令

4.1 http命令

创建HTTP项目的用法如上已经展示,可以创建http协议的server和client。其中server和client里的示例代码都可以自行改动,配置文件server.confclient.conf里也可以指定基本的配置项,cmake编译文件都已经生成好了,整个项目可以直接拿走使用。

./srpc http
Missing: PROJECT_NAME

Usage:
    ./srpc http <PROJECT_NAME> [FLAGS]

Example:
    ./srpc http my_http_project

Available Flags:
    -o :    project output path (default: CURRENT_PATH)
    -d :    path of dependencies (default: COMPILE_PATH)

4.2 rpc命令

构建一个以protobuf或者thrift作为IDL的多协议RPC项目。

我们的client和server只需要保证以同样的协议进行通信,而其余的东西交给SPRC框架帮你处理就好,最终开发者接触到的就是我们的IDL所约定的接口。其中,支持的RPC协议包括:SRPC、SRPCHttp、BRPC、TRPC、TRPCHttp、Thrift、ThriftHttp。这些东西都可以在构建时通过参数指定。

我们执行./srpc rpc,就可以看到rpc命令支持的参数:

我们尝试以默认方式构建一个RPC项目。也可以使用-f指定IDL文件进行构建,这会使用srpc_generator去进行代码生成

./srpc rpc rpc_project

打开之后,可以看到和http命令相比,有如下区别:

  1. 多了一个rpc_project.proto
  2. server_main.cc和client_main.cc分别变成了SRPCServerSRPCClient
  3. CMakeLists.txt也变复杂了,因为需要依赖protobuf和snappy等压缩库;

我们同样可以通过make把项目编译出来,运行方式也与前面类似。

其他常用的参数中,值得一提的是-f:指定要创建的RPC项目所依赖的proto文件。示例如下:

./srpc rpc rpc_example -f test_proto/test.proto 

然后就可以看到一些生成代码多信息,这和原先使用srpc_genrator时所看到的是类似的。

Info: srpc generator begin.
proto file: [/root/srpc/tools/rpc_example/test.proto]
Successfully parse service block [message] : EchoRequest
Successfully parse service block [message] : EchoResponse
Successfully parse service block [service] : new_test
Successfully parse method:Echo req:EchoRequest resp:EchoResponse
finish parsing proto file: [/root/srpc/tools/rpc_example/test.proto]
[Generator] generate srpc files: /root/srpc/tools/rpc_example/test.srpc.h 
[Generator Done]
[Generator] generate server files: /root/srpc/tools/rpc_example/server_main.cc, client files: /root/srpc/tools/rpc_example/client_main.cc
Info: srpc generator done.

Success:
      make project path rpc_example/ done.

Commands:
      cd rpc_example/
      make -j

Execute:
      ./server
      ./client

4.3 api命令

如果对IDL文件不熟悉的小伙伴,可以在执行rpc命令之前先生成一个默认的IDL,我们按如下命令,就可以生成默认为protobuf格式的my_proto_file.proto

./srpc api my_proto_file
Success:
      Create api file my_proto_file.proto at path /root/srpc/tools/ done.

Suggestions:
      Modify the api file as you needed.
      And make rpc project base on this file with the following command:

      ./srpc rpc my_rpc_project -f my_proto_file.proto -p /root/srpc/tools/my_proto_file/

可以看到my_proto_file.proto已经成功创建,Suggestions给出了命令可以让我们基于这个文件进一步构造我们的rpc项目,当然也可以先进行修改再构建项目。打开文件看到默认的rpc接口如下:

syntax="proto2";                                                                   
                                                                                   
message EchoRequest {                                                              
    required string message = 1;                                                   
};                                                                                 
                                                                                   
message EchoResponse {                                                             
    required string message = 1;                                                   
    optional int32 state = 2;                                                      
    optional int32 error = 3;                                                      
};                                                                                 
                                                                                   
service my_proto_file {                                                            
    rpc Echo(EchoRequest) returns (EchoResponse);                                  
};

4.4 redis命令

这个命令主要用于构建redis协议的server和client,由于Workflow的协议对server和client来说都是对等的,因此基于Workflow实现的redis server依然非常简洁高效。

这个例子中,client发出的请求是set k1 v1,server收到任何内容都回复一个OK。并且client.conf中增加了用户名和密码的项,开发者可以通过修改配置,用这个client访问其他任意的redis server。

根据指引我们创建了一个项目后,就可以得到最简单的redis server和client。client就简单地实现了发送SET k1 v1命令,而server无论收到什么都会简单地回复一个OK。我们可以用这简单的示例,改造一个可以请求任何redis协议服务的client,也可以构造一个简单的redis服务器。

./server

Redis server start, port 6379
redis server get cmd: [SET] from peer address: 127.0.0.1:60665, seq: 0.
./client

Redis client state = 0 error = 0
response: OK

如果client有填写用户名和密码的需求,可以填到client.conf中。我们打开这个配置文件看看:

  1 {
  2   "client":
  3   {
  4     "remote_host": "127.0.0.1",
  5     "remote_port": 6379,
  6     "retry_max": 2,
  7     "user_name": "root",
  8     "password": ""
  9   }
 10 }

4.5 proxy : 代理服务器

代理服务器顾名思义,就是可以多构建一个proxy,我们可以用client去访问proxy,并由proxy去转发给server,这中间proxy就可以做很多事情,包括:更改协议、内容校验等等。

一个常见的场景是,我们的现有业务是客户端发出TRPC协议,而需要访问SRPC协议的服务器时,则可以构建出一个TRPC-SRPC的proxy,并且让大家使用统一的proto文件约定好请求,则proxy就可以直接做转发。

./srpc proxy

执行上述命令,我们可以看到proxy命令的指引:

Missing: PROJECT_NAME

Usage:
    ./srpc proxy <PROJECT_NAME> [FLAGS]

Example:
    ./srpc redis my_proxy_project

Available Flags:
    -c :    client type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http)
    -s :    server type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http)
    -o :    project output path (default: CURRENT_PATH)
    -d :    path of dependencies (default: COMPILE_PATH)

我们执行如下命令,用-c指定client端的协议,用-s指定server端的协议:

./srpc proxy srpc_trpc_proxy_example -c SRPC -s TRPC
Success:
      make project path srpc_trpc_proxy_example/ " done.

Commands:
      cd srpc_trpc_proxy_example/
      make -j

Execute:
      ./server
      ./proxy
      ./client

查看新创建的项目中有什么文件:

cd srpc_trpc_proxy_example && tree
.
├── CMakeLists.txt
├── GNUmakefile
├── client.conf
├── client_main.cc
├── config
│   ├── Json.cc
│   ├── Json.h
│   ├── config.cc
│   └── config.h
├── proxy.conf
├── proxy_main.cc
├── server.conf
├── server_main.cc
└── srpc_trpc_proxy_example.proto

2 directories, 13 files

分别在三个终端执行./server ./proxy./client,我们可以看到,client发送了trpc协议的请求"Hello, this is sync request!" 和一个异步请求Hello, this is async request!"给proxy,而proxy收到之后把请求用srpc协议发给了server。SRPC server填了回复"Hi back"并通过刚才的proxy路线转回给了client,期间转发纯异步,不会阻塞任何线程。

./server

srpc_trpc_proxy_example TRPC server start, port 1412
get req: message: "Hello, this is sync request!"
get req. message: "Hello, this is async request!"
./proxy

srpc_trpc_proxy_example [SRPC]-[TRPC] proxy start, port 1411
srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1
message: "Hello, this is sync request!"
srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1
message: "Hello, this is async request!"
./client

sync resp. message: "Hi back"
async resp. message: "Hi back"

其中proxy_main.cc的实现,是起了一个TRPCServer,并使用SRPCClient去转发请求。感兴趣的小伙伴可以围观一下,其实SRPC项目的tutorial里也已经有这样的例子了:tutorial-15-srpc_pb_proxy.cc

4.6 file : 文件服务器

文件服务器通过异步IO读取,也是我们常用的功能,这里不再赘述,对实现感兴趣的小伙伴欢迎查看原先的一篇文章:《Workflow编程小示例4: 转发服务器与series上下文的使用》

我们通过./srpc file file_project构建一下项目,我们可以通过curl命令去读取想要的文件,例如curl localhost:8080/index.html 就可以读取到指定root目录下的index.html文件。

./srpc file file_project
Success:
      make project path file_project/ done.

Commands:
      cd ./file_project/
      make -j

Execute:
      ./server

Try file service:
      curl localhost:8080/index.html
      curl -i localhost:8080/a/b/

可以看到,多了一个html的目录,里边放了index.html, 404.hmtl50x.html。如果使用过其他Http服务器的小伙伴应该不陌生,这是常见的用法。

.
├── CMakeLists.txt
├── GNUmakefile
├── config
│   ├── Json.cc
│   ├── Json.h
│   ├── config.cc
│   └── config.h
├── file_service.cc
├── file_service.h
├── html
│   ├── 404.html
│   ├── 50x.html
│   └── index.html
├── server.conf
└── server_main.cc

3 directories, 13 files

打开server.conf,就可以看到我们为文件服务器添加的具体配置项:rooterror_page。我们可以通过root去指定打开文件的根目录,以及通过error_page去关联具体的错误码和它们所要返回作body的页面名称。而错误页面和其他文件一样,都是通过异步IO的方式读取,不会阻塞server当前的处理线程。

熟悉的error_page,它来了:

{     
  "server":     
  {     
    "port": 8080,     
    "root": "./html/",     
    "error_page" : [     
      {     
        "error" : [ 404 ],     
        "page" : "404.html"     
      },     
      {     
        "error" : [ 500, 502, 503, 504],     
        "page" : "50x.html"     
      }     
    ]     
  }     
}

我们执行make进行编译,然后执行./server把文件服务器跑起来,然后用curl进行测试:

示例1:在根目录./html/下读取文件index.html(即使请求localhost:8080,默认也是读index.html)。

curl localhost:8080/index.html

<html>Hello from workflow and srpc file server!</html>

示例2:读文件/a/b/,这个文件不存在,所以我们根据上面配置文件server.conf中所指定的,填入404错误码会返回页面404.html的内容。

curl -i localhost:8080/a/b/
HTTP/1.1 404 Not Found
Server: SRPC HTTP File Server
Content-Length: 59
Connection: Keep-Alive

<html>This is not the web page you are looking for.</html>

以下信息在server端可以看到:

./server 
http file service start, port 8080
file service get request: /a/b/

4.7 compute : 计算服务器

计算服务器也是通过go_task发起计算任务,不会阻塞当前线程。实现原理欢迎参考:《WF编程小示例6: 计算型服务器与计算任务》

./srpc compute compute_test

通过以上命令,我们可以创建一个小项目,项目默认接收url请求作为参数n,并进行斐波那契计算。

Success:
      make project path compute_test/ done.

Commands:
      cd compute_test/
      make -j

Execute:
      ./server

Try compute with n=8:
      curl localhost:8080/8

进入compute_test/目录执行make,并执行./server运行起来。然后我们就可以使用curl或者浏览器输入localhost:8080/8,计算第8个斐波那契数是多少。这里以curl为例:

curl localhost:8080/8

<html><p>0 + 1 = 1.</p><p>1 + 1 = 2.</p><p>1 + 2 = 3.</p><p>2 + 3 = 5.</p><p>3 + 5 = 8.</p><p>5 + 8 = 13.</p><p>The No. 8 Fibonacci number is: 13.</p></html>

我们看到了server内部的计算步骤。server的计算例子使用了go_task去封装一个计算函数,欢迎尝试更多的计算调度。