2024年Go最新深入解析grpc源码2-客户端与服务器通信流程_grpc,2024年最新薪资翻倍

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  }()

但是我们并没有看到[wg.wait]( )(),或者e.fired 什么时候为1的?答案在Stop 以及GracefulStop()身上


* 在默认的for{}死循环中监听连接,服务器就被阻塞住了,在e.fired变为1(调用stop 系列函数),或者出错就会返回,返回就会调用defer,此时就会执行到<-s.done.Done()这个逻辑,再次阻塞住。  
 如果创建grpc Server没有调用stop 系统函数停止服务,那么就会直接退出了,不会进入到<-s.done.Done这个逻辑
* 停止函数都会做一件事情,调用s.quit.Fire,将**e.fired原子交换变成1**,然后调用s.serveWG.Wait()等待所有协程处理完任务然后停止。实现优雅退出
* 停止函数会处理一些停止逻辑,清理不用的数据,在最后关闭s.done.Done,这样阻塞在<-s.done.Done()上面的主协程就会真正退出了。


#### **1.3.2stop(停止)**


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1701




func (s *Server) Stop() {
s.quit.Fire() //

defer func() { //最后执行等待所有协程退出,并关闭done channel
s.serveWG.Wait()
s.done.Fire()
}()

s.channelzRemoveOnce.Do(func() {
if channelz.IsOn() {
channelz.RemoveEntry(s.channelzID)
}
})

s.mu.Lock() //加锁将servers 的监听连接和连接列表获取处理
listeners := s.lis
s.lis = nil
conns := s.conns
s.conns = nil
//当GracefulStop 和 Stop并发执行的时候,打断GracefulStop阻塞在条件变量上面,让GracefulStop继续执行完
s.cv.Broadcast()
s.mu.Unlock()

for lis := range listeners { //关闭所有监听连接
lis.Close()
}
for _, cs := range conns { //关闭所有连接
for st := range cs {
st.Close()
}
}
if s.opts.numServerWorkers > 0 { //停止协程池
s.stopServerWorkers()
}

s.mu.Lock()
if s.events != nil { //清除事件
s.events.Finish()
s.events = nil
}
s.mu.Unlock()
}


* 调用这个马上会关闭所有连接和listeners(简单粗暴)
* rpc客户端将会收到连接错误


#### **1.3.3GracefulStop(优雅的停止)**


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1747



func (s *Server) GracefulStop() {
s.quit.Fire() //先标识服务器关闭
defer s.done.Fire()//最后执行关闭done channel

s.channelzRemoveOnce.Do(func() {
if channelz.IsOn() {
channelz.RemoveEntry(s.channelzID)
}
})
s.mu.Lock()
if s.conns == nil { //连接为空直接返回
s.mu.Unlock()
return
}

for lis := range s.lis { //关闭所有监听套接,这样就不会有新连接到来了
lis.Close()
}
s.lis = nil
if !s.drain { //如果连接没有排干,那么就循环处理连接的数据
for _, conns := range s.conns {
for st := range conns {
st.Drain()
}
}
s.drain = true //如果所有连接都处理完了,那么将已排干设置为true
}

//等待所有线程退出,这里只能确定,不会有新连接到来
s.mu.Unlock()
s.serveWG.Wait()
s.mu.Lock()

for len(s.conns) != 0 {
s.cv.Wait() //等待条件满足被唤醒
}
s.conns = nil
if s.events != nil {
s.events.Finish()
s.events = nil
}
s.mu.Unlock()
}


* 调用s.serveWG.Wait()等待所有协程处理完退出
* 因为s.conns不等于0,说明连接没有处理完,需要s.cv.Wait() 等待所有连接处理完,条件变量被唤醒的地方有两处  
 1、[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1721,这种只发生在并发调用GracefulStop和Stop 的时候,当调用stop 时是直接暴力关闭所有连接,并将s.conns置为nil,此时唤醒的话,GracefulStop就能继续执行了,不会一直阻塞等待。  
 2、[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1035,每条连接在建立连接后都会defer调用removeConn,每次调用完removeConn都会调用Broadcast唤醒,当条件不满足时,又会继续等待,直到s.conns为nil,所有连接停止。
* 优雅的停止grpc服务,先停止创建新连接,然后等待所有连接处理完
* st.Drain就是发送goaway 消息代表离开



func (t *http2Server) Drain() {
t.mu.Lock()
defer t.mu.Unlock()
if t.drainChan != nil {
return
}
t.drainChan = make(chan struct{})
t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte{}, headsUp: true})
}


