Skip to content
小马哥 edited this page Oct 31, 2016 · 10 revisions

概述

Hprose 2.0 for Golang 支持多种底层网络协议绑定的服务器,比如:HTTP 服务器,Socket 服务器和 WebSocket 服务器。

其中 HTTP 服务器支持在 HTTP、HTTPS 协议上通讯。

Socket 服务器支持在 TCP、Unix Socket 协议上通讯,并且支持全双工和半双工两种模式。

WebSocket 服务器支持在 ws、wss 协议上通讯。

尽管支持这么多不同的底层网络协议,但除了在对涉及到底层网络协议的参数设置和服务启动上有所不同以外,其它的用法都完全相同。因此,我们在下面介绍 Hprose 服务器的功能时,若未涉及到底层网络协议的区别,就以 HTTP 服务器为例来进行说明。

Server 与 Service 的区别

hprose 的服务器端的实现,分为 ServiceServer 两部分。

其中 Service 部分是核心功能,包括接收请求,处理请求,服务调用,返回应答等整个服务的处理流程。

Server 则主要负责启动和关闭服务器,它包括设置服务地址和端口,设置服务器启动选项,启动服务器,接收来自客户端的连接然后传给 Service 进行处理。

hprose 没有为 HTTP/HTTPS 和 WebSocket 服务提供 Server 实现,只提供了 Service 实现。

因为使用 Service 可以更方便、更灵活的跟已有的库和框架结合,例如:net/http、fasthttp、gin、echo、iris 等。这些库和框架都提供了丰富的中间件,在这种情况下,只需要把 Service 作为这些库和框架的中间件来使用就可以了,在这种情况下,我们就不需要使用 Server 了。

对于 TCP 和 UnixSocket 服务,hprose 提供了一个默认的 Server 实现。当开发者没有什么特殊需求,只是希望启动一个独立的 hprose 服务器时,那使用 Server 就是一个最方便的选择了。

Server 部分的实现是很简单的,如果开发者希望把 hprose 服务结合到自己的某个服务器中去,而不是作为一个单独的服务器来运行,在这种情况下,直接使用 Service 就可以了。

创建服务器

创建 http 服务器

package main

import (
	"net/http"
	"runtime"

	"github.com/hprose/hprose-golang/rpc"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	service := rpc.NewHTTPService()
	service.AddFunction("hello", hello)
	http.ListenAndServe(":8080", service)
}

创建 fasthttp 服务器

package main

import (
	"runtime"

	"github.com/hprose/hprose-golang/rpc"
	"github.com/valyala/fasthttp"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	service := rpc.NewFastHTTPService()
	service.AddFunction("hello", hello)
	fasthttp.ListenAndServe(":8080", service.ServeFastHTTP)
}

对比上面两个服务,我们发现服务函数(方法)的编写和发布上是没有区别的,区别只在于创建服务器所使用的构造函数和启动服务器所使用的库。

上面的例子发布的是单个的函数,实际上不仅仅可以发布单个的函数,还可以同时发布多个函数,多个方法,多个结构体对象等等,后面我们会详细介绍这些。这里以发布 hello 函数为例,目的是为了突出创建服务器这个重点。

创建 TCP 服务器

package main

import (
	"runtime"

	"github.com/hprose/hprose-golang/rpc"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	server := rpc.NewTCPServer("tcp4://0.0.0.0:4321/")
	server.AddFunction("hello", hello)
	server.Start()
}

同样服务编写和发布没有区别,区别只在于构造函数和服务的启动。在这里我们创建 TCP 服务器时,使用的是 TCPServer 而不是 TCPService,这可以省去自己创建监听 TCP 服务器的麻烦,但是如果你愿意自己来做这部分也可以,使用 TCPService 就可以了。例如:

	service := rpc.NewTCPService()
	service.AddFunction("hello", hello)
	addr, _ := net.ResolveTCPAddr("tcp", ":4321")
	listener, _ := net.ListenTCP("tcp", addr)
	service.ServeTCP(listener)

当然上面这个使用 service 发布服务的代码只是一个最简单的例子,它并不完备,如果你要自己实现一个完备的 TCP 服务器,还需要做好多工作,上面的代码只是为了说明 servicelistener 如何结合。

创建 Unix Socket 服务器

package main

import (
	"runtime"

	"github.com/hprose/hprose-golang/rpc"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	server := rpc.NewUnixServer("unix:/tmp/my.sock")
	server.AddFunction("hello", hello)
	server.Start()
}

