-
Notifications
You must be signed in to change notification settings - Fork 205
Hprose 客户端
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 客户端来说,它们只接受 http
和 https
开头的地址。
对于 TCP 客户端,只接受 tcp
、tcp4
和 tcp6
开头的地址。
对于 Unix Socket 客户端,只接受 unix
开头的地址。
对于 WebSocket 客户端,只接受 ws
和 wss
开头的地址。
其中 fasthttp 和 WebSocket 客户端已经移动到 rpc/fasthttp 和 rpc/websocket 目录下。
func NewClient(uri ...string) Client
该方法也是接受 1 个或多个服务器地址,且地址开始的 scheme 部分必须相同。
该方法接受 http
, https
, tcp
, tcp4
, tcp6
, unix
, ws
, wss
开头的地址。该方法会根据所指定的 scheme 来自动识别该创建哪种具体的客户端对象。
如果指定的是 http
、https
的地址,默认情况下是返回 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
方法进行设置时,设置的事件对象中应该包含所有你希望实现的事件。
在客户端异步调用发生了错误,但是回调函数中的最后一个参数不是 error
类型参数时,该事件会被触发。当异步调用的回调函数本身发生了 panic 时,该事件也会被触发。
如果调用的 Failswitch
设置为 true
,当在调用中出现网络错误,进行服务器切换时,该事件会被触发。
URI() string
返回当前客户端使用的服务器地址。
SetURI(uri string)
设置当前客户端使用的服务器地址。如果你想要设置多个服务器地址,请使用 SetURIList
方法代替该方法。
URIList() []string
返回当前客户端可使用的服务器地址列表。
SetURIList(uriList []string)
设置当前客户端可使用的服务器地址列表,请注意,设置的服务器地址列表会重新随机排序,所以 uriList
参数中服务器地址列表的顺序跟使用 URIList
得到的服务器地址列表的顺序并不一致。
即使是同一个列表,在使用 SetURIList
设置后,使用 URIList
得到的服务器地址列表顺序也不相同。但是 uriList
参数中的列表顺序并不会被改变。
TLSClientConfig() *tls.Config
返回客户端的 tls 配置。
SetTLSClientConfig(config *tls.Config)
设置客户端的 tls 配置。
Retry() int
返回幂等性调用在因网络原因调用失败后的默认重试次数。
SetRetry(value int)
设置默认重试次数,只有在调用被设置为幂等性调用时,该设置才起作用,该设置的初始值为 10
。
Timeout() time.Duration
返回客户端调用超时时间的默认值。
SetTimeout(value time.Duration)
设置客户端调用超时时间的默认值,该设置初始值为 30 秒。
Failround() int
当调用中发生服务地址切换时,如果服务列表中所有的服务地址都切换过一遍之后,该值会加 1
。初始值为 0
。你可以根据该方法返回值来决定是否更新服务列表。更新服务列表可以使用 SetURIList
方法,当调用 SetURIList
后,该值会被重置为 0
。
SetEvent(ClientEvent)
用于设置客户端事件。
SetUserData(userdata map[string]interface{}) Client
设置客户端 Context
中的 UserData
的初始值。
Invoke(string, []reflect.Value, *InvokeSettings) ([]reflect.Value, error)
Invoke
方法有三个输入参数,两个输出参数。
第一个输入参数是远程方法名。
第二个输入参数是远程方法参数。
第三个输入参数是远程调用设置。
第一个输出参数表示返回的结果。
第二个输出参数表示返回的错误。
因为使用该方法进行远程调用时,远程方法参数和返回结果都是 reflect.Value
类型的 slice,使用较为麻烦,所以,通常不会直接使用该方法进行远程调用。
该方法的使用方式在 Hprose 服务器 AddMissingMethod 方法一节有详细介绍,这里就不在单独举例了。
Go(string, []reflect.Value, *InvokeSettings, Callback)
其中 Callback
定义如下:
type Callback func([]reflect.Value, error)
Callback
的第一个参数是返回的结果,第二个参数是返回的错误。它们跟 Invoke
方法的两个输出参数相对应。该方法通常也很少会被使用。
所以上面的介绍的两种调用方法,可以实现同步调用和异步调用,但是使用起来较为复杂,所以通常不会直接被使用。
下面将要介绍的方法是最常用的,而且也很方便、灵活、直观。
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
}
比如上面这段程序中,User
和 Order
是两个用于传递数据的结构体,它们在 init
函数中被注册。
UserService
和 OrderService
是两个用于远程服务代理的结构体,MyService
也是,但 MyService
中还包含了 UserService
和 OrderService
这两个服务。
那么现在用 NewMyService
方法创建的 myService
对象上就包含了所有这些服务,嵌套的结构体名称会自动作为名称空间附加在调用的方法名之前,例如:
myService.User.Delete(1)
当调用上面这个方法时,其实调用的是 User_Delete
这个远程方法。
上面这个嵌套结构体只有一层,实际上你还可以定义更复杂的多层嵌套,这里就不再举例说明了。
前面介绍 Invoke
和 Go
方法时,它们的最后一个输入参数都是一个 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
}
我们下面就对该结构体中的几个字段进行详细说明。
该字段表示调用是否为引用参数传递,默认值为 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"
。
该字段表示调用所返回的结果是否为简单数据。简单数据是指: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
模式,服务器端的设置只影响服务的返回结果,客户端的设置只影响客户端发送的参数,所以这两个设置是独立的,客户端和服务器端不必同时设置,但可以同时设置。
该字段表示调用是否为幂等性调用,幂等性调用表示不论该调用被重复几次,对服务器的影响都是相同的。幂等性调用在因网络原因调用失败时,会自动重试。如果 Failswitch 字段同时被设置为 true
,并且客户端设置了多个服务地址,在重试时还会自动切换地址。
该字段默认值为 false
,即表示当前调用是非幂等性调用,出错不进行重试。当使用代理对象调用时,它使用 idempotent
标记来定义。
该字段表示当前调用在因网络原因失败时是否自动切换服务地址。
该字段默认值为 false
,即表示当出错时不自动切换服务地址。当使用代理对象调用时,它使用 failswitch
标记来定义。
该字段表示幂等性调用在因网络原因调用失败后的重试次数。只有 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()
}
这些标记都是各自独立的,可以同时使用多个标记。
该字段表示当前调用是否不等待返回值。当该字段设置为 true
时,请求发送之后,并不等待服务器返回结果,而是直接向下继续执行。
该字段默认值为 false
,即表示调用需要等待返回值。当使用代理对象调用时,它使用 oneway
标记来定义。
该字段表示当前调用的超时时间,如果调用超过该时间后仍然没有返回,则会以超时错误返回。
该字段默认值为 0
,即表示使用客户端默认的超时时间。当使用代理对象调用时,它使用 timeout
标记来定义。
需要注意,因为是 time.Duration
类型的数据,所以它的单位是纳秒。
该字段为 true
时,返回结果中的 map
类型的默认映射类型改为 map[string]interface{}
,否则默认映射类型为 map[interface{}]interface{}
,当你确认你的 map
数据是 JSON
兼容的,即 map
的 key
一定是 string
类型或者可以转换为 string
类型的数据时,可以将该字段设置为 true
。
该字段默认为 false
。当使用代理对象调用时,它使用 jsoncompat
标记来定义。
该字段用来设置返回值的类型,当该值为 nil
时,表示没有返回值。因为 go 支持多结果返回,因此可以设置多个返回值类型。最后的错误类型不包含在这个字段中。
该字段默认为 nil
。当使用代理对象调用时,它直接通过代理对象结构体中函数字段的返回值类型或回调函数的参数类型来定义。
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 过滤器时,会举例介绍它的用法。
默认值为 false
。该设置表示所有客户端是否共享 Cookie 管理器。默认值表示共享。当该变量被设置为 true
时,每个客户端会拥有一个独立的 Cookie 管理器。
HTTPClient
和 FastHTTPClient
结构体指针上定义了如下字段和方法。
用来设置自定义的 HTTP 头信息。
该方法返回最大并发请求数。
该方法用于设置最大并发请求数,当并发请求超过该设置之后,后面的请求将会排队等待。该设置默认值为 10
。
返回客户端是否支持 HTTP 持久连接。默认值为 true
。
设置客户端是否支持 HTTP 持久连接。
返回客户端是否使用压缩传输。默认值为 false
。
设置客户端是否使用压缩传输。
HTTPClient
结构体指针上还定义了如下特殊字段:
该字段是一个匿名字段,类型为 http.Transport
,你可以通过对它上面的字段进行设置来改变 HTTP 客户端的一些特殊设置。
该字段其实是 Transport
结构体上的一个字段,它表示最多允许的空闲持久连接数。这里单独拿出来说是因为它的默认值也是 10
,目的是跟 MaxConcurrentRequests
的值一样,在这种情况下,所有的请求都可以通过持久连接发送,可以有效的提高客户端跟服务器的通讯效率。所以当你希望改变 MaxConcurrentRequests
设置时,最好保持跟 MaxIdleConnsPerHost
设置的一致性。
FastHTTPClient
结构体指针上还定义了如下特殊字段:
该字段是一个匿名字段,类型为 fasthttp.Client
,你可以通过对它上面的字段进行设置来改变 FastHTTP 客户端的一些特殊设置。
TCPClient
和 UnixClient
这两个结构体包含 SocketClient
这个匿名字段,因此,SocketClient
上的字段和方法都会被继承。
设置与连接相关的操作系统接收缓冲区的大小。当为 0 时,表示不进行设置,使用系统默认值。
设置与连接相关的操作系统发送缓冲区的大小。当为 0 时,表示不进行设置,使用系统默认值。
用于存取 TLS 配置,跟使用 TLSClientConfig
和 SetTLSClientConfig
方法存取相同。
返回连接池空闲超时时长。
设置连接池空闲超时时长,默认值为 0,表示永不超时,直到客户端关闭。
返回连接池最大连接数。
设置连接池最大连接数。默认为 runtime.NumCPU()
个。
TCPClient
除了包含上面的继承自 SocketClient
的设置以外,还有以下几个可以设置的字段:
用于设置当连接中仍有数据等待发送或接受时的 Close
方法的行为。
如果其值小于 0
(默认),Close
方法立即返回,操作系统停止后台数据发送;如果其值等于 0
,Close
立刻返回,操作系统丢弃任何未发送或未接收的数据;如果其值大于 0
,Close
方法阻塞最多 Linger
秒,等待数据发送或者接收,在一些操作系统中,在超时后,任何未发送的数据会被丢弃。
用于设定操作系统是否应该延迟数据包传递,以便发送更少的数据包(Nagle's算法)。默认为 true
,即数据应该在 Write
方法后立刻发送。
用于设置操作系统是否应该在该连接中发送 keepalive 信息。默认为 true
。
用于设置 keepalive 的周期,超出会断开。默认值为 0
,表示使用系统默认设置。
WebSocketClient
结构体指针上只有如下两个可设置的字段:
用来设置自定义的 HTTP 头信息。
该方法返回最大并发请求数。
该方法用于设置最大并发请求数,当并发请求超过该设置之后,后面的请求将会排队等待。该设置默认值为 10
。