#### **1.3.4处理连接**


回到上面为每个连接创建协程,来看看在协程处理了什么事情



go func() {
s.handleRawConn(lis.Addr().String(), rawConn)
s.serveWG.Done()
}()


**handleRawConn**



func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) {
if s.quit.HasFired() { //如果服务器被退出了,那么关闭连接,返回
rawConn.Close()
return
}
//设置连接超时
rawConn.SetDeadline(time.Now().Add(s.opts.connectionTimeout))

//完成http2 握手
st := s.newHTTP2Transport(rawConn)
//去掉连接超时
rawConn.SetDeadline(time.Time{})
if st == nil { //如果握手失败,st 为nil,直接返回
return
}

if !s.addConn(lisAddr, st) {
return
}
go func() {
s.serveStreams(st)
s.removeConn(lisAddr, st)
}()
}


* 创建一个http2 服务器的实例st,在这个创建过程会完成http2 的握手,http2 的握手过程我会在下一章http2 协议深入讲解
* addConn将地址和st 添加到s.conns里面去



func (s *Server) addConn(addr string, st transport.ServerTransport) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.conns == nil { //已经为空,说明服务器已经关闭了,直接返回
st.Close()
return false
}
if s.drain {
// Transport added after we drained our existing conns: drain it
// immediately.
st.Drain()
}

if s.conns[addr] == nil {
// Create a map entry if this is the first connection on this listener.
s.conns[addr] = make(map[transport.ServerTransport]bool)
}
s.conns[addr][st] = true
return true
}


* 再开个协程去处理stream,处理完stream移除所有连接,这里可能疑问,为什么要再开个协程?  
 官方说新开个协程去专门处理没有io 的数据连接处理,但是会提高效率吗?**各位同鞋欢迎在评论区写出你的答案。**
* 如果这里开协程处理就不会阻塞在业务中,就会返回调用s.serveWG.Done(),所以调用s.serveWG.wait()不阻塞了,并不代表所有协程都已经完数据了


#### **1.3.5建立流,并处理流**


**serveStreams**


上面完成http2 的握手后,开了个协处理流的数据,参数是上面创建的http2 服务器实例。



func (s *Server) serveStreams(st transport.ServerTransport) {
defer st.Close() //在最后关闭这个http2 服务器实例
var wg sync.WaitGroup

var roundRobinCounter uint32 //
st.HandleStreams(func(stream *transport.Stream) {
wg.Add(1)
if s.opts.numServerWorkers > 0 {
data := &serverWorkerData{st: st, wg: &wg, stream: stream}
select {
case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data: //将数据打包放在workers协程池的管道
default: //如果没有可用的workers协程,
//走默认逻辑,自己创建一个goroutine 去处理数据
go func() {
s.handleStream(st, stream, s.traceInfo(st, stream))
wg.Done()
}()
}
} else {
go func() {
defer wg.Done()
s.handleStream(st, stream, s.traceInfo(st, stream))
}()
}
}, func(ctx context.Context, method string) context.Context {//trace信息
if !EnableTracing {
return ctx
}
tr := trace.New(“grpc.Recv.”+methodFamily(method), method)
return trace.NewContext(ctx, tr)
})
wg.Wait() //等待协程处理完成
}


* 可用分两部分来看这个serveStreams函数,一个是走协程池,一个是自己创建goroutine处理流数据
* 调用http2 服务器实例的HandleStreams,这个函数的作用是解码http2 的data 帧,将数据封装成stream流,然后用传进去的回调函数处理stream。