这个跟 TCP 服务很类似,不多作介绍了。关于 UnixService 的用法同上面的 TCPService 也是类似的,也略过不做介绍了。

创建 WebSocket 服务器

package main

import (
	"net/http"
	"runtime"

	"github.com/hprose/hprose-golang/rpc"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	service := rpc.NewWebSocketService()
	service.AddFunction("hello", hello)
	http.ListenAndServe(":8080", service)
}

这个跟 HTTP 服务器的创建很类似,而且这个 WebSocket 的 hprose 服务器,同时也是一个 HTTP 的 hprose 服务器,它可以同时接受 HTTP 和 WebSocket 的 hprose 客户端请求。

跟 gin 框架结合

HTTP\HTTPS 和 WebSocket 服务并没有单独实现 Server,而只是实现了 Service,所以,这些服务器的启动与关闭依赖于你所使用的 http 库或框架。上面在创建服务器一节中,我们已经介绍了使用 net/http 和 fasthttp 如何来创建服务器。下面我们再举一个在 gin 框架下面如何发布服务的例子:

package main

import (
	"github.com/hprose/hprose-golang/rpc"
	"gopkg.in/gin-gonic/gin.v1"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	service := rpc.NewHTTPService()
	service.AddFunction("hello", hello)
	router := gin.Default()
	router.Any("/hello", func(c *gin.Context) {
		service.ServeHTTP(c.Writer, c.Request)
	})
	router.Run(":8080")
}

WebSocket 服务器跟上面的写法类似,只要把 rpc.NewHTTPService() 换成 rpc.NewWebSocketService() 就可以了,其它代码都不需要修改。

跟 echo 框架结合

package main

import (
	"github.com/hprose/hprose-golang/rpc"
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	service := rpc.NewHTTPService()
	service.AddFunction("hello", hello)
	e := echo.New()
	e.Any("/hello", standard.WrapHandler(service))
	e.Run(standard.New(":8080"))
}

WebSocket 服务器跟上面的写法类似,只要把 rpc.NewHTTPService() 换成 rpc.NewWebSocketService() 就可以了,其它代码都不需要修改。

跟 iris 框架结合

package main

import (
	"github.com/hprose/hprose-golang/rpc"
	"github.com/kataras/iris"
)

func hello(name string) string {
	return "Hello " + name + "!"
}

func main() {
	service := rpc.NewFastHTTPService()
	service.AddFunction("hello", hello)
	iris.Any("/hello", func(c *iris.Context) {
		service.ServeFastHTTP(c.RequestCtx)
	})
	iris.Listen(":8080")
}

跟 gin 和 echo 不同,iris 是基于 fasthttp 的,所以这里创建的是 FastHTTPService

Socket 服务器的启动与关闭

下面来介绍一下关于 TCPServerUnixServer 的启动与关闭。

Start 和 Handle 方法区别

TCPServerUnixServer 都包含 StartHandle 两个方法,它俩的功能很接近,都是用于启动服务。区别是:

Handle 启动服务是非阻塞的,Handle 启动服务后,代码会继续执行。

Start 启动服务之后会阻塞不再向下执行。Start 启动之后,会监听以下的系统信号:

  • syscall.SIGHUP
  • syscall.SIGQUIT
  • syscall.SIGTERM
  • syscall.SIGINT
  • syscall.SIGKILL

当收到 syscall.SIGHUP 信号时,服务会重启。当收到其它几个信号时,服务会关闭。

Stop 和 Close 方法的区别

Stop 对应 Start 方法,Close 对应 Handle 方法。即,Stop 方法实际上是发送一个 syscall.SIGQUIT 信号给 Start 启动的服务器以结束服务,对于直接使用 Handle 方法启动的服务,Stop 方法无效。使用 Close 方法可以关闭使用 Handle 方法启动的服务。

Restart 方法

Restart 这个也是用于重启使用 Start 方法启动的服务的,对于直接使用 Handle 方法启动的服务无效。

服务设置

Debug 字段

该设置默认值是 false,当发生错误时,只返回错误信息本身。

当该字段设置为 true 时,当发生 panic 时,会将整个 panic 的错误堆栈信息返回给客户端,在用户进行 hprose 服务开发时,该设置可以帮助你快速定位错误位置。

ErrorDelay 字段

该设置为整型值,默认值为 10 秒。

该字段表示在调用执行时,如果发生异常,将延时一段时间后再返回给客户端。

