4

如果我们想在服务端为客户端提供一个打印一个输入字段的服务,借助net/rpc,我们可以这样实现:

初步实现

服务端

package main

import (
    "log"
    "net"
    "net/rpc"
)

//构造一个类 名字是HelloService
type HelloService struct{}

//给这个类绑定一个Hello方法 实现打印的功能,这个方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法
func (item *HelloService) Hello(req string, reply *string) error {
    *reply = "server-hello  " + req
    return nil
}

func main() {
    //将HelloService的对象注册为一个RPC服务
    rpc.RegisterName("HelloService", new(HelloService))
    listener, err := net.Listen("tcp", ":1234")

    if err != nil {
        log.Fatal("ListenTCP error:", err)
    }
    //建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。
    conn, err := listener.Accept()
    if err != nil {
        log.Fatal("Accept error:", err)
    }
    rpc.ServeConn(conn)
}

客户端

package main

import (
    "fmt"
    "log"
    "net/rpc"
)

func main() {
    //通过rpc.Dial拨号RPC服务
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing", err)
    }

    var reply string
    //通过client.Call调用具体的RPC方法
    //在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,
    //第二和第三个参数分别我们定义RPC方法的两个参数。
    err = client.Call("HelloService.Hello", " client-Hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}

运行结果:

server-hello   client-Hello

重构与优化

在涉及RPC的应用中,作为开发人员一般至少有三种角色:首先是服务端实现RPC方法的开发人员,其次是客户端调用RPC方法的人员,最后也是最重要的是制定服务端和客户端RPC接口规范的设计人员。在前面的例子中我们为了简化将以上几种角色的工作全部放到了一起,虽然看似实现简单,但是不利于后期的维护和工作的切割。我们将RPC服务的接口规范分为三个部分:首先是服务的名字,然后是服务要实现的详细方法列表,最后是注册该类型服务的函数。为了避免名字冲突,我们在RPC服务的名字中增加了包路径前缀(这个是RPC服务抽象的包路径,并非完全等价Go语言的包路径)。RegisterHelloService注册服务时,编译器会要求传入的对象满足HelloServiceInterface接口。

服务端

package main

import (
    "log"
    "net"
    "net/rpc"
)

type HelloService struct{}

func (item *HelloService) Hello(req string, reply *string) error {
    *reply = "server-hello  " + req
    return nil
}

//明确服务的名字
const HelloServiceName = "path/to/pkg.HelloService"

//明确服务的接口 说明服务要实现的详细方法列表
type HelloServiceInterface = interface {
    Hello(request string, reply *string) error
}

//注册该类型服务的函数 注册服务时,编译器会要求传入的对象满足HelloServiceInterface接口
func RegisterHelloService(svc HelloServiceInterface) error {
    return rpc.RegisterName(HelloServiceName, svc)
}

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)
    }
}

客户端

package main

import (
    "fmt"
    "log"
    "net/rpc"
)

//明确服务的名字
const HelloServiceName = "path/to/pkg.HelloService"

func main() {
    //通过rpc.Dial拨号RPC服务
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("dialing", err)
    }

    var reply string
    //通过client.Call调用具体的RPC方法
    //在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,
    //第二和第三个参数分别我们定义RPC方法的两个参数。
    err = client.Call(HelloServiceName+".Hello", " client-Hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}

运行结果

server-hello   client-Hello

参考: https://chai2010.cn/advanced-...

LiberHome
409 声望1.1k 粉丝

有问题 欢迎发邮件 📩 liberhome@163.com