#### **1.3.6grpc 协程池是怎么做的?**


先回到我们创建服务器的时候,有个创建协程池的函数。当设置协程work数量后,将会初始化协程池。


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +583



if s.opts.numServerWorkers > 0 {
s.initServerWorkers()
}


**initServerWorkers**



func (s *Server) initServerWorkers() {
s.serverWorkerChannels = make([]chan *serverWorkerData, s.opts.numServerWorkers)
for i := uint32(0); i < s.opts.numServerWorkers; i++ {
s.serverWorkerChannels[i] = make(chan *serverWorkerData)
go s.serverWorker(s.serverWorkerChannels[i])
}
}


* 作用是提前创建协程处理将要到来的连接,避免在调用runtime.morestack的时候花费更多时间
* 原理是提前创建numServerWorkers数量的WorkerChannels,然后在循环里面,为每个channel 开辟空间,注意没有缓冲,将会阻塞。  
 最后每个索引再起一个协程将channel 传进去。


serverWorkerData结构如下,包含流,http2 服务器实例,和一个sync.WaitGroup



type serverWorkerData struct {
st transport.ServerTransport
wg *sync.WaitGroup
stream *transport.Stream
}


下面是serverWorker实现,总共起numServerWorkers个协程调用该函数,并且每个协程有它们自己的channel。




//serverWorkerResetThreshold定义栈重置的频率,每n 个请求,就会重置一次,会在它的地方重新生成goroutine。worker可以重置它自己的栈,这样做是防止太大的栈永远在内存里面(因为在协程执行中,可能处理业务分配了很大的栈)。 2^16是每个goroutine的栈至少存活的秒数在典型的工作载荷中,假设QPS 几百请求每秒。
const serverWorkerResetThreshold = 1 << 16
func (s *Server) serverWorker(ch chan *serverWorkerData) {

threshold := serverWorkerResetThreshold + grpcrand.Intn(serverWorkerResetThreshold)
for completed := 0; completed < threshold; completed++ {
data, ok := <-ch
if !ok { //这里代表该channel 已经关闭,不明白可以看我写的chan 源码解析
return
}
s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream))
data.wg.Done()
}
go s.serverWorker(ch)
}


* 在一个for 循环里面,调用<-ch阻塞获取数据,因为ch 是没有缓冲的,所以是阻塞获取,当ch 被关闭的时候,就退出这个协程return。如果有数据到来,就调用 s.handleStream处理数据,最后调用data.wg.Done标识这个任务处理完,因为在管道消息发送方还调用wait等着。
* 因为是个循环,每处理一个数据completed就会+1,直到超过threshold,才会退出循环。  
 threshold为一个默认值(1 << 16)+一个随机值0~(1 << 16)之间,这样做的目的是为了避免,所有协程在同一时间退出循环,造成都重新生成goroutine,增加服务器压力,所以选择一个随机时间。
* 在completed超过 threshold,也就是处理数据超过这个值后就会通过go 重新开辟协程(重置栈)继续下轮循环。
* serverWorker可以处理不同的请求,并且避免了栈分配的昂贵代价。参考:[https://github.com/golang/go/issues/18138]( )


回到上面的问题,如何将数据分配给每个协程,这里用到了roundRobin算法,对worker数量取模,模的下标就是数组的索引,然后根据索引从数组里面取协程对应的channel,将数据发送到channel。然后等待的协程就会处理这个请求了。



if s.opts.numServerWorkers > 0 {
data := &serverWorkerData{st: st, wg: &wg, stream: stream}
select {
case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data: //将数据打包放假workers协程池的管道
default: //如果没有可用的workers协程,
//走默认逻辑,自己创建一个goroutine 去处理数据
go func() {
s.handleStream(st, stream, s.traceInfo(st, stream))
wg.Done()
}()
}
}


关闭这些worker也很简单,关闭这些channel就行了,然后for 循环就会因为关闭channel return。还记得调用stop 暴力关闭的时候,也会调用这个函数去停掉这些worker协程。



func (s *Server) stopServerWorkers() {
for i := uint32(0); i < s.opts.numServerWorkers; i++ {
close(s.serverWorkerChannels[i])
}
}


**如何设置worker数量**


在创建grpc server 的时候设置



s := grpc.NewServer(grpc.MaxSendMsgSize(1000),
grpc.NumStreamWorkers(10))


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +505



func NumStreamWorkers(numServerWorkers uint32) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.numServerWorkers = numServerWorkers
})
}


