Go语言_RPC_Go语言的RPC
一 标准库的RPC
RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。简单的说就是要像调用本地函数一样调用服务器的函数。
RPC协议构建于TCP或UDP,或者是 HTTP之上,允许开发者直接调用另一台计算机上的程序,而开发者无需额外地为这个调用过程编写网络通信相关代码,使得开发包括网络分布式程序在内的应用程序更加容易.
Go语言的标准库已经提供了RPC框架和不同的RPC实现.
下面是一个服务器的例子:
type Echo int func (t *Echo) Hi(args string, reply *string) error {
*reply = "echo:" + args
return nil
} func main() {
rpc.Register(new(Echo))
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":1234")
if e != nil {
log.Fatal("listen error:", e)
}
http.Serve(l, nil)
}
其中 rpc.Register
用于注册RPC服务, 默认的名字是对象的类型名字(这里是Echo
). 如果需要指定特殊的名字, 可以用 rpc.RegisterName
进行注册.
被注册对象的类型所有满足以下规则的方法会被导出到RPC服务接口:
func (t *T) MethodName(argType T1, replyType *T2) error
被注册对应至少要有一个方法满足这个特征, 否则可能会注册失败.
然后 rpc.HandleHTTP
用于指定 RPC 的传输协议, 这里是采用 http 协议作为RPC调用的载体. 用户也可以用rpc.ServeConn
接口, 定制自己的传输协议.
客户端可以这样调用Echo.Hi
接口:
func main() {
client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal("dialing:", err)
} var args = "hello rpc"
var reply string
err = client.Call("Echo.Hi", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
}
客户端先用rpc.DialHTTP
和RPC服务器进行一个链接(协议必须匹配).
然后通过返回的client
对象进行远程函数调用. 函数的名字是由client.Call
第一个参数指定(是一个字符串).
基于HTTP的RPC调用一般是在调试时使用, 默认可以通过浏览"127.0.0.1:1234/debug/rpc"
页面查看RPC的统计信息.
另外一个例子:
服务器端代码:
- package main
- import (
- "errors"
- "fmt"
- "net/http"
- "net/rpc"
- )
- const (
- //URL = "10.200.7.244:3545"
- URL = "127.0.0.1:3545"
- )
- type Args struct {
- A, B int
- }
- type Quotient struct {
- Quo, Rem int
- }
- type Arith int
- func (t *Arith) Multiply(args *Args, reply *int) error {
- *reply = args.A * args.B
- return nil
- }
- func (t *Arith) Divide(args *Args, quo *Quotient) error {
- if args.B == 0 {
- return errors.New("divide by zero!")
- }
- quo.Quo = args.A / args.B
- quo.Rem = args.A % args.B
- return nil
- }
- func main() {
- arith := new(Arith)
- rpc.Register(arith)
- rpc.HandleHTTP()
- err := http.ListenAndServe(URL, nil)
- if err != nil {
- fmt.Println(err.Error())
- }
- }
客户端代码:
- package main
- import (
- "fmt"
- "net/rpc"
- )
- const (
- //URL = "10.200.7.234:3545"
- URL = "127.0.0.1:3545"
- )
- type Args struct {
- A, B int
- }
- func main() {
- client, err := rpc.DialHTTP("tcp", URL)
- if err != nil {
- fmt.Println(err.Error())
- }
- args := Args{4, 4}
- var reply int
- err = client.Call("Arith.Multiply", &args, &reply)
- if err != nil {
- fmt.Println(err.Error())
- } else {
- fmt.Println(reply)
- }
- }
- client.Call("Arith.Multiply", &args, &reply)
以上的方式为同步调用
异步调用的代码:
- quotient := new(Quotient)
- divCall := client.Go("Arith.Divide", args, "ient, nil)
- replyCall := <-divCall.Done
测试截图:
二 基于 JSON 的 RPC 调用
在上面的RPC例子中, 我们采用了默认的HTTP协议作为RPC调用的传输载体.
因为内置net/rpc
包接口设计的缺陷, 我们无法使用jsonrpc
等定制的编码作为rpc.DialHTTP
的底层协议. 如果需要让jsonrpc
支持rpc.DialHTTP
函数, 需要调整rpc的接口.
以前有个Issue2738是针对这个问题. 我曾提交的 CL10704046 补丁用于修复这个问题. 不过因为涉及到增加rpc的接口, 官方没有接受(因为自己重写一个DialHTTP
会更简单).
除了传输协议, 还有可以指定一个RPC编码协议, 用于编码/节目RPC调用的函数参数和返回值. RPC调用不指定编码协议时, 默认采用Go语言特有的gob
编码协议.
因为, 其他语言一般都不支持Go语言的gob
协议, 因此如果需要跨语言RPC调用就需要
采用通用的编码协议.
Go的标准库还提供了一个"net/rpc/jsonrpc"
包, 用于提供基于JSON编码的RPC支持.
服务器部分只需要用rpc.ServeCodec
指定json编码协议就可以了:
func main() {
lis, err := net.Listen("tcp", ":1234")
if err != nil {
return err
}
defer lis.Close() srv := rpc.NewServer()
if err := srv.RegisterName("Echo", new(Echo)); err != nil {
return err
} for {
conn, err := lis.Accept()
if err != nil {
log.Fatalf("lis.Accept(): %v\n", err)
}
go srv.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
客户端部分值需要用 jsonrpc.Dial
代替 rpc.Dial
就可以了:
func main() {
client, err := jsonrpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal("dialing:", err)
}
...
}
如果需要在其他语言中使用jsonrpc
和Go语言进行通讯, 需要封装一个和jsonrpc
匹配的库.
关于jsonrpc
的实现细节这里就不展开讲了, 感兴趣的话可以参考这篇文章: JSON-RPC: a tale of interfaces.
三 基于 Protobuf 的 RPC 调用
Protobuf 是 Google 公司开发的编码协议. 它的优势是编码后的数据体积比较小(并不是压缩算法), 比较适合用于命令的传输编码.
Protobuf 官方团队提供 Java/C++/Python 几个语言的支持, Go语言的版本由Go团队提供支持, 其他语言由第三方支持.
Protobuf 的语言规范中可以定义RPC接口. 但是在Go语言和C++版本的Protobuf中都没有生成RPC的实现.
不过作者在 Go语言版本的Protobuf基础上开发了 RPC 的实现 protorpc, 同时提供的 protoc-gen-go
命令可以生成相应的RPC代码. 项目地址: https://code.google.com/p/protorpc/
该实现支持Go语言和C++语言, 在Protobuf官方wiki的第三方RPC实现列表中有介绍:https://code.google.com/p/protobuf/wiki/ThirdPartyAddOns#RPC_Implementations
要使用 protorpc, 需要先在proto文件定义接口(arith.pb/arith.proto
):
package arith; // go use cc_generic_services option
option cc_generic_services = true; message ArithRequest {
optional int32 a = 1;
optional int32 b = 2;
} message ArithResponse {
optional int32 val = 1;
optional int32 quo = 2;
optional int32 rem = 3;
} service ArithService {
rpc multiply (ArithRequest) returns (ArithResponse);
rpc divide (ArithRequest) returns (ArithResponse);
}
protorpc使用cc_generic_services
选择控制是否输出RPC代码. 因此, 需要设置cc_generic_services
为true
.
然后下载 protoc-2.5.0-win32.zip, 解压后可以得到一个 protoc.exe
的编译命令.
然后使用下面的命令获取 protorpc 和对应的 protoc-gen-go
插件.
go get code.google.com/p/protorpc
go get code.google.com/p/protorpc/protoc-gen-go
需要确保 protoc.exe
和 protoc-gen-go.exe
都在 $PATH
中. 然后运行以下命令将前面的接口文件转换为Go代码:
cd arith.pb && protoc --go_out=. arith.proto
新生成的文件为arith.pb/arith.pb.go
.
下面是基于 Protobuf-RPC 的服务器:
package main import (
"errors" "code.google.com/p/goprotobuf/proto" "./arith.pb"
) type Arith int func (t *Arith) Multiply(args *arith.ArithRequest, reply *arith.ArithResponse) error {
reply.Val = proto.Int32(args.GetA() * args.GetB())
return nil
} func (t *Arith) Divide(args *arith.ArithRequest, reply *arith.ArithResponse) error {
if args.GetB() == 0 {
return errors.New("divide by zero")
}
reply.Quo = proto.Int32(args.GetA() / args.GetB())
reply.Rem = proto.Int32(args.GetA() % args.GetB())
return nil
} func main() {
arith.ListenAndServeArithService("tcp", ":1984", new(Arith))
}
其中导入的 "./arith.pb"
的名字为 arith
, 在 arith.pb/arith.proto
文件中定义(这2个可能不同名, 导入时要小心).
arith.ArithRequest
和arith.ArithResponse
是RPC接口的输入和输出参数, 也是在在arith.pb/arith.proto
文件中定义的.
同时生成的还有一个arith.ListenAndServeArithService
函数, 用于启动RPC服务. 该函数的第三个参数是RPC的服务对象, 必须要满足 arith.EchoService
接口的定义.
客户端的使用也很简单, 只要一个 arith.DialArithService
就可以链接了:
stub, client, err := arith.DialArithService("tcp", "127.0.0.1:1984")
if err != nil {
log.Fatal(`arith.DialArithService("tcp", "127.0.0.1:1984"):`, err)
}
defer client.Close()
arith.DialArithService
返回了一个 stub
对象, 该对象已经绑定了RPC的各种方法, 可以直接调用(不需要用字符串指定方法名字):
var args ArithRequest
var reply ArithResponse args.A = proto.Int32(7)
args.B = proto.Int32(8)
if err = stub.Multiply(&args, &reply); err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d", args.GetA(), args.GetB(), reply.GetVal())
相比标准的RPC的库, protorpc 由以下几个优点:
- 采用标准的Protobuf协议, 便于和其他语言交互
- 自带的
protoc-gen-go
插件可以生成RPC的代码, 简化使用 - 服务器注册和调用客户端都是具体类型而不是字符串和
interface{}
, 这样可以由编译器保证安全 - 底层采用了
snappy
压缩传输的数据, 提高效率
不足之处是使用流程比标准RPC要繁复(需要将proto转换为Go代码).
四 C++ 调用 Go 提供的 Protobuf-RPC 服务
protorpc 同时也提供了 C++ 语言的实现.
C++版本的安装如下:
hg clone https://code.google.com/p/protorpc.cxx/
cd protorpc.cxx
- build with cmake
C++ 版本 的 protorpc 对 protoc.exe
扩展了一个 --cxx_out
选项, 用于生成RPC的代码:
${protorpc_root}/protobuf/bin/protoc --cxx_out=. arith.proto
注:--cxx_out
选项生成的代码除了RPC支持外, 还有xml的序列化和反序列化支持.
下面是 C++ 的客户端链接 Go 语言版本的 服务器:
#include "arith.pb.h" #include <google/protobuf/rpc/rpc_server.h>
#include <google/protobuf/rpc/rpc_client.h> int main() {
::google::protobuf::rpc::Client client("127.0.0.1", 1234); service::ArithService::Stub arithStub(&client); ::service::ArithRequest arithArgs;
::service::ArithResponse arithReply;
::google::protobuf::rpc::Error err; // EchoService.mul
arithArgs.set_a(3);
arithArgs.set_b(4);
err = arithStub.multiply(&arithArgs, &arithReply);
if(!err.IsNil()) {
fprintf(stderr, "arithStub.multiply: %s\n", err.String().c_str());
return -1;
}
if(arithReply.c() != 12) {
fprintf(stderr, "arithStub.multiply: expected = %d, got = %d\n", 12, arithReply.c());
return -1;
} printf("Done.\n");
return 0;
}
详细的使用说明请参考: README.md .
更多的例子请参考: rpcserver.cc
和 rpcclient.cc
五 总结
Go语言的RPC客户端是一个使用简单, 而且功能强大的RPC库. 基于标准的RPC库我们可以方便的定制自己的RPC实现(传输协议和串行化协议都可以定制).
不过在开发 protorpc 的过程中也发现了net/rpc
包的一些不足之处:
- 内置的
HTTP
协议的RPC的串行化协议和传输协议耦合过于紧密, 用户扩展的协议无法支持内置的HTTP
传输协议(因为rpc.Server
和rpc.Client
接口缺陷导致的问题) rpc.Server
只能注册rpc.ServerCodec
, 而不能注册工厂函数. 而jsonrpc.NewServerCodec
需要依赖先建立链接(conn
参数), 这样导致了HTTP
协议只能支持内置的gob
协议rpc.Client
的问题和rpc.Server
类似
因为Go1需要保证API的兼容性, 因此上述的问题只能希望在未来的Go2能得到改善.
Go语言_RPC_Go语言的RPC的更多相关文章
- OC语言-02-OC语言-基础知识
一.基础语法 1> OC语言和C语言 C语言是面向过程的语言,OC语言是面向对象的语言 OC语言继承了C语言,并增加了面向对象的思想 以下内容只介绍OC语言与C语言的不同之处 2> 关键字 ...
- 国家语言,语言代码,locale id对应表
国家语言,语言代码,locale id对应表.比如 en_US对应的id为1033, 中文的locale=zh_CN,id=2052. Locale Languagecode LCIDstring L ...
- Atitit.编程语言的主要的种类and趋势 逻辑式语言..函数式语言...命令式语言
Atitit.编程语言的主要的种类and趋势 逻辑式语言..函数式语言...命令式语言 1. 编程语言的主要的种类 逻辑式语言..函数式语言...命令式语言 1 2. 逻辑式语言,,不必考虑实现过程而 ...
- Atitit.go语言golang语言的新的特性 attilax总结
Atitit.go语言golang语言的新的特性 attilax总结 1. 继承树less 动态接口1 1.1. 按照书中说的,Go语言具有以下的特征,下面我们分别来进行介绍. q 自动垃圾回收 ...
- 关于"动态语言" "静态语言" "静态类型语言" "动态类型语言"的区别
参考链接:关于“编译型”“解释型”“动态语言”“静态语言”“动态类型语言”“静态类型语言”的区分以及优缺点(汇总整理) 很多人把这两类混为一谈,但是这是完全不同的两个概念!!! 动态和静态语言主要看的 ...
- 编程小白必备——主流语言C语言知识点
对于编程语言来说,经常看到有因为各自支持的语言阵营而互怼的,其实根本没那个必要,都只是一种工具而已.当多数主流语言都会使用时也许你就不会有偏见了,本质不过都是用来描述计算机的一个任务,只是每门语言设计 ...
- [R语言]R语言计算unix timestamp的坑
R+mongo的组合真是各种坑等着踩 由于mongo中的时间戳普遍使用的是unix timestamp的格式,因此需要对每天的数据进行计算的时候,很容易就想到对timestamp + gap对方式来实 ...
- [R语言]R语言使用多线程对数据库进行大批量访问时出现无法连接问题
问题描述: 在R中使用多线程对数据库进行写入,在服务器端运行脚本(linux环境),总是在第6-7万个任务线程时,出现无法连接到数据库的问题.任务中断,错误信息为task 6xxxx failed,C ...
- OC语言-07-OC语言-Foundation框架
结构体 NSRange/CGRange 用来表示一个元素在另一个元素中的范围,NSRange等价于CGRange 包含两个属性: NSUInteger location:表示一个元素在另一个元素中的位 ...
随机推荐
- 重新造轮子之静态链接1(Static linking)
最近学习计算机病毒学的过程中,又讲到了静态链接的问题,联想到了之前保健哥在信息安全的课堂上向我们展示了一个没有main()函数的C程序到底应该如何编写.个人觉得这个小实验对于加深静态链接的过程的理解也 ...
- 由浅入深学习PBR的原理和实现
目录 一. 前言 1.1 本文动机 1.2 PBR知识体系 1.3 本文内容及特点 二. 初阶:PBR基本认知和应用 2.1 PBR的基本介绍 2.1.1 PBR概念 2.1.2 与物理渲染的差别 2 ...
- 【Jenskins】安装与配置
Jenskins教程:http://www.yiibai.com/jenkins/ 一.Jenskins的安装 1.jenskins下载和启动 Jenskins下载地址:https://jenkins ...
- luogu2564 [SCOI2009]生日礼物
排序枚举左端点,则右端点必定不降 #include <algorithm> #include <iostream> #include <cstring> #incl ...
- luogu3369 【模板】普通平衡树(Treap/SBT) treap splay
treap做法,参考hzwer的博客 #include <iostream> #include <cstdlib> #include <cstdio> using ...
- css图像处理与动画
先讨论几个css 问题 1,css 清除浮动的方法 2,css 居中 3,多行省略号 4,小布局技巧 2D 动画功能属性兼容性:transform.transition.animation trans ...
- jenkins项目数据位置
JENKINS_HOME: C:\Documents and Settings\AAA\.jenkins /root/.jenkins jenkins迁移只需要备份JENKINS_HOME的内容即可 ...
- CentOS7开启docker远程访问
在 CentOS 中没有 /etc/default/docker,另外在 CentOS7 中也没有找到 /etc/sysconfig/docker这个配置文件. 在 /usr/lib/systemd/ ...
- win7中如何恢复UltraEdit的默认配置
自己win7上安装的UltraEdit 17.10.0.1015.由于编码格式不知道什么时候被改乱了,导致UE编写的java程序出现如下两种编译错误: D:\jcode>javac testco ...
- bzoj 3779 重组病毒 好题 LCT+dfn序+线段树分类讨论
题目大意 1.将x到当前根路径上的所有点染成一种新的颜色: 2.将x到当前根路径上的所有点染成一种新的颜色,并且把这个点设为新的根: 3.查询以x为根的子树中所有点权值的平均值. 分析 原题codec ...