在关闭该功能的情况下,如果某个服务发生 panic 或者返回错误,客户端又反复重试该调用,可能会导致服务器不能正常处理其它业务请求而造成的假死机现象。使用该功能,可以避免这种问题发生。

如果你不需要该功能,设置为 0 就可以关闭它。

UserData 字段

该设置为 map[string]interface{} 类型,默认值为 nil

该字段表示默认传入 contextUserData 值。关于 contextcontextUserData 在后面介绍中间件时,会举例说明。这里就不展开讨论了。

发布服务

hprose 为发布服务提供了多个方法,这些方法可以随意组合,通过这种组合,你所发布的服务将不会局限于某一个函数,某一个方法,某一个对象,而是可以将不同的函数和方法随意重新组合成一个服务。

AddFunction 方法

AddFunction(name string, function interface{}, option ...Options) Service

该方法的用于发布一个函数(命名函数或匿名函数都可以)或者一个已绑定的方法。

第一个参数 name 表示发布的函数名,它与实际的函数名或方法名无关,可以相同,也可以不同。

第二个参数是实际函数,它可以是一个函数(命名函数或匿名函数都可以)或者一个已绑定的方法,参数个数不限,返回结果个数不限,但参数和结果都必须是可序列化类型(不可序列化类型包含函数类型、chan 类型以及 unsafe.Pointer 类型),如果返回结果中包含 error 类型的结果,需要是最后一个结果参数。

第三个参数 option 是服务发布选项,类型是 Options,该类型是一个结构体,它是可选的,最多一个。一会儿,我们会详细介绍它。

该方法的返回值是服务器对象本身,目的是为了可以方便的进行链式调用。

下面来介绍一下 Options 结构体各个字段的意义。

Options 结构体

Mode 字段

该字段有 4 个取值:

  • Normal
  • Serialized
  • Raw
  • RawWithEndTag

该字段用来指明方法调用结果的类型。

如果返回结果就是普通对象,那么不需设置该字段,也就是默认值 Normal

如果返回结果是 hprose 序列化之后的数据([]byte 类型),那么设置该字段为 Serialized 可以避免该结果被二次序列化。

如果返回结果是一个完整的响应,当这个响应结果不带 hprose 结束符时,需要将该字段设置为 Raw。如果这个响应结果带 hprose 结束符,则设置这个字段为 RawWithEndTag

这个参数主要用于存储转发的 hprose 代理服务器。通常我们不需要用到这个参数。

Simple 字段

该字段表示调用所返回的结果是否为简单数据。简单数据是指:nil、数字(包括有符号和无符号的各种长度的整数类型、浮点数类型,*big.Int, *big.Rat, *big.Float)、bool 值、字符串、二进制数据、日期时间等基本类型的数据或者不包含引用的数组、slice, map 和结构体对象。当该字段设置为 true 时,在进行序列化操作时,将忽略引用处理,加快序列化速度。但如果数据不是简单类型的情况下,将该字段设置为 true,可能会因为死循环导致堆栈溢出的错误。简单的讲,用 JSON 可以表示的数据都是简单数据。

Oneway 字段

该字段表示在收到调用时,是否立即返回空响应之后再执行调用。通常对于没有返回值的服务方法,而且又不需要保证跟后续调用有同步关系的情况下,可以将该参数设置为 true,这将加快客户端的响应速度。

NameSpace 字段

该字段用于设置发布方法的名称空间,它会被加在函数(方法)名前面,并且用下划线(_)作为分隔。客户端可以直接以 NameSpace_MethodName 方式调用,但是对于不同语言的客户端还支持使用类似 NameSpace.MethodNameNameSpace->MethodName 的方式调用。NameSpace 的值本身也可以使用下划线(_)分隔主次名空间。

JSONCompatible 字段

该字段为 true 时,map 类型的默认映射类型改为 map[string]interface{},否则默认映射类型为 map[interface{}]interface{},当你确认你的 map 数据是 JSON 兼容的,即 map 的 key 一定是 string 类型或者可以转换为 string 类型的数据时,可以将该字段设置为 true

AddFunctions 方法

AddFunctions(names []string, functions []interface{}, option ...Options) Service

该方法用于发布一组相同选项的函数或已绑定方法。

names 是发布的函数名列表。functions 是发布的函数列表。这两个列表中的函数名和函数是一一对应的,所以长度必须相等。

AddMethod 方法

AddMethod(name string, obj interface{}, alias string, option ...Options) Service

该方法用于发布一个对象上的方法。