* 实验建议和cup 数量保持一致表现最好,但是需要更多测试,不传这个默认值为0,代表不开启


#### **1.3.7处理steam数据流程**


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1586


终于来到怎么[解码]( )服务方法和数据的流程了。肝的有点累,不过http2 内容更多,各位同学如果有收获的话,不要忘记点赞。



func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {
sm := stream.Method() //获取http2 解码出来的方法
if sm != “” && sm[0] == ‘/’ {
sm = sm[1:]
}
pos := strings.LastIndex(sm, “/”)
if pos == -1 {
if trInfo != nil {
trInfo.tr.LazyLog(&fmtStringer{“Malformed method name %q”, []interface{}{sm}}, true)
trInfo.tr.SetError()
}
errDesc := fmt.Sprintf(“malformed method name: %q”, stream.Method())
if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
if trInfo != nil {
trInfo.tr.LazyLog(&fmtStringer{“%v”, []interface{}{err}}, true)
trInfo.tr.SetError()
}
channelz.Warningf(logger, s.channelzID, “grpc: Server.handleStream failed to write status: %v”, err)
}
if trInfo != nil {
trInfo.tr.Finish()
}
return
}
service := sm[:pos] //获取服务名
method := sm[pos+1:]//获取方法名
//这里根据服务名获取我们注册的时候添加进去的服务器
srv, knownService := s.services[service]
if knownService { //如果获取到了,再从methods获取方法,如果方法获取到了,调用一元rpc 方法处理函数processUnaryRPC
if md, ok := srv.methods[method]; ok {
s.processUnaryRPC(t, stream, srv, md, trInfo)
return
}
//如果上面没有获取到,再从streams方法里面找一找,找到了,处理,然后return
if sd, ok := srv.streams[method]; ok {
s.processStreamingRPC(t, stream, srv, sd, trInfo)
return
}
}
//如果是未知服务,去看看设没设置未知服务的处理方法,如果有的话,调用并return
if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil {
s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo)
return
}
//来到这里说明已经出错了,未知的服务,也没设置未知服务的处理方式
var errDesc string
//生成错误描述信息
if !knownService {
errDesc = fmt.Sprintf(“unknown service %v”, service)
} else {
errDesc = fmt.Sprintf(“unknown method %v for service %v”, method, service)
}
//如果trace信息不为空,打印
if trInfo != nil {
trInfo.tr.LazyPrintf(“%s”, errDesc)
trInfo.tr.SetError()
}
//调用http2 服务实例报错
if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
if trInfo != nil {
trInfo.tr.LazyLog(&fmtStringer{“%v”, []interface{}{err}}, true)
trInfo.tr.SetError()
}
channelz.Warningf(logger, s.channelzID, “grpc: Server.handleStream failed to write status: %v”, err)
}
//最后完成trInfo
if trInfo != nil {
trInfo.tr.Finish()
}
}


