
Go 语言的 RPC 包的路径为`net/rpc`，也就是放在了 net 包目录下面。因此我们可以猜测该 RPC 包是建立在 net 包基础之上的。我们基于 http 实现了一个打印例子。下面我们尝试基于 rpc 实现一个类似的例子。

<!--more-->

### 服务端

```go
packagemain

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

type HelloServicestruct{
}

func(s*HelloService)Hello(request string, reply*string)error{
	//返回值是通过修改 request 的值
	*reply = "Hello" + request
	return nil
}

func main(){

	//1.实例化一个 server
	listener,err:=net.Listen("tcp",":1234")
	if err!=nil{
		log.Fatalln(err)
	}
	//2.注册处理逻辑
	err=rpc.RegisterName("HelloService",&HelloService{})
	if err!=nil{
		return
	}
	//3.启动服务
	conn,err:=listener.Accept()//当一个新的链接进来的时候，
	rpc.ServeConn(conn)

//一连串的代码大部分都是 net 的包好像和 rpc 没有什么关系
//1.go 语言的 rpc 序列化的反序列协议是 Gob
}
```

其中 Hello 方法必须满足 Go 语言的 RPC 规则：

1. 方法只能有两个可序列化的参数，其中第二个参数是指针类型，并且返回一个 error 类型，同时必须是公开的方法。

然后就可以将`HelloService`类型的对象注册为一个 RPC 服务：(TCP RPC 服务)。

其中`rpc.Register()`函数调用会将对象类型中所有满足 RPC 规则的对象方法注册为 RPC 函数，所有注册的方法会放在`“HelloService”`服务空间之下。

然后我们建立一个唯一的 TCP 链接，并且通过`rpc.ServeConn()`函数在该 TCP 链接上为对方提供 RPC 服务。

### 客户端

```go
package main

import(
	"log"
	"net/rpc"
)

func main(){

	//1.建立连接
	client,err:=rpc.Dial("tcp","localhost:1234")
	if err!=nil{
		log.Fatalln(err)
	}
	var reply=new(string)//在内存中分配变量，并把指针赋值给变量
	//varreplystring//此时的 string 已经有地址了，而且还有零值使用&reply 传递参数
	//这里调用的服务的方法是服务名。方法名
	err=client.Call("HelloService.Hello","jimyag",reply)
	if err!=nil{
		log.Fatalln(err)
	}
	log.Printf(*reply)
}
```

首先是通过 rpc.Dial 拨号 RPC 服务，然后通过 client.Call 调用具体的 RPC 方法。在调用 client.Call 时，第一个参数是用点号链接的 RPC 服务名字和方法名字，第二和第三个参数分别我们定义 RPC 方法的两个参数。

### 改进 rpc 的调用过程

#### 改进 1

前面的 rpc 调用虽然简单，但是和普通的 http 的调用差异不大，这次我们解决下面的问题

1. serviceName 统一和名称冲突的问题
   - 多个 server 的包中 serviceName 同名的问题
   - server 端和 client 端如何统一 serviceName

上述实现中服务名称是在客户端和服务端写死的，如果有一方改动，那么双方都要改动

目录结构

```shell
.
├── client
│   └── main.go
├── handle
│   └── handle.go
└── server
    └── main.go
```

新建`handler/handler.go`文件内容如下： 

```go
package handle

const (
    // 解决命名冲突
	HelloServiceName = "handle/HelloService"
)
```

为什么要新建这个文件？

是为了解耦。

##### 服务端

```go
package main

import (
	"net"
	"net/rpc"

	"test-rpc/handle" // 自己的包名
)

type HelloService struct {
}

func (S *HelloService) Hello(request string, reply *string) error {
	*reply = "hello " + request
	return nil
}
func main() {
	_ = rpc.RegisterName(handle.HelloServiceName, &HelloService{})
	lisener, err := net.Listen("tcp", ":1234")
	if err != nil {
		panic("监听端口失败")
	}
	conn, err := lisener.Accept()
	if err != nil {
		panic("建立连接失败")
	}

	rpc.ServeConn(conn)
}
```

##### 客户端

```go
package main

import (
   "fmt"
   "net/rpc"

   "test-rpc/handle" // 自己的包名
)

func main() {
   client, err := rpc.Dial("tcp", "localhost:1234")
   if err != nil {
      panic("连接到服务器失败")
   }

   var reply string
    // 只要加上调用的方法名即可
   err = client.Call(handle.HelloServiceName+".Hello", "jimyag", &reply)
   if err != nil {
      panic("服务调用失败")
   }

   fmt.Println(reply)
}
```

#### 改进 2

以上，我们解耦了服务名。但是，对于服务端和客户端来说，他们只要管调用相关的方法就行，不要管相关的实现。

那么我们可以封装一个 client 和 server 端的代理，让 client 和 server 端就像调用本地方法一样。

继续屏蔽`HelloserviceName`和`Hello`函数名称

##### 目录结构

```shell
.
├── client
│   └── main.go
├── client_proxy
│   └── client_proxy.go
├── handle
│   └── handle.go
├── server
│   └── main.go
└── server_porxy
    └── server_proxy.go
```

##### `handle.go`

```go
package handle

type HelloService struct{}

func (s *HelloService) Hello(request string, reply *string) error {
   *reply = "hello " + request
   return nil
}
```

##### `server_proxy.go`

在提供的服务中通过 interface 进行封装，在这里我们关心的调用的函数，而不是某个结构体。所以封装的时候传入的参数为 interface

```go
package server_porxy

import "net/rpc"

const HelloServiceName = "handler/HelloService"

type HelloServiceInterface interface {
   Hello(request string, reply *string) error
}

// 封装服务的注册
func RegisterHelloService(srv HelloServiceInterface) error {
   return rpc.RegisterName(HelloServiceName, srv)
}
```

##### `server.go`

服务端调用的时候，就可以直接注册一个 hello 的服务。

```go
package main

import (
   "net"
   "net/rpc"

   "test-rpc3/handle" // 项目包名
   "test-rpc3/server_porxy" // 项目包名
)

func main() {
   helloHandler := &handle.HelloService{}
   _ = server_porxy.RegisterHelloService(helloHandler)
   listener, err := net.Listen("tcp", ":1234")
   if err != nil {
      panic("监听端口失败")
   }
   conn, err := listener.Accept()
   if err != nil {
      panic("建立链接失败")
   }
   rpc.ServeConn(conn)
}
```

##### `client_proxy.go`

客户端调用远程的方法时候，要像调用本地方法一样进行调用。封装一个 hello 的 client，只需要调用 client 里面的方法就行。

```go
package client_proxy

import "net/rpc"

const HelloServiceName = "handler/HelloService"

// 将 hello client 暴露出去
type HelloServiceClient struct {
   *rpc.Client
}

func NewClient(address string) HelloServiceClient {
   conn, err := rpc.Dial("tcp", address)
   if err != nil {
      panic("连接服务器错误")
   }
   return HelloServiceClient{conn}
}

func (c *HelloServiceClient) Hello(request string, reply *string) error {
   err := c.Call(HelloServiceName+".Hello", request, reply)
   if err != nil {
      return err
   }
   return nil
}
```

##### `client.go`

```go
package main

import (
   "fmt"

   "test-rpc3/client_proxy"
)

func main() {
   client := client_proxy.NewClient("localhost:1234")
   var reply string
   err := client.Hello("jimyag", &reply)
   if err != nil {
      panic("调用失败")
   }
   fmt.Println(reply)
}
```

