1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 20:52:22 +00:00

ch4-01: 完善

This commit is contained in:
chai2010 2018-06-28 07:56:15 +08:00
parent 94a8058bf6
commit 0c05e7c7a6

View File

@ -66,7 +66,119 @@ func main() {
## 更安全的PRC接口
在涉及RPC的应用中作为开发人员一般至少有三种角色首选是服务端实现RPC方法的开发人员其次是客户端调用RPC方法的人员最后也是最重要的是制定服务端和客户端RPC接口规范的设计人员。在前面的例子中我们为了简化将以上几种角色的工作全部放到了一起虽然看似实现简单但是不利于后期的维护和工作的切割。
如果要重构HelloService服务第一步需要明确服务的名字和接口
```go
const HelloServiceName = "path/to/pkg.HelloService"
type HelloServiceInterface = interface {
Hello(request string, reply *string) error
}
func RegisterHelloService(svc HelloServiceInterface) error {
rpc.RegisterName(HelloServiceName, svc)
}
```
我们将RPC服务的接口规范分为三个部分首选是服务的名字然后是服务要实现的详细方法列表最后是注册该类型服务的函数。为了避免名字冲突我们在RPC服务的名字中增加了包路径前缀这个是RPC服务抽象的包路径并非完全等价Go语言的包路径。RegisterHelloService注册服务时编译器会要求传入的对象满足HelloServiceInterface接口。
在定义了RPC服务接口规范之后客户端就可以根据规范编写RPC调用的代码了
```go
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方法依然比较繁琐同时参数的类型依然无法得到编译器提供的安全保障。
为了简化客户端用户调用RPC函数我们在可以在接口规范部分增加对客户端的简单包装
```go
type HelloServiceClient struct {
*rpc.Client
}
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
func DialHelloService(network, address string) (*HelloServiceClient, error) {
c, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: c}
}
func (p *HelloServiceClient) Hello(request string, reply *string) error {
return client.Call(HelloServiceName+".Hello", request, reply)
}
```
我们在接口规范中针对客户端新增加了HelloServiceClient类型改类型也必须满足HelloServiceInterface接口这样客户端用户就可以直接通过接口对应的方法调用RPC函数。同时提供了一个DialHelloService方法直接拨号HelloService服务。
基于新的客户端接口,我们可以简化客户端用户的代码:
```go
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)
}
}
```
现在客户端用户不用再担心RPC方法名字或参数类型不匹配等低级错误的发生。
最后是基于RPC接口规范编写真实的代码
```go
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)
}
}
```
在新的RPC服务端实现中我们用RegisterHelloService函数来注册函数这样不仅可以避免服务名称的工作同时也保证了传入的服务对象满足了RPC接口定义的定义。最后我们支持多个TCP链接然后为每个TCP链接建立RPC服务。
## RPC不应该绑定到某个语言
TODO
@ -74,6 +186,8 @@ TODO
不过上面的例子依然比较简陋首选是RPC服务只能接受一次请求其次客户端要通过字符串标识符来区分调用RPC服务不够友好。
同时改进,支持多个链接
netrpc简单例子
通过接口给服务端和客户端增加类型约束,缺点是繁琐
http模式但是只能gob无法跨语言