本节是继上一章节Hello world的进一步深入挖掘;
一、grpc 服务接口类型
在godoc的网站上对grpc的端口类型进行了简单的介绍,总共有下面4种类型[1]:
gRPC lets you define four kinds of service method: Unary RPCs where the client sends a single request to the server and gets a single response back, just like a normal function call. rpc SayHello(HelloRequest) returns (HelloResponse){ } Server streaming RPCs where the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){ } Client streaming RPCs where the client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them and return its response. rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) { } Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){ } We’ll look at the different types of RPC in more detail in the RPC life cycle section below.
上面是从官网摘抄过来的,简单的来讲客户端和服务发送数据有两种形式:Unary和Streaming。Unary (一元)一次只发送一个包; Streaming(流)一次可以发送多个包。两种方式组合一下就形成了4种类型:
服务端 | 客户端 | |
1 | Unary | Unary |
2 | Streaming | Streaming |
3 | Unary | Streaming |
4 | Streaming | Unary |
开发者需要根据业务需求来考虑使用不同的服务接口类型,最近作者在做一个项目采用的就是第一种接口类型,然后就引发了本片文章后面的一系列问题.... 。
在上一篇Hello world文章里面的示例就是第一种类型接口,它最终声明了一个需要开发者去实习具体业务逻辑的接口:
// Server API for Greeter service type GreeterServer interface { // Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) }
在Hello world的示例中只对HelloRequest参数进行了使用,而没有使用conext参数,那这个参数有什么用?首先作者介绍一下在golang中context类型的作用。
二、Golang context
2.1、简介
作者所讲的context的包名称是: "golang.org/x/net/context" ,希望读者不要引用错误了。
在godoc中对context的介绍如下:
Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes. As of Go 1.7 this package is available in the standard library under the name context. https://golang.org/pkg/context.
Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between must propagate the Context, optionally replacing it with a modified copy created using WithDeadline, WithTimeout, WithCancel, or WithValue.
Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation:
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:
func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.
Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.
The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
See http://blog.golang.org/context for example code for a server that uses Contexts.
鉴于作者英文水平有限,在这里不进行对照翻译,以免误导读者。它的第一句已经介绍了它的作用了:一个贯穿API的边界和进程之间的context 类型,可以携带deadlines、cancel signals和其他信息。就如同它的中文翻译一样:上下文。在一个应用服务中会并行运行很多的goroutines或进程, 它们彼此之间或者是从属关系、竞争关系、互斥关系,不同的goroutines和进程进行交互的时候需要进行状态的切换和数据的同步,而这就是context包要支持的功能。
2.2、解析
context的接口定义如下:
// A Context carries a deadline, a cancelation signal, and other values across // API boundaries.Context‘s methods may be called by multiple goroutines simultaneously. type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) // Done returns a channel that‘s closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. Done() <-chan struct{} // Err returns a non-nil error value after Done is closed. Err returns // Canceled if the context was canceled or DeadlineExceeded if the // context‘s deadline passed. No other values for Err are defined. // After Done is closed, successive calls to Err return the same value. Err() error // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. Value(key interface{}) interface{} }
每一个接口都有详细的注释,这里就不重复了。 在context的源码中有以下几个结构体实现了ContextInterface:
empty context
// An emptyCtx is never canceled, has no values, and has no deadline. It is not // struct{}, since vars of this type must have distinct addresses. type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil } func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
这是一个空的ctx类型,每一个返回值都为空,它什么都功能都不具备,主要的作用是作为所有的context类型的起始点,context.Background()函数返回的就是这中类型的Context:
var ( background = new(emptyCtx) todo = new(emptyCtx) ) // Background returns a non-nil, empty Context. It is never canceled, has no // values, and has no deadline. It is typically used by the main function, // initialization, and tests, and as the top-level Context for incoming // requests. func Background() Context { return background }
empty context的作用作者下面会阐述。
cancle context
// A cancelCtx can be canceled. When canceled, it also cancels any children // that implement canceler. type cancelCtx struct { Context mu sync.Mutex // protects following fields done chan struct{} // created lazily, closed by first cancel call children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call } func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d } func (c *cancelCtx) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err } func (c *cancelCtx) String() string { return fmt.Sprintf("%v.WithCancel", c.Context) } // cancel closes c.done, cancels each of c‘s children, and, if // removeFromParent is true, removes c from its parent‘s children. func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err if c.done == nil { c.done = closedchan } else { close(c.done) } for child := range c.children { // NOTE: acquiring the child‘s lock while holding parent‘s lock. child.cancel(false, err) } c.children = nil c.mu.Unlock() if removeFromParent { removeChild(c.Context, c) } }
timer context
value context
作者接下来会说明context的使用方式,首先来看示例一:
p { margin-bottom: 0.25cm; direction: ltr; color: rgb(0, 0, 10); line-height: 120%; text-align: left }
p.western { font-family: "Liberation Serif", serif; font-size: 12pt }
p.cjk { font-family: "Noto Sans CJK SC Regular"; font-size: 12pt }
p.ctl { font-family: "Noto Sans CJK SC Regular"; font-size: 12pt }
a:link { }
参考网址
[1] https://grpc.io/docs/guides/concepts.html#service-definition
原文地址:https://www.cnblogs.com/cnblogs-wangzhipeng/p/9112751.html