Skip to content
小马哥 edited this page Nov 18, 2017 · 8 revisions

概述

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

其中 HTTP 客户端支持跟 HTTP、HTTPS 绑定的 hprose 服务器通讯。

Socket 客户端支持跟 TCP、UNIX Socket 绑定的 hprose 服务器通讯,并且支持全双工和半双工两种模式。

WebSocket 客户端支持跟 ws、wss 绑定的 hprose 服务器通讯。

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

创建客户端

创建客户端有两种方式,一种是直接使用特化的构造器函数,另一种是使用工厂构造器函数。

第一种方式返回的是具体的客户端结构体指针对象,第二种方式返回的是客户端接口对象。

使用特化的构造器函数创建客户端

特化的构造器函数有下面几个:

func NewHTTPClient(uri ...string) (client *HTTPClient)
func NewTCPClient(uri ...string) (client *TCPClient)
func NewUnixClient(uri ...string) (client *UnixClient)
func NewFastHTTPClient(uri ...string) (client *FastHTTPClient)
func NewWebSocketClient(uri ...string) (client *WebSocketClient)

他们分别返回 net/http 客户端、TCP 客户端、Unix Socket 客户端、fasthttp 客户端和 WebSocket 客户端。

这几个客户端的结构体对象指针都实现了 Client 接口。

每个客户端可以接受 1 个或多个服务器地址,且地址开始的 scheme 部分必须相同。

对于 fasthttp 客户端和 net/http 客户端来说,它们只接受 httphttps 开头的地址。

对于 TCP 客户端,只接受 tcptcp4tcp6 开头的地址。

对于 Unix Socket 客户端,只接受 unix 开头的地址。

对于 WebSocket 客户端,只接受 wswss 开头的地址。

其中 fasthttp 和 WebSocket 客户端已经移动到 rpc/fasthttp 和 rpc/websocket 目录下。

使用工厂构造器函数创建客户端

func NewClient(uri ...string) Client

该方法也是接受 1 个或多个服务器地址,且地址开始的 scheme 部分必须相同。

该方法接受 http, https, tcp, tcp4, tcp6, unix, ws, wss 开头的地址。该方法会根据所指定的 scheme 来自动识别该创建哪种具体的客户端对象。

如果指定的是 httphttps 的地址,默认情况下是返回 net/http 的客户端。

你可以使用 import _ "github.com/hprose/hprose-golang/rpc/fasthttp" 方式来初始化 fasthttp 的客户端,例如:

import (
	"github.com/hprose/hprose-golang/rpc"
	_ "github.com/hprose/hprose-golang/rpc/fasthttp"
)

client := rpc.NewClient("http://127.0.0.1:8080/")

使用 fasthttp 客户端会比使用 net/http 快很多。

使用 WebSocket 客户端时,也需要使用 import _ "github.com/hprose/hprose-golang/rpc/websocket" 方式来初始化 WebSocket 的客户端,例如:

import (
	"github.com/hprose/hprose-golang/rpc"
	_ "github.com/hprose/hprose-golang/rpc/websocket"
)

client := rpc.NewClient("ws://127.0.0.1:8080/")

客户端事件

客户端事件通过 SetEvent 方法进行设置。

客户端事件有两个,它们分别定义为:

type onErrorEvent interface {
	OnError(name string, err error)
}

type onFailswitchEvent interface {
	OnFailswitch(Client)
}

因为 go 语言不需要显式实现接口的特点,所以这两个事件本身被设置为私有接口。但这并不影响你去实现它,这两个事件可以单独实现,也可以一起实现。通过 SetEvent 方法进行设置时,设置的事件对象中应该包含所有你希望实现的事件。

OnError 事件

在客户端异步调用发生了错误,但是回调函数中的最后一个参数不是 error 类型参数时,该事件会被触发。当异步调用的回调函数本身发生了 panic 时,该事件也会被触发。

OnFailswitch 事件

如果调用的 Failswitch 设置为 true,当在调用中出现网络错误,进行服务器切换时,该事件会被触发。

客户端方法

URI 方法

URI() string 

返回当前客户端使用的服务器地址。

SetURI 方法

SetURI(uri string)

设置当前客户端使用的服务器地址。如果你想要设置多个服务器地址,请使用 SetURIList 方法代替该方法。

URIList 方法

URIList() []string