* stream.Method()获取客户端请求的服务和方法,这里先剧透下,在http2 怎么解析这个method的。  
[https://github.com/grpc/grpc-go/blob/master/internal/transport/http2\_server.go]( ) +403  
 case ":path":  
 s.method = hf.Value  
 在前面的例子中, hf.Value为/helloworld.Greeter/SayHello,前面代表服务,后面代表服务的方法
* 根据pos取"/"位置,如果 pos == -1代表这个是错误的,处理一系列错误
* 根据解码出来的方法和服务寻找handler,如果找到了直接处理并返回,如果没有找到则找unknow service的处理方法
* 最后没找到就直接响应http2 的错误


#### **1.3.8处理processUnaryRPC**


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1125


方法比较多,错误处理及监控指标采集代码直接简化成文字,具体内容读者感兴趣可自行查看源码



func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
//…状态

//comp and cp由于压缩,decomp and dc由于解压,如果comp and decomp同时设置,他们是一样的效果
//但是,
var comp, decomp encoding.Compressor
var cp Compressor
var dc Decompressor


//如果dc被设置匹配流的压缩,就使用它,要不然的话为decomp去找一个匹配的注册的压缩器

if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc {
dc = s.opts.dc
} else if rc != “” && rc != encoding.Identity {
decomp = encoding.GetCompressor(rc)
if decomp == nil {
st := status.Newf(codes.Unimplemented, “grpc: Decompressor is not installed for grpc-encoding %q”, rc)
t.WriteStatus(stream, st)
return st.Err()
}
}

// If cp is set, use it. Otherwise, attempt to compress the response using
// the incoming message compression method.
//
// NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686.
if s.opts.cp != nil {
cp = s.opts.cp
stream.SetSendCompress(cp.Type())
} else if rc := stream.RecvCompress(); rc != “” && rc != encoding.Identity {
// Legacy compressor not specified; attempt to respond with same encoding.
comp = encoding.GetCompressor(rc)
if comp != nil {
stream.SetSendCompress(rc)
}
}

var payInfo *payloadInfo
if sh != nil || binlog != nil {
payInfo = &payloadInfo{}
}
d, err := recvAndDecompress(&parser{r: stream}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp)

df := func(v interface{}) error {
    if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil {
        return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err)
    }
    if sh != nil {
        sh.HandleRPC(stream.Context(), &stats.InPayload{
            RecvTime:   time.Now(),
            Payload:    v,
            WireLength: payInfo.wireLength + headerLen,
            Data:       d,
            Length:     len(d),
        })
    }
    .......
    return nil
}
ctx := NewContextWithServerTransportStream(stream.Context(), stream)
reply, appErr := md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)
//处理错误
............

if err := s.sendResponse(t, stream, reply, cp, opts, comp); err != nil {
if err == io.EOF {
// The entire stream is done (for unary RPC only).
return err

    }

// …错误处理

}
// …日志处理
// …错误处理
}


* 获取消息的压缩和解压器,然后将数据正确解压出来
* md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)调用handler处理解码的数据,以前面的一元rpc handler 为例,下面是protoc 为我们生成的handler,根据服务和方法找到这个函数以后,就开始调用这个函数。其中dec为上面的df 函数,做了一件事情,就是获取数据的解码器,在这个例子中是protobuf,然后将数据解码进请求,跟json.Unmarshal(d, v)是一样的作用,这里只不过解码protobuf 数据而已。



func _HelloService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
//创建一个请求的实例
in := new(HelloRequest)
//将数据解码进请求
if err := dec(in); err != nil {
return nil, err
}
//如果传入进来的拦截器不为空,那么直接调用我们自定义的SayHello方法将请求传进去处理,并将响应结果返回
if interceptor == nil {
return srv.(HelloServiceServer).SayHello(ctx, in)
}
//如果拦截器不为空,那么我们就封装下,将请求和服务信息还有handler传进拦截器
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: “/nihao.HelloService/SayHello”,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}


#### **sendResponse**


sendResponse将handler处理完的消息响应给客户端



func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error {
//将传进来的msg数据进行编码
data, err := encode(s.getCodec(stream.ContentSubtype()), msg)
if err != nil {
channelz.Error(logger, s.channelzID, "grpc: server failed to encode response: ", err)
return err
}
//将数据进行压缩
compData, err := compress(data, cp, comp)
if err != nil {
channelz.Error(logger, s.channelzID, "grpc: server failed to compress response: ", err)
return err
}
//获取消息头和载荷
hdr, payload := msgHeader(data, compData)
// TODO(dfawley): should we be checking len(data) instead?
if len(payload) > s.opts.maxSendMessageSize {
return status.Errorf(codes.ResourceExhausted, “grpc: trying to send message larger than max (%d vs. %d)”, len(payload), s.opts.maxSendMessageSize)
}
//将消息通过t 写回到客户端
err = t.Write(stream, hdr, payload, opts)
if err == nil && s.opts.statsHandler != nil {
s.opts.statsHandler.HandleRPC(stream.Context(), outPayload(false, msg, data, payload, time.Now()))
}
return err
}


