1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 12:32:21 +00:00

ch4-03: 完成

This commit is contained in:
chai2010 2018-07-02 10:37:17 +08:00
parent ca1ed4da49
commit d1c8357fdd
10 changed files with 358 additions and 23 deletions

View File

@ -34,7 +34,7 @@
* [第四章 RPC和Protobuf](ch4-rpc/readme.md)
* [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
* [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
* [4.3. 玩转RPC(TODO)](ch4-rpc/ch4-03-netrpc-hack.md)
* [4.3. 玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
* [4.4. GRPC入门(TODO)](ch4-rpc/ch4-04-grpc.md)
* [4.5. GRPC进阶(TODO)](ch4-rpc/ch4-05-grpc-hack.md)
* [4.6. Protobuf扩展语法和插件(TODO)](ch4-rpc/ch4-06-pb-option.md)

View File

@ -1,24 +1,151 @@
# 4.3. 玩转RPC
TODO
在不同的场景中RPC有着不同的需求因此开源的社区就诞生了各种RPC框架。本节我们将尝试Go内置RPC框架在一些比较特殊场景的用法。
<!--
## 反向RPC
认证/反向/gls能够拿到req吗基于req的gls
通常的RPC是基于C/S结构RPC的的服务端对应网络的服务器RPC的客户端也对应网络客户端。但是对于一些特殊场景比如在公司内网提供一个RPC服务但是在外网无法链接到内网的服务器。这种时候我们可以参考类似反向代理的技术首先从内网主动链接到外网的TCP服务器然后基于TCP链接向外网提供RPC服务。
--
以下是启动反向RPC服务的代码
pb 和 json 是类似的,
```go
func main() {
rpc.Register(new(HelloService))
唯一的差异的 protoc 工具
for {
conn, _ := net.Dial("tcp", "localhost:1234")
if conn == nil {
time.Sleep(time.Second)
continue
}
有了代码生成,一切就发生变化了
rpc.ServeConn(conn)
conn.Close()
}
}
```
pb 的重要性不再时底层的编码,二是 api 的通用语言!!!
反向RPC的内网服务将不再主导提供TCP监听服务而是首先主动链接到对方的TCP服务器。然后基于每个建立的TCP链接向对方提供RPC服务。
但是 pb 个 rest 是无法一一等价的
而RPC客户端则需要在一个公共的地址提供一个TCP服务用于接受RPC服务器的链接请求
生成简单的代码,增加接口约束
```go
func main() {
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
甚至最终回归到 gob 或 json 编码
-->
clientChan := make(chan *rpc.Client)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
clientChan <- rpc.NewClient(conn)
}
}()
doClientWork(clientChan)
}
```
当接每个链接建立后基于网络链接构造RPC客户端对象并发送到clientChan管道。
客户端执行RPC调用的操作在doClientWork函数完成
```go
func doClientWork(clientChan <-chan *rpc.Client) {
client := <-clientChan
defer client.Close()
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
```
首先从管道去除一个RPC客户端对象并且通过defer语句指定在函数退出钱关闭客户端。然后是执行正常的RPC调用。
## 上下文信息
首先是上下文信息基于上下文我们可以针对不同客户端提供定制化的RPC服务。我们可以通过为每个信道提供独立的RPC服务来实现对上下文特性的支持。
首先改造HelloService里面增加了对应链接的conn成员
```go
type HelloService struct {
conn net.Conn
}
```
然后为每个信道启动独立的RPC服务
```go
func main() {
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 func() {
defer conn.Close()
p := rpc.NewServer()
p.Register(&HelloService{conn: conn})
p.ServeConn(conn)
} ()
}
}
```
Hello方法中就可以根据conn成员识别不同信道的RPC调用
```go
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request + ", from" + p.conn.RemoteAddr().String()
return nil
}
```
基于上下文信息我们可以方便地为RPC服务增加简单的登陆状态的验证
```go
type HelloService struct {
conn net.Conn
isLogin bool
}
func (p *HelloService) Login(request string, reply *string) error {
if request != "user:password" {
return fmt.Errorf("auth failed")
}
log.Println("login ok")
p.isLogin = true
return nil
}
func (p *HelloService) Hello(request string, reply *string) error {
if !p.isLogin {
return fmt.Errorf("please login")
}
*reply = "hello:" + request + ", from" + p.conn.RemoteAddr().String()
return nil
}
```
这样可以要求在客户端链接RPC服务时首先要执行登陆操作登陆成果后才能正常执行其他的服务。

View File

@ -0,0 +1,37 @@
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Call("HelloService.Login", "abc", &reply)
if err != nil {
log.Println(err)
} else {
log.Println("login ok")
}
err = client.Call("HelloService.Login", "user:password", &reply)
if err != nil {
log.Println(err)
} else {
log.Println("login ok")
}
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}

View File

@ -0,0 +1,53 @@
package main
import (
"fmt"
"log"
"net"
"net/rpc"
)
type HelloService struct {
conn net.Conn
isLogin bool
}
func ServeHelloService(conn net.Conn) {
p := rpc.NewServer()
p.Register(&HelloService{conn: conn})
p.ServeConn(conn)
}
func (p *HelloService) Login(request string, reply *string) error {
if request != "user:password" {
log.Println("login failed")
return fmt.Errorf("auth failed")
}
log.Println("login ok")
p.isLogin = true
return nil
}
func (p *HelloService) Hello(request string, reply *string) error {
if !p.isLogin {
return fmt.Errorf("please login")
}
*reply = "hello:" + request + ", from" + p.conn.RemoteAddr().String()
return nil
}
func main() {
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 ServeHelloService(conn)
}
}

View File

@ -0,0 +1,22 @@
package main
import (
"fmt"
"log"
"net/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)
}

View File

@ -1,5 +0,0 @@
package main
func main() {
// todo
}

View File

@ -0,0 +1,38 @@
package main
import (
"log"
"net"
"net/rpc"
)
type HelloService struct {
conn net.Conn
}
func ServeHelloService(conn net.Conn) {
p := rpc.NewServer()
p.Register(&HelloService{conn: conn})
p.ServeConn(conn)
}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request + ", from" + p.conn.RemoteAddr().String()
return nil
}
func main() {
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 ServeHelloService(conn)
}
}

View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"log"
"net"
"net/rpc"
)
func main() {
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}
clientChan := make(chan *rpc.Client)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
clientChan <- rpc.NewClient(conn)
}
}()
client := <-clientChan
defer client.Close()
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}

View File

@ -1,5 +0,0 @@
package main
func main() {
// todo
}

View File

@ -0,0 +1,29 @@
package main
import (
"net"
"net/rpc"
"time"
)
type HelloService struct{}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello:" + request
return nil
}
func main() {
rpc.Register(new(HelloService))
for {
conn, _ := net.Dial("tcp", "localhost:1234")
if conn == nil {
time.Sleep(time.Second)
continue
}
rpc.ServeConn(conn)
conn.Close()
}
}