返回当前客户端可使用的服务器地址列表。

SetURIList 方法

SetURIList(uriList []string)

设置当前客户端可使用的服务器地址列表,请注意,设置的服务器地址列表会重新随机排序,所以 uriList 参数中服务器地址列表的顺序跟使用 URIList 得到的服务器地址列表的顺序并不一致。

即使是同一个列表,在使用 SetURIList 设置后,使用 URIList 得到的服务器地址列表顺序也不相同。但是 uriList 参数中的列表顺序并不会被改变。

TLSClientConfig 方法

TLSClientConfig() *tls.Config

返回客户端的 tls 配置。

SetTLSClientConfig 方法

SetTLSClientConfig(config *tls.Config) 

设置客户端的 tls 配置。

Retry 方法

Retry() int

返回幂等性调用在因网络原因调用失败后的默认重试次数。

SetRetry 方法

SetRetry(value int)

设置默认重试次数,只有在调用被设置为幂等性调用时,该设置才起作用,该设置的初始值为 10

Timeout 方法

Timeout() time.Duration

返回客户端调用超时时间的默认值。

SetTimeout 方法

SetTimeout(value time.Duration)

设置客户端调用超时时间的默认值,该设置初始值为 30 秒。

Failround 方法

Failround() int

当调用中发生服务地址切换时,如果服务列表中所有的服务地址都切换过一遍之后,该值会加 1。初始值为 0。你可以根据该方法返回值来决定是否更新服务列表。更新服务列表可以使用 SetURIList 方法,当调用 SetURIList 后,该值会被重置为 0

SetEvent 方法

SetEvent(ClientEvent)

用于设置客户端事件。

SetUserData 方法

SetUserData(userdata map[string]interface{}) Client

设置客户端 Context 中的 UserData 的初始值。

远程调用

通过 Invoke 方法同步调用

Invoke(string, []reflect.Value, *InvokeSettings) ([]reflect.Value, error)

Invoke 方法有三个输入参数,两个输出参数。

第一个输入参数是远程方法名。

第二个输入参数是远程方法参数。

第三个输入参数是远程调用设置。

第一个输出参数表示返回的结果。

第二个输出参数表示返回的错误。

因为使用该方法进行远程调用时,远程方法参数和返回结果都是 reflect.Value 类型的 slice,使用较为麻烦,所以,通常不会直接使用该方法进行远程调用。

该方法的使用方式在 Hprose 服务器 AddMissingMethod 方法一节有详细介绍,这里就不在单独举例了。

通过 Go 方法异步调用

Go(string, []reflect.Value, *InvokeSettings, Callback)

其中 Callback 定义如下:

type Callback func([]reflect.Value, error)

Callback 的第一个参数是返回的结果,第二个参数是返回的错误。它们跟 Invoke 方法的两个输出参数相对应。该方法通常也很少会被使用。

使用 UseService 方法生成远程服务代理对象

所以上面的介绍的两种调用方法,可以实现同步调用和异步调用,但是使用起来较为复杂,所以通常不会直接被使用。

下面将要介绍的方法是最常用的,而且也很方便、灵活、直观。

UseService(remoteService interface{}, namespace ...string)

UseService 方法的第一个参数是一个有效的结构体指针,或者是有效的结构体指针的指针,总之不能为 nil 指针。它所指向的结构体中定义了一系列的函数字段,这些函数字段会被 UseService 初始化为具体的远程调用。之后你就可以像调用本地方法一样去调用远程方法了。

namespace 参数表示远程服务的名称空间,该参数可以是 0 到 1 个值。当省略时,默认值为空。

同步调用

例如下面定义一个 HelloService 结构体:

type HelloService struct {
	Hello func(string) string
}

然后使用:

var hello *HelloService
client.UseService(&hello)

初始 hello 对象。

最后就可以使用:

result := hello.Hello("World")

的方式来直接调用远程的 Hello 函数了。

上面的 HelloService 中,我们的 Hello 方法只有一个字符串返回值,在这种情况下,如果在进行 Hello 调用过程中发生错误,将会发生 panic。如果不希望发生 panic,可以像下面这样定义:

type HelloService struct {
	Hello func(string) (string, error)
}

异步调用

type HelloService struct {
	Hello func(func(string, error), string)
}

上面这个 Hello 是异步调用形式。

通过这种方式来定义异步调用时,回调函数必须是函数字段类型的第一个参数。