* msgHeader对数据进行再编码msgHeader,逻辑为:  
 1、如果有数据有压缩了,将头部数据第一个字节设为压缩模式,剩下4个字节用来代表压缩数据长度。  
 2、如果没有压缩,头部第一个数据为没有压缩,剩下4个字节代表未压缩数据的长度,因为网络数据都是大端模式,所以要使用binary.BigEndian.PutUint32将数据放入hdr



const (
payloadLen = 1
sizeLen = 4
headerLen = payloadLen + sizeLen
)

// msgHeader returns a 5-byte header for the message being transmitted and the
// payload, which is compData if non-nil or data otherwise.
func msgHeader(data, compData []byte) (hdr []byte, payload []byte) {
hdr = make([]byte, headerLen)
if compData != nil {
hdr[0] = byte(compressionMade)
data = compData
} else {
hdr[0] = byte(compressionNone)
}

// Write length of payload into buf
binary.BigEndian.PutUint32(hdr[payloadLen:], uint32(len(data)))
return hdr, data
}


* 最后通过t.write 将数据打包成data 帧返回给客户端,grpc 并没有直接写入数据到连接,而是用协程异步loop 循环从一个缓存结构里面获取帧然后写回到客户端,感兴趣可以读下面的源码先预习  
 创建http2 的服务器启动协程loop write: [https://github.com/grpc/grpc-go/blob/master/internal/transport/http2\_server.go]( ) +324 (+326调用loop write)  
 loop writer 处理缓存帧的地方:[https://github.com/grpc/grpc-go/blob/master/internal/transport/controlbuf.go]( ) +520


#### **1.3.9处理processStreamingRPC**


[https://github.com/grpc/grpc-go/blob/master/server.go]( ) +1415


方法比较多,错误处理及监控指标采集代码直接简化成文字,具体内容读者感兴趣可自行查看源码



func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error){
//…状态
//创建stream对象
ctx := NewContextWithServerTransportStream(stream.Context(), stream)
ss := &serverStream{
ctx: ctx,
t: t,
s: stream,
p: &parser{r: stream},
codec: s.getCodec(stream.ContentSubtype()),
maxReceiveMessageSize: s.opts.maxReceiveMessageSize,
maxSendMessageSize: s.opts.maxSendMessageSize,
trInfo: trInfo,
statsHandler: sh,
}
//状态采集和二进制日志采集

// If dc is set and matches the stream’s compression, use it. Otherwise, try
// to find a matching registered compressor for decomp.
if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc {
ss.dc = s.opts.dc
} else if rc != “” && rc != encoding.Identity {
ss.decomp = encoding.GetCompressor(rc)
if ss.decomp == nil {
st := status.Newf(codes.Unimplemented, “grpc: Decompressor is not installed for grpc-encoding %q”, rc)
t.WriteStatus(ss.s, st)
return st.Err()
}
}

// If cp is set, use it.  Otherwise, attempt to compress the response using
// the incoming message compression method.
//
// NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686.
if s.opts.cp != nil {
    ss.cp = s.opts.cp
    stream.SetSendCompress(s.opts.cp.Type())
} else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity {
    // Legacy compressor not specified; attempt to respond with same encoding.
    ss.comp = encoding.GetCompressor(rc)
    if ss.comp != nil {
        stream.SetSendCompress(rc)
    }
}


ss.ctx = newContextWithRPCInfo(ss.ctx, false, ss.codec, ss.cp, ss.comp)
if info != nil {
server = info.serviceImpl
}
if s.opts.streamInt == nil {
appErr = sd.Handler(server, ss)
} else {
info := &StreamServerInfo{
FullMethod: stream.Method(),
IsClientStream: sd.ClientStreams,
IsServerStream: sd.ServerStreams,
}
appErr = s.opts.streamInt(server, ss, info, sd.Handler)
}
//处理错误

}


* 和一元rpc 很类似,主要区别是调用流的handler和流的拦截器处理方法,这里没有直接解压数据,而是封装成stream对象传入方法,在前面我们已经介绍过了,流的处理是循环调用传进来的stream.Recv 方法或者调用steam.Send 方法。在调用的时候才会使用这些压缩,解压,编码解码器。


### **2客户端**



package main

import (
“context”
“flag”
“log”
“time”

“google.golang.org/grpc”
pb “google.golang.org/grpc/examples/helloworld/helloworld”
)

const (
defaultName = “world”
)

var (
addr = flag.String(“addr”, “localhost:50051”, “the address to connect to”)
name = flag.String(“name”, defaultName, “Name to greet”)
)

func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithInsecure())
if err != nil {
log.Fatalf(“did not connect: %v”, err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)

// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf(“could not greet: %v”, err)
}
log.Printf(“Greeting: %s”, r.GetMessage())
}