name 为对象上的方法名(注意大小写一定要跟对象上定义的方法名大小写相同,如果对象类型的定义在不同的包中,则方法必须是可导出方法),obj 是持有发布方法的对象。。alias 是远程调用使用的方法名,当其值为 "" 时,表示跟 name 一致,option 含义同上,可以省略。

AddMethods 方法

AddMethods(names []string, obj interface{}, aliases []string, option ...Options) Service

该方法用于发布一个对象上的多个方法。

该方法同上面的 AddMethod 方法类似,但它可以发布多个方法。names 是方法名列表,aliases 是远程调用的函数名列表,可以为 nil,在不为 nil 的情况下,需要一一对应,长度相等。

AddInstanceMethods 方法

AddInstanceMethods(obj interface{}, option ...Options) Service

跟上面几个方便相比,这是一个比较常用的用于发布服务的方法。

它会发布 obj 上的所有可导出的方法和函数字段,但是如果 obj 是一个结构体对象,对于其匿名字段上继承的方法和函数字段并不会发布。

那如果我先连继承的方法一起导出该怎么办呢?有两种方法:

第一种是,对于 obj 和它上面的匿名字段分别调用 AddInstanceMethods 方法进行发布,或者调用 AddFunctionAddMethodAddFunctionsAddMethods 来一个一个或一组一组的发布。这种方法比较繁琐。

第二种方法是使用下面的 AddAllMethods 方法,它可以让你一步到位。

AddAllMethods 方法

AddAllMethods(obj interface{}, option ...Options) Service

该方法不但会发布 obj 上的所有可导出的方法和函数字段。而且会发布 obj 所有字段上的方法和函数字段,对于非匿名字段,还会自动添加名空间。

下面我们来举例说明 AddInstanceMethodsAddAllMethods 具体使用方法和区别。

假设我们已经定义了一下数据结构:

// Foo ...
type Foo int

// MethodA1 ...
func (Foo) MethodA1() {}

// MethodA2 ...
func (*Foo) MethodA2() {}

// Bar ...
type Bar struct {
	Foo
	FuncB func()
}

// MethodB1 ...
func (Bar) MethodB1() {}

// MethodB2 ...
func (*Bar) MethodB2() {}

// Foobar ...
type Foobar struct {
	FooField Foo
	BarField *Bar
}

现在我们用 http 方式来发布:

func main() {
	service := rpc.NewHTTPService()
	service.AddInstanceMethods(Foobar{})
	http.ListenAndServe(":8080", service)
}

现在我们打开浏览器,输入:

http://127.0.0.1:8080/

我们会看到一下输出:

