标签:令行 turn 标准库 要求 拨号 shel 识别 定义 另一个
type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request
return nil
}
必须满足Go语言的RPC规则
:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法
。rpc.Register
函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在“HelloService”服务空间之下。然后我们建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
rpc.Dial
拨号RPC服务,然后通过client.Call
调用具体的RPC方法。在调用client.Call
时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数
。服务端实现RPC方法
的开发人员,其次是客户端调用RPC方法``的人员,最后也是最重要的是
制定服务端和客户端RPC接口规范```的设计人员。在前面的例子中我们为了简化将以上几种角色的工作全部放到了一起,虽然看似实现简单,但是不利于后期的维护和工作的切割。const HelloServiceName = "path/to/pkg.HelloService"
type HelloServiceInterface = interface {
Hello(request string, reply *string) error
}
func RegisterHelloService(svc HelloServiceInterface) error {
return rpc.RegisterName(HelloServiceName, svc)
}
三个部分
:首先是服务的名字
,然后是服务要实现的详细方法列表
,最后是注册该类型服务的函数
。为了避免名字冲突,我们在RPC服务的名字中增加了包路径前缀(这个是RPC服务抽象的包路径,并非完全等价Go语言的包路径)。RegisterHelloService注册服务时,编译器会要求传入的对象满足HelloServiceInterface接口。func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Call(HelloServiceName+".Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
}
client.Call
的第一个参数用HelloServiceName+".Hello"代替了"HelloService.Hello"
。然而通过client.Call函数调用RPC方法依然比较繁琐,同时参数的类型依然无法得到编译器提供的安全保障。// 定义客户端结构体
type HelloServiceClient struct {
*rpc.Client
}
// 要求客户端接口实现server接口
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
// 封装方法返回HelloServiceClient客户端对象
func DialHelloService(network, address string) (*HelloServiceClient, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: c}, nil
}
// 实现RPC客户端方法封装
func (p *HelloServiceClient) Hello(request string, reply *string) error {
return p.Client.Call(HelloServiceName+".Hello", request, reply)
}
HelloServiceClient
类型,该类型也必须满足HelloServiceInterface
接口,这样客户端用户就可以直接通过接口对应的方法调用RPC函数。同时提供了一个DialHelloService
方法,直接拨号HelloService
服务。func main() {
client, err := DialHelloService("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Hello("hello", &reply)
if err != nil {
log.Fatal(err)
}
}
type HelloService struct {}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request
return nil
}
func main() {
RegisterHelloService(new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeConn(conn)
}
}
net/rpc/jsonrpc
扩展实现一个跨语言的RPC。func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
func main() {
conn, err := net.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("net.Dial:", err)
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
net.Dial
函数建立TCP链接,然后基于该链接建立针对客户端的json编解码器。nc命令
$ nc -l 1234在同样的端口启动一个TCP服务。然后再次执行一次RPC调用将会发现nc输出了以下的信息:>> {"method":"HelloService.Hello","params":["hello"],"id":0}
method
部分对应要调用的rpc服务和方法组合成的名字,params
部分的第一个元素为参数,id是由调用端维护的一个唯一的调用编号。clientRequest
,服务端是serverRequest
。clientRequest和serverRequest结构体的内容基本是一致的:type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
Id uint64 `json:"id"`
}
type serverRequest struct {
Method string `json:"method"`
Params *json.RawMessage `json:"params"`
Id *json.RawMessage `json:"id"`
}
$ echo -e ‘{"method":"HelloService.Hello","params":["hello"],"id":1}‘ | nc localhost 1234
>> {"id":1,"result":"hello:hello","error":null}
type clientResponse struct {
Id uint64 `json:"id"`
Result *json.RawMessage `json:"result"`
Error interface{} `json:"error"`
}
type serverResponse struct {
Id *json.RawMessage `json:"id"`
Result interface{} `json:"result"`
Error interface{} `json:"error"`
}
func main() {
rpc.RegisterName("HelloService", new(HelloService))
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
ReadCloser: r.Body,
Writer: w,
}
rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
http.ListenAndServe(":1234", nil)
}
http.ResponseWriter
和http.Request
类型的参数构造一个io.ReadWriteCloser
类型的conn通道。然后基于conn构建针对服务端的json编码解码器。最后通过rpc.ServeRequest
函数为每次请求处理一次RPC方法调用。$ curl localhost:1234/jsonrpc -X POST --data ‘{"method":"HelloService.Hello","params":["hello"],"id":0}‘
>> {"id":0,"result":"hello:hello","error":null}
标签:令行 turn 标准库 要求 拨号 shel 识别 定义 另一个
原文地址:https://www.cnblogs.com/binHome/p/13053759.html