回调函数的输入参数形式跟同步调用的输出参数形式是相同的,最后的 error 返回值同样是可选的,当省略时,如果定义了 OnError 事件,该事件会在异步调用发生错误时被触发,否则异步调用的错误将被忽略。

回调函数没有输出参数。

同名重载

在一个用于远程调用代理的结构体中,你可以同时定义多个函数字段,甚至可以对同一个远程方法定义多个不同名称的函数字段,例如:

type HelloService struct {
	Hello      func(string) (string, error)
	AsyncHello func(func(string, error), string) `name:"hello"`
}

上面的 AysncHello 函数跟 Hello 函数对应的都是同一个远程方法 hello,这是通过 name:"hello" 这个标记来指定的。

嵌套组合

结构体还支持嵌套组合,例如:

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

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}

type UserService struct {
	Add    func(user *User) (id int, error)
	Delete func(id int) (bool, error)
	Update func(id int, user *User) (bool, error)
	Get    func(id int) (*User, error)
	List   func(page int, count int) ([]*User, error)
}

type Order struct {
	ID          int    `json:"id"`
	Name        string `json:"name"`
	OrderNumber string `json:"orderNumber"`
	Amount      int64  `json:"amount"`
	User        *User  `json:"user"`
}

type OrderService struct {
	Add    func(order *Order) (id int, error)
	Delete func(id int) (bool, error)
	Update func(id int, order *Order) (bool, error)
	Get    func(id int) (*Order, error)
	List   func(page int, count int) ([]*List, error)
}

type MyService struct {
	Login func(name string, password string) (bool, error)
	User UserService
	Order OrderService
}

func init() {
	io.Register((*User)(nil), "User", "json")
	io.Register((*Order)(nil), "Order", "json")
}

func NewMyService(client rpc.Client) (myService *MyService) {
	client.UseService(&myService)
	return
}

比如上面这段程序中,UserOrder 是两个用于传递数据的结构体,它们在 init 函数中被注册。

UserServiceOrderService 是两个用于远程服务代理的结构体,MyService 也是,但 MyService 中还包含了 UserServiceOrderService 这两个服务。

那么现在用 NewMyService 方法创建的 myService 对象上就包含了所有这些服务,嵌套的结构体名称会自动作为名称空间附加在调用的方法名之前,例如:

    myService.User.Delete(1)

当调用上面这个方法时,其实调用的是 User_Delete 这个远程方法。

上面这个嵌套结构体只有一层,实际上你还可以定义更复杂的多层嵌套,这里就不再举例说明了。

InvokeSettings 结构体

前面介绍 InvokeGo 方法时,它们的最后一个输入参数都是一个 InvokeSettings 结构体指针。因为该设置涉及到的内容很多,故而放在此处单独介绍,而且它里面的选项跟使用远程服务代理对象调用方式也有密切的对应关系,我们会在这里一并介绍。

InvokeSettings 结构体定义如下:

type InvokeSettings struct {
	ByRef          bool
	Simple         bool
	Idempotent     bool
	Failswitch     bool
	Oneway         bool
	JSONCompatible bool
	Retry          int
	Mode           ResultMode
	Timeout        time.Duration
	ResultTypes    []reflect.Type
}

我们下面就对该结构体中的几个字段进行详细说明。

ByRef 字段

该字段表示调用是否为引用参数传递,默认值为 false,即不使用引用参数传递。当使用代理对象调用时,它使用 byref 标记来定义,例如:

package main