### **2.1 创建grpc 客户端**


* [https://github.com/grpc/grpc-go/blob/master/clientconn.go]( ) +104
* [https://github.com/grpc/grpc-go/blob/master/clientconn.go]( ) +135



conn, err := grpc.Dial(*addr, grpc.WithInsecure())

//创建客户端连接
func Dial(target string, opts …DialOption) (*ClientConn, error) {
return DialContext(context.Background(), target, opts…)
}


func DialContext(ctx context.Context, target string, opts …DialOption) (conn *ClientConn, err error) {
cc := &ClientConn{
target: target,
csMgr: &connectivityStateManager{},
conns: make(map[*addrConn]struct{}),
dopts: defaultDialOptions(),
blockingpicker: newPickerWrapper(),
czData: new(channelzData),
firstResolveEvent: grpcsync.NewEvent(),
}
//设置重试节流
cc.retryThrottler.Store((*retryThrottler)(nil))
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
cc.ctx, cc.cancel = context.WithCancel(context.Background())

for _, opt := range opts {
opt.apply(&cc.dopts)
}
//客户端一元rpc 拦截器
chainUnaryClientInterceptors(cc)
//stream rpc 拦截器
chainStreamClientInterceptors(cc)

defer func() {
if err != nil {
cc.Close()
}
}()


//检查证书配置相关
if cc.dopts.copts.TransportCredentials == nil && cc.dopts.copts.CredsBundle == nil {
return nil, errNoTransportSecurity
}
if cc.dopts.copts.TransportCredentials != nil && cc.dopts.copts.CredsBundle != nil {
return nil, errTransportCredsAndBundle
}
if cc.dopts.copts.CredsBundle != nil && cc.dopts.copts.CredsBundle.TransportCredentials() == nil {
return nil, errNoTransportCredsInBundle
}
transportCreds := cc.dopts.copts.TransportCredentials
if transportCreds == nil {
transportCreds = cc.dopts.copts.CredsBundle.TransportCredentials()
}
if transportCreds.Info().SecurityProtocol == “insecure” {
for _, cd := range cc.dopts.copts.PerRPCCredentials {
if cd.RequireTransportSecurity() {
return nil, errTransportCredentialsMissing
}
}
}

//设置默认服务器配置
if cc.dopts.defaultServiceConfigRawJSON != nil {
scpr := parseServiceConfig(*cc.dopts.defaultServiceConfigRawJSON)
if scpr.Err != nil {
return nil, fmt.Errorf(“%s: %v”, invalidDefaultServiceConfigErrPrefix, scpr.Err)
}
cc.dopts.defaultServiceConfig, _ = scpr.Config.(*ServiceConfig)
}
cc.mkp = cc.dopts.copts.KeepaliveParams

//设置用户代理标识
if cc.dopts.copts.UserAgent != "" {
    cc.dopts.copts.UserAgent += " " + grpcUA
} else {
    cc.dopts.copts.UserAgent = grpcUA
}
//设置创建超时
if cc.dopts.timeout > 0 {
    var cancel context.CancelFunc
    ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout)
    defer cancel()
}
//在最后监听服务Done信号,然后退出
defer func() {
    select {
    case <-ctx.Done():
        switch {
        case ctx.Err() == err:
            conn = nil
        case err == nil || !cc.dopts.returnLastError:
            conn, err = nil, ctx.Err()
        default:
            conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)
        }
    default:
    }
}()


scSet := false
if cc.dopts.scChan != nil {
// Try to get an initial service config.
select {
case sc, ok := <-cc.dopts.scChan:
if ok {
cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
scSet = true
}
default:
}
}
if cc.dopts.bs == nil {
cc.dopts.bs = backoff.DefaultExponential
}

// Determine the resolver to use.
resolverBuilder, err := cc.parseTargetAndFindResolver()
if err != nil {
return nil, err
}
cc.authority, err = determineAuthority(cc.parsedTarget.Endpoint, cc.target, cc.dopts)
if err != nil {
return nil, err
}
channelz.Infof(logger, cc.channelzID, “Channel authority set to %q”, cc.authority)

//阻塞等待配置更新完成
if cc.dopts.scChan != nil && !scSet {
    // Blocking wait for the initial service config.
    select {
    case sc, ok := <-cc.dopts.scChan:
        if ok {
            cc.sc = &sc
            cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
        }
    case <-ctx.Done():
        return nil, ctx.Err()
    }
}
if cc.dopts.scChan != nil {
    go cc.scWatcher()
}


var credsClone credentials.TransportCredentials
if creds := cc.dopts.copts.TransportCredentials; creds != nil {
credsClone = creds.Clone()
}
//负载均衡选项配置
cc.balancerBuildOpts = balancer.BuildOptions{
DialCreds: credsClone,
CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer,
Authority: cc.authority,
CustomUserAgent: cc.dopts.copts.UserAgent,
ChannelzParentID: cc.channelzID,
Target: cc.parsedTarget,
}

// Build the resolver.
rWrapper, err := newCCResolverWrapper(cc, resolverBuilder)
if err != nil {
return nil, fmt.Errorf(“failed to build resolver: %v”, err)
}
cc.mu.Lock()
cc.resolverWrapper = rWrapper
cc.mu.Unlock()

//阻塞等待连接配置好
if cc.dopts.block {
for {
cc.Connect()
s := cc.GetState()
if s == connectivity.Ready {
break
} else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {
if err = cc.connectionError(); err != nil {
terr, ok := err.(interface {
Temporary() bool
})
if ok && !terr.Temporary() {
return nil, err
}
}
}
if !cc.WaitForStateChange(ctx, s) {
// ctx got timeout or canceled.
if err = cc.connectionError(); err != nil && cc.dopts.returnLastError {
return nil, err
}
return nil, ctx.Err()
}
}
}

return cc, nil
}


### **2.2 请求**


来看grpc 客户端实例


* NewGreeterClient通过传进来的grpc 连接初始化greeterClient对象,下面代码由protoc 自动生成。
* 调用SayHello的时候,调用grpc 客户端的invoke 方法,请求为路径为/helloworld.Greeter/SayHello,数据为传进来的数据



// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts …grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
cc grpc.ClientConnInterface
}

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

 }
            return nil, ctx.Err()
        }
    }
}


return cc, nil
}


### **2.2 请求**


来看grpc 客户端实例


* NewGreeterClient通过传进来的grpc 连接初始化greeterClient对象,下面代码由protoc 自动生成。
* 调用SayHello的时候,调用grpc 客户端的invoke 方法,请求为路径为/helloworld.Greeter/SayHello,数据为传进来的数据



// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts …grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
cc grpc.ClientConnInterface
}

[外链图片转存中…(img-VdmwVoGP-1715650175681)]
[外链图片转存中…(img-xVYukD0l-1715650175681)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值