Fa2{u#s8"MethodC1"}z

这个输出中,u# 是一个特殊的函数,它是 hprose 2.0 默认发布的一个函数,该函数的作用是生成一个唯一的标示,客户端在推送时,可以通过它来获取客户端的唯一标示。这里我们略微一提,后面在推送服务一章再对其详解。我们在这里只要知道它跟我们发布的方法没有关系就可以了。

我们从上面的输出中可以看出,我们的代码只发布了方法 MethodC1 这个方法。

稍微改一下:

func main() {
	service := rpc.NewHTTPService()
	service.AddInstanceMethods(&Foobar{})
	http.ListenAndServe(":8080", service)
}

再重新执行该服务器。打开浏览器,我们会看到内容变成下面这样:

Fa3{u#s8"MethodC1"s8"MethodC2"}z

也就是说,定义在指针上的方法,只有使用指针对象才能发布,而定义在值上的方法,使用指针或值对象都可以发布。

我们再来看一下 AddAllMethods 是怎么发布的:

func main() {
	service := rpc.NewHTTPService()
	service.AddAllMethods(&Foobar{})
	http.ListenAndServe(":8080", service)
}

现在我们会看到内容变成下面这样:

Fa4{u#s8"MethodC1"s8"MethodC2"s17"FooField_MethodA1"}z

FooField_MethodA1 方法也被发布了,但是 FooField_MethodA2BarField 上面的方法都没有发布。原因是,FooField 是一个值字段,所以 FooField_MethodA2 不会被发布。而 BarField 这个指针字段并没有初始化,所以也不会被发布。

那我们再来改一下:

func main() {
	service := rpc.NewHTTPService()
	service.AddAllMethods(&Foobar{BarField:&Bar{}})
	http.ListenAndServe(":8080", service)
}

现在浏览器输出变成了下面这样:

Fa8{u#s8"MethodC1"s8"MethodC2"s17"FooField_MethodA1"s17"BarField_MethodA1"s17"BarField_MethodA2"s17"BarField_MethodB1"s17"BarField_MethodB2"}z

在这里,MethodC1MethodC2 被发布没有疑问。

FooField_MethodA1BarField_MethodA1 分别来自字段 FooFieldBarField,后一个还是通过匿名自动 “继承” 自 Foo 的方法。AddAllMethods 自动把字段名作为名空间将它们区别开了。

BarField 字段现在是一个已经初始化了的指针字段,所以定义在它指针和值上的方法都发布了,包括从匿名字段继承来的方法也发布了。

前面我们还说过除了方法,函数字段也会被发布,我们在 Bar 结构体上定义了 FuncB 这个函数字段,为何这个函数字段没有被发布呢?

原因是在发布之前,它没有被赋值,如果函数字段的值为 nil 则这个函数字段不会被发布。我们把上面的程序稍微修改一下,再来看一下是不是这样:

func main() {
	service := rpc.NewHTTPService()
	foobar := &Foobar{}
	foobar.BarField = new(Bar)
	foobar.BarField.FuncB = func() {}
	service.AddAllMethods(foobar)
	http.ListenAndServe(":8080", service)
}

现在输出变成了这样:

Fa9{u#s8"MethodC1"s8"MethodC2"s17"FooField_MethodA1"s17"BarField_MethodA1"s17"BarField_MethodA2"s17"BarField_MethodB1"s17"BarField_MethodB2"s14"BarField_FuncB"}z

好了,现在 BarField_FuncB 也被发布了。

在上面关于 AddInstanceMethodsAddAllMethods 例子中,我们发布的方法既没有参数也没有结果,这仅仅是为了举例方便,而不是说 hprose 只能发布这样的方法,实际上 hprose 可以发布的方法,对参数个数没有限制,对返回值个数也没有限制。甚至连可变参数的方法都支持。

AddMissingMethod 方法

type MissingMethod func(name string, args []reflect.Value, context Context) []reflect.Value
AddMissingMethod(method MissingMethod, option ...Options) Service

该方法用于发布一个用于处理客户端调用缺失服务的函数。缺失服务是指服务器端并没有明确发布的远程函数/方法。例如:

在服务器端没有发布 hello 函数时,在默认情况下,客户端调用该函数,服务器端会返回 `'Can't find this function hello().' 这样一个错误。

但是如果服务器端通过本方法发布了一个用于处理客户端调用缺失服务的 method,则服务器端会返回这个 method 函数(方法)的返回值。

该方法还可以用于做 hprose 的代理服务器,例如:

package main

import (
	"net/http"
	"reflect"

	"github.com/hprose/hprose-golang/rpc"
)

type HproseProxy struct {
	client rpc.Client
}

func newHproseProxy() *HproseProxy {
	proxy := new(HproseProxy)
	proxy.client = rpc.NewClient("http://www.hprose.com/example/")
	return proxy
}

func (proxy *HproseProxy) MissingMethod(
	name string, args []reflect.Value, context rpc.Context) []reflect.Value {
	result, err := proxy.client.Invoke(name, args, &rpc.InvokeSettings{
		Mode:        rpc.Raw,
		ResultTypes: []reflect.Type{reflect.TypeOf((*interface{})(nil)).Elem()},
	})
	if err != nil {
		panic(err)
	}
	return result
}

func main() {
	service := rpc.NewHTTPService()
	service.AddMissingMethod(newHproseProxy().MissingMethod, rpc.Options{Mode: rpc.Raw})
	http.ListenAndServe(":8080", service)
}

运行改程序,用浏览器打开服务地址,我们会看到:

Fa2{u#u*}z

其中 u* 表示的就是这个 MissingMethod。现在如果调用 hello 方法,我们就会得到 http://www.hprose.com/example/ 这个服务地址提供的 hello 方法的返回值了。

MissingMethod 里面的进行 Invoke 调用时,我们设置了调用模式是 Raw,这样 MissingMethod 的返回值就是远程服务的原始未解析数据,省去了反序列化的过程,而我们发布的 MissingMethod 设置了调用模式是 Raw,这样就可以直接把 MissingMethod 的结果传递给客户端,省去了序列化过程。所以使用这种方式来实现 hprose 代理会更加高效。

AddNetRPCMethods 方法

AddNetRPCMethods(rcvr interface{}, option ...Options) Service

该方法用于发布为 net/rpc 编写的 RPC 服务。只有满足如下标准的方法才会被当做 net/rpc 的远程服务方法,其余方法会被忽略:

  • 方法是导出的
  • 方法有两个参数,都是导出类型或内建类型
  • 方法的第二个参数是指针
  • 方法只有一个error接口类型的返回值

事实上,方法必须看起来像这样:

func (t *T) MethodName(argType T1, replyType *T2) error

通过 hprose 发布的 net/rpc 服务并不能用 net/rpc 客户端去调用,但是可以用任何语言的 hprose 客户端去调用(当然也包括 golang 的 hprose 客户端)。

调用时,方法名跟发布的方法名相同,参数只有一个,就是第一个参数,而发布的方法中的第二个参数会被作为返回值返回,服务器和客户端的参数类型不必完全一致,但需要是可相互转换的类型,这里可相互转化的含义要比 go 内置的类型转换范围要广一些,比如数字、*big.Int 和数字字符串是可以相互转换的,结构体对象和其指针类型也是可以相互转换的。而发布的方法中的 error 返回值会被作为异常返回(如果返回值为 nil,则没有异常)。

我们下面来看一个具体的例子:

package main

import (
	"errors"
	"net/http"

	"github.com/hprose/hprose-golang/rpc"
)

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() {
	service := rpc.NewHTTPService()
	service.AddNetRPCMethods(new(Arith))
	http.ListenAndServe(":8080", service)
}

这个例子里面,main 函数之前的内容,跟 golang 标准库文档中的 net/rpc 的例子中的内容完全一样。而发布服务则比 net/rpc 里面还要简单一些,而且你可以用任何 hprose 支持的方式来发布。

下面我们再来看一下如何在客户端调用该服务,首先我们来看在 golang 中如何调用:

package main

import (
	"fmt"
	"log"

	"github.com/hprose/hprose-golang/rpc"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Stub struct {
	// Synchronous call
	Multiply func(args *Args) int
	// Asynchronous call
	Divide func(func(*Quotient, error), *Args)
}

func main() {
	client := rpc.NewClient("http://127.0.0.1:8080")
	var stub *Stub
	client.UseService(&stub)
	fmt.Println(stub.Multiply(&Args{8, 7}))
	done := make(chan struct{})
	stub.Divide(func(result *Quotient, err error) {
		if err != nil {
			log.Fatal("arith error:", err)
		} else {
			fmt.Println(result.Quo, result.Rem)
		}
		done <- struct{}{}
	}, &Args{8, 7})
	<-done
}

在这里 Multiply 方法是同步调用,在客户端的定义中,我们会看到跟服务器端定义的方式不同,参数仅仅是服务器端方法的第一个参数,而结果类型是 int,这里我们没有定义 error 类型的返回值,这样如果调用发生错误,程序会发生 panic,如果你不希望发生 panic,你可以在 int 后面再加个 error 类型的结果。

Divide 是异步调用,第一个参数是回调方法,第二个参数才是传给远程服务的参数。

根据对应关系,我想上面的客户端代码并不难理解,所以这里就不做进一步解释了。

下面我们来看一个用 js 调用上面两个方法的例子:

<!DOCTYPE html>
<html>
  <head>
    <title>hprose</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="hprose.js"></script>
  </head>
  <body>
    <script type="text/javascript">
		// The method name is case-insensitive
		var methods = ['multiply', 'divide'];
		var client = hprose.Client.create('http://127.0.0.1:8080/', methods);
		// The first letter of the field name is convert to lowercase.
		client.multiply({a:8, b:7}).then(function(product) {
			return client.divide({a:product, b:6});
		}).then(function(quotient) {
			// The first letter of the field name is convert to lowercase.
			console.log(quotient.quo, quotient.rem);
		}).catch(function(err) {
			// The returned err is here
			console.error(err);
		});
    </script>
  </body>
</html>

在这里,调用的方法名是忽略大小写区别的,我们都用小写的方式也可以成功调用。对于参数的字段和结果中的结构体字段,首字母要改为小写。除了这两点需要注意之外,其它的内容直接参考 hprose-js 的文档就可以。

Remove 方法

Remove(name string) Service

将发布的方法删除。注意这里的 name 参数是指客户端调用的方法名(包括名空间部分)。例如如果要删除下面这个方法:

server.AddFunction("hello", hi, rpc.Options{NameSpace: "Test"})

需要使用:

server.Remove("test_hello")

来删除,注意这里不区分大小写。

Clone this wiki locally