import (
	"fmt"

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

func swap(a, b *int) {
	*b, *a = *a, *b
}

type RO struct {
	Swap func(a, b *int) error `byref:"true"`
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("swap", swap)
	server.Handle()
	client := rpc.NewClient(server.URI())
	var ro *RO
	client.UseService(&ro)
	a := 1
	b := 2
	ro.Swap(&a, &b)
	fmt.Println(a, b)
	client.Close()
	server.Close()
}

运行该程序,执行结果为:

2 1

如果去掉 byref 标记,或者将 byref 标记的值设为 "false",结果为

1 2

如果仅仅将 byref 标记设置为 "true",而方法的参数不是指针类型,而是值类型,那么虽然调用会以引用参数传递的方式调用,但是 a, b 的值并不会被交换。

所以引用参数传递必须满足两个条件,参数为指针类型,设置了 byref 标记为 "true"

Simple 字段

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

该字段默认值为 false,即不使用简单模式序列化请求参数。当使用代理对象调用时,它使用 simple 标记来定义,例如:

package main

import (
	"fmt"

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

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

// Stub is ...
type Stub struct {
	Hello func(string) (string, error) `simple:"true"`
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("hello", hello)
	server.Handle()
	client := rpc.NewClient(server.URI())
	var stub *Stub
	client.UseService(&stub)
	fmt.Println(stub.Hello("World"))
	client.Close()
	server.Close()
}

服务器端在发布服务时也可以设置服务函数(方法)为 Simple 模式,服务器端的设置只影响服务的返回结果,客户端的设置只影响客户端发送的参数,所以这两个设置是独立的,客户端和服务器端不必同时设置,但可以同时设置。

Idempotent 字段

该字段表示调用是否为幂等性调用,幂等性调用表示不论该调用被重复几次,对服务器的影响都是相同的。幂等性调用在因网络原因调用失败时,会自动重试。如果 Failswitch 字段同时被设置为 true,并且客户端设置了多个服务地址,在重试时还会自动切换地址。

该字段默认值为 false,即表示当前调用是非幂等性调用,出错不进行重试。当使用代理对象调用时,它使用 idempotent 标记来定义。

Failswitch 字段

该字段表示当前调用在因网络原因失败时是否自动切换服务地址。

该字段默认值为 false,即表示当出错时不自动切换服务地址。当使用代理对象调用时,它使用 failswitch 标记来定义。

Retry 字段

该字段表示幂等性调用在因网络原因调用失败后的重试次数。只有 Idempotent 字段为 true 时,该设置才有作用。

该字段默认值为 0,表示重试次数使用客户端的默认重试次数。当使用代理对象调用时,它使用 retry 标记来定义。例如:

package main

import (
	"fmt"

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

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

// Stub is ...
type Stub struct {
	Hello func(string) (string, error) `simple:"true" idempotent:"true" retry:"30"`
}

func main() {
	server := rpc.NewTCPServer("")
	server.AddFunction("hello", hello)
	server.Handle()
	client := rpc.NewClient(server.URI())
	var stub *Stub
	client.UseService(&stub)
	fmt.Println(stub.Hello("World"))
	client.Close()
	server.Close()
}

这些标记都是各自独立的,可以同时使用多个标记。

Oneway 字段

该字段表示当前调用是否不等待返回值。当该字段设置为 true 时,请求发送之后,并不等待服务器返回结果,而是直接向下继续执行。

该字段默认值为 false,即表示调用需要等待返回值。当使用代理对象调用时,它使用 oneway 标记来定义。

Timeout 字段

该字段表示当前调用的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。

该字段默认值为 0,即表示使用客户端默认的超时时间。当使用代理对象调用时,它使用 timeout 标记来定义。

需要注意,因为是 time.Duration 类型的数据,所以它的单位是纳秒。

JSONCompatible 字段

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

该字段默认为 false。当使用代理对象调用时,它使用 jsoncompat 标记来定义。

ResultTypes 字段

该字段用来设置返回值的类型,当该值为 nil 时,表示没有返回值。因为 go 支持多结果返回,因此可以设置多个返回值类型。最后的错误类型不包含在这个字段中。

该字段默认为 nil。当使用代理对象调用时,它直接通过代理对象结构体中函数字段的返回值类型或回调函数的参数类型来定义。

SetUserData 方法

func (settings *InvokeSettings) SetUserData(data map[string]interface{})

InvokeSettings 结构体指针上还定义了一个 SetUserData 方法,它用来设置 context 上的 UserData 的初始值。当使用代理对象调用时,它使用 userdata 标记来定义,userdata 的标记值使用 JSON 格式表示。比如:

type HelloService struct {
	Hello func(string) (string, error) `simple:"true" userdata:"{\"cache\":true}"`
}

userdata 的设置值是用户自己设置的,它本身没有什么特殊含义,但是用户可以通过它跟 Hprose 中间件或者 Hprose 过滤器结合,来实现功能强大的插件。我们在后面介绍 Hprose 中间件Hprose 过滤器时,会举例介绍它的用法。

HTTP 和 FastHTTP 客户端的共同设置

DisableGlobalCookie 全局变量

默认值为 false。该设置表示所有客户端是否共享 Cookie 管理器。默认值表示共享。当该变量被设置为 true 时,每个客户端会拥有一个独立的 Cookie 管理器。

HTTPClientFastHTTPClient 结构体指针上定义了如下字段和方法。

Header 字段

用来设置自定义的 HTTP 头信息。

MaxConcurrentRequests 方法

该方法返回最大并发请求数。

SetMaxConcurrentRequests 方法

该方法用于设置最大并发请求数,当并发请求超过该设置之后,后面的请求将会排队等待。该设置默认值为 10

KeepAlive 方法

返回客户端是否支持 HTTP 持久连接。默认值为 true

SetKeepAlive 方法

设置客户端是否支持 HTTP 持久连接。

Compression 方法

返回客户端是否使用压缩传输。默认值为 false

SetCompression 方法

设置客户端是否使用压缩传输。

HTTP 客户端特殊设置

HTTPClient 结构体指针上还定义了如下特殊字段:

Transport 字段

该字段是一个匿名字段,类型为 http.Transport,你可以通过对它上面的字段进行设置来改变 HTTP 客户端的一些特殊设置。

MaxIdleConnsPerHost 字段

该字段其实是 Transport 结构体上的一个字段,它表示最多允许的空闲持久连接数。这里单独拿出来说是因为它的默认值也是 10,目的是跟 MaxConcurrentRequests 的值一样,在这种情况下,所有的请求都可以通过持久连接发送,可以有效的提高客户端跟服务器的通讯效率。所以当你希望改变 MaxConcurrentRequests 设置时,最好保持跟 MaxIdleConnsPerHost 设置的一致性。

FastHTTP 客户端特殊设置

FastHTTPClient 结构体指针上还定义了如下特殊字段:

Client 字段

该字段是一个匿名字段,类型为 fasthttp.Client,你可以通过对它上面的字段进行设置来改变 FastHTTP 客户端的一些特殊设置。

Socket 客户端特殊设置

TCPClientUnixClient 这两个结构体包含 SocketClient 这个匿名字段,因此,SocketClient 上的字段和方法都会被继承。

ReadBuffer 字段

设置与连接相关的操作系统接收缓冲区的大小。当为 0 时,表示不进行设置,使用系统默认值。

WriteBuffer 字段

设置与连接相关的操作系统发送缓冲区的大小。当为 0 时,表示不进行设置,使用系统默认值。

TLSConfig 字段

用于存取 TLS 配置,跟使用 TLSClientConfigSetTLSClientConfig 方法存取相同。

IdleTimeout 方法

返回连接池空闲超时时长。

SetIdleTimeout 方法

设置连接池空闲超时时长,默认值为 0,表示永不超时,直到客户端关闭。

MaxPoolSize 方法

返回连接池最大连接数。

SetMaxPoolSize 方法

设置连接池最大连接数。默认为 runtime.NumCPU() 个。

TCP 客户端特殊设置

TCPClient 除了包含上面的继承自 SocketClient 的设置以外,还有以下几个可以设置的字段:

Linger 字段

用于设置当连接中仍有数据等待发送或接受时的 Close 方法的行为。

如果其值小于 0(默认),Close 方法立即返回,操作系统停止后台数据发送;如果其值等于 0Close 立刻返回,操作系统丢弃任何未发送或未接收的数据;如果其值大于 0Close 方法阻塞最多 Linger 秒,等待数据发送或者接收,在一些操作系统中,在超时后,任何未发送的数据会被丢弃。

NoDelay 字段

用于设定操作系统是否应该延迟数据包传递,以便发送更少的数据包(Nagle's算法)。默认为 true,即数据应该在 Write 方法后立刻发送。

KeepAlive 字段

用于设置操作系统是否应该在该连接中发送 keepalive 信息。默认为 true

KeepAlivePeriod 字段

用于设置 keepalive 的周期,超出会断开。默认值为 0,表示使用系统默认设置。

WebSocket 客户端特殊设置

WebSocketClient 结构体指针上只有如下两个可设置的字段:

Header 字段

用来设置自定义的 HTTP 头信息。

MaxConcurrentRequests 方法

该方法返回最大并发请求数。

SetMaxConcurrentRequests 方法

该方法用于设置最大并发请求数,当并发请求超过该设置之后,后面的请求将会排队等待。该设置默认值为 10

Clone this wiki locally