go-grpc服务端调用

完整的示例代码可以参考前面的文章go-grpc客户端调用.

grpc.NewServer()方法:

1.拷贝默认配置:

defaultServerOptions:

globalServerOptions:

这个全局配置是一个切口ServerOption的切片.上面的截图中可以看到.定义如下.

然后这个属性是通过init方法进行加载前进行初始化.

这个方法并没有真正的赋值.调用的地方如下.

2.实例化结构体:

Go 复制代码
s := &Server{
		lis:          make(map[net.Listener]bool),       // 保存所有监听的listener,Serve()时添加
		opts:         opts,                              // 最终生效全部配置
		statsHandler: istats.NewCombinedHandler(opts.statsHandlers...), // 组合多个stats监控handler
		conns:        make(map[string]map[transport.ServerTransport]bool), // 维护客户端连接:key是client地址,value是该地址下所有transport
		services:     make(map[string]*serviceInfo),     // 注册的service,key为服务全名 /helloworld.Greeter
		quit:         grpcsync.NewEvent(),               // quit事件:触发服务关闭(GracefulStop)
		done:         grpcsync.NewEvent(),               // done事件:服务完全关闭完成
		channelz:     channelz.RegisterServer(""),       // channelz链路追踪,注册server节点
	}

3.组装一元拦截器链 + 流式拦截器链:

chainUnaryServerInterceptors和chainStreamServerInterceptors方法可以参考客户端调用的.

4.开启trace事件日志,记录NewServer调用点(文件+行号):

5.numServerWorkers>0初始化协程池:

helloworld_grpc.pb.go的RegisterGreeterServer:

1.调用参数grpc.ServiceDesc:

复制代码
可以从上面信息得到这个注册服务的信息.特别是这个服务的handler.

2.RegisterService方法:

3.register方法:

grpc.Server的Server方法:

1.serveWG:WaitGroup,跟踪所有Serve监听协程 + 连接处理协程:

2.包装 listenSocket,注册 channelz 诊断节点:

3.退避重试:

Go 复制代码
var tempDelay time.Duration // accept临时错误退避时间
	for {
		rawConn, err := lis.Accept() // 阻塞等待客户端TCP连接
		if err != nil {
			// 处理临时错误:例如文件描述符耗尽,进行指数退避重试
			if ne, ok := err.(interface{ Temporary() bool }); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
					tempDelay = min(tempDelay, 1*time.Second) // 最大退避1s
				}
				s.mu.Lock()
				s.printf("Accept error: %v; retrying in %v", err, tempDelay)
				s.mu.Unlock()
				timer := time.NewTimer(tempDelay)
				select {
				case <-timer.C:
				case <-s.quit.Done():
					// 服务关闭,直接退出循环
					timer.Stop()
					return nil
				}
				continue
			}

			// 非临时错误:listener被关闭
			s.mu.Lock()
			s.printf("done serving; Accept = %v", err)
			s.mu.Unlock()

			if s.quit.HasFired() {
				return nil
			}
			return err
		}

		tempDelay = 0 // accept成功,重置退避

4.处理客户端连接:

5.newHTTP2Transport:

Go 复制代码
// 从Server.opts拷贝全部配置,构造transport层配置结构体
	config := &transport.ServerConfig{
		MaxStreams:            s.opts.maxConcurrentStreams,        // 最大并发流,HTTP2 max_concurrent_streams
		ConnectionTimeout:     s.opts.connectionTimeout,             // 连接建立超时
		Credentials:           s.opts.creds,                         // TLS/mTLS凭证
		InTapHandle:           s.opts.inTapHandle,
		StatsHandler:          s.statsHandler,                       // metrics/统计埋点
		KeepaliveParams:       s.opts.keepaliveParams,               // server keepalive 参数
		KeepalivePolicy:       s.opts.keepalivePolicy,               // keepalive 策略
		InitialWindowSize:     s.opts.initialWindowSize,             // http2 stream 初始流控窗口
		InitialConnWindowSize: s.opts.initialConnWindowSize,         // http2 连接级流控窗口
		WriteBufferSize:       s.opts.writeBufferSize,
		ReadBufferSize:        s.opts.readBufferSize,
		SharedWriteBuffer:     s.opts.sharedWriteBuffer,
		ChannelzParent:        s.channelz,                           // channelz父节点,链路诊断
		MaxHeaderListSize:     s.opts.maxHeaderListSize,             // http2 header最大大小
		HeaderTableSize:       s.opts.headerTableSize,               // HPACK头部压缩表大小
		BufferPool:            s.opts.bufferPool,                    // 内存buffer池
		StaticWindowSize:      s.opts.staticWindowSize,
	}

6.创建ServerTransport,内部完成 TLS握手 + HTTP2协商:

tlsCreds实现类(credentials/tls.go:168):

  1. tls.Server(rawConn, c.config):把裸 TCP conn 包装成 Go 标准库 crypto/tls 的服务端 TLS conn。注意此时还没有任何网络交互,只是包装。

  2. conn.Handshake():阻塞执行真正的 TLS 握手------证书交换、密钥协商(ECDHE)、(如果配了 mTLS)验证客户端证书。失败就关闭连接返回 err。

  3. conn.ConnectionState():拿到握手结果(协商出的协议、密码套件、对端证书链等)。

  4. ALPN 检查:协商出的应用层协议为空说明客户端没做 ALPN。HTTP/2 over TLS 必须通过 ALPN 协商出 h2,所以 1.67+ 默认直接拒绝连接(GRPC_ENFORCE_ALPN_ENABLED 环境变量控制,见 internal/envconfig)。

  5. 构造 TLSInfo:这就是返回的第二个值 authInfo 的真身,里面装着 tls.ConnectionState(含对端证书),安全级别标为 PrivacyAndIntegrity。

  6. 提取 SPIFFE ID:如果对端证书带 SPIFFE URI,解析出来放进去(服务网格场景用)。

7.WrapSyscallConn(rawConn,conn)(internal/credentials/syscallconn.go:49):返回一个包装 conn------普通读写都走 TLS conn,但 SyscallConn() 委托给原来的 rawConn,这样 gRPC 内部(如 channelz 取 TCP 信息)还能直接访问底层 socket。

初始化framer:HTTP2帧编解码器:

组装服务端初始SETTINGS帧(发给客户端):

Go 复制代码
isettings := []http2.Setting{{
		ID:  http2.SettingMaxFrameSize,
		Val: http2MaxFrameLen, // 最大帧大小,默认16KB
	}}
	// MaxConcurrentStreams:告诉客户端本连接最大并发RPC流
	if config.MaxStreams != math.MaxUint32 {
		isettings = append(isettings, http2.Setting{
			ID:  http2.SettingMaxConcurrentStreams,
			Val: config.MaxStreams,
		})
	}
	// Stream级流控窗口 InitialWindowSize
	iwz := int32(initialWindowSize)
	if config.InitialWindowSize >= defaultWindowSize {
		iwz = config.InitialWindowSize
	}
	// Connection级连接流控窗口 InitialConnWindowSize
	icwz := int32(initialWindowSize)
	if config.InitialConnWindowSize >= defaultWindowSize {
		icwz = config.InitialConnWindowSize
	}
	if iwz != defaultWindowSize {
		isettings = append(isettings, http2.Setting{
			ID:  http2.SettingInitialWindowSize,
			Val: uint32(iwz)})
	}
	if config.MaxHeaderListSize != nil {
		isettings = append(isettings, http2.Setting{
			ID:  http2.SettingMaxHeaderListSize,
			Val: *config.MaxHeaderListSize,
		})
	}
	if config.HeaderTableSize != nil {
		isettings = append(isettings, http2.Setting{
			ID:  http2.SettingHeaderTableSize,
			Val: *config.HeaderTableSize,
		})
	}
	// 把SETTINGS帧写入缓冲区
	if err := framer.fr.WriteSettings(isettings...); err != nil {
		return nil, connectionErrorf(false, err, "transport: %v", err)
	}

连接级WindowUpdate帧:调整连接流控窗口::

Keepalive参数填充,补默认值 + jitter抖动:

ServerParameters(kp)------服务端自己怎么管连接

两个值得单独说的点:

  • MaxConnectionAge += getJitter(...) 那行(http2_server.go:234):给退休年龄随机加 ±10% 抖动。想象 LB 后面 1000 条连接同一秒建的还是同一秒到期------不加抖动就会同一秒集体退休、集体重建,瞬间流量尖峰(连接风暴)。大家各退各的,错开。

  • SetTCPUserTimeout(http2_server.go:245):把应用层的 Timeout 同步设到内核 TCP 层(Linux 的 TCP_USER_TIMEOUT:数据发出去多久收不到 ACK,内核直接报错)。否则可能内核还在傻等重传,应用层早已认为连接死了。Time == infinity 说明你压根禁用了 keepalive,也就不用设。

EnforcementPolicy(kep)------管制客户端

这是服务端的反骚扰条款。心跳本质是"让连接不被中间的 NAT/防火墙掐死 + 探活",几分钟一次足够了;但如果客户端把 Time 配成 1 秒疯狂 ping,服务端就被白嫖 CPU------所以收 ping 时检查(http2_server.go:916-924):没有活跃流还乱 ping、或者间隔小于 MinTime,直接断。

一句话总结

kp 管自己:"多久没动静我探一次、多久没回算死、闲连接多久回收、连接多久退休";kep 管别人:"你心跳太勤我就拉黑"。默认配置下服务端很佛系:不回收闲连接、不让连接退休,只有 2 小时一次的心跳和 5 分钟的反骚扰底线。

创建 http2Server 实例(ServerTransport实现):

Go 复制代码
done := make(chan struct{})
	peer := peer.Peer{
		Addr:      conn.RemoteAddr(),
		LocalAddr: conn.LocalAddr(),
		AuthInfo:  authInfo, // TLS握手后的认证信息,传递到RPC的peer.FromContext
	}
	t := &http2Server{
		done:              done,
		conn:              conn,
		peer:              peer,
		framer:            framer,
		readerDone:        make(chan struct{}),
		loopyWriterDone:   make(chan struct{}),
		maxStreams:        config.MaxStreams,
		inTapHandle:       config.InTapHandle,
		fc:                &trInFlow{limit: uint32(icwz)}, // 连接级流控
		state:             reachable,
		activeStreams:     make(map[uint32]*ServerStream), // 当前活跃stream,key=streamID
		stats:             config.StatsHandler,
		kp:                kp,
		idle:              time.Now(),
		kep:               kep,
		initialWindowSize: iwz,
		bufferPool:        config.BufferPool,
	}
	t.setResetPingStrikes = func() {
		atomic.StoreUint32(&t.resetPingStrikes, 1)
	}
	// channelz 链路观测注册socket节点
	var czSecurity credentials.ChannelzSecurityValue
	if au, ok := authInfo.(credentials.ChannelzSecurityInfo); ok {
		czSecurity = au.GetSecurityValue()
	}
	t.channelz = channelz.RegisterSocket(
		&channelz.Socket{
			SocketType:       channelz.SocketTypeNormal,
			Parent:           config.ChannelzParent,
			SocketMetrics:    channelz.SocketMetrics{},
			EphemeralMetrics: t.socketMetrics,
			LocalAddr:        t.peer.LocalAddr,
			RemoteAddr:       t.peer.Addr,
			SocketOptions:    channelz.GetSocketOption(t.conn),
			Security:         czSecurity,
		},
	)
	t.logger = prefixLoggerForServerTransport(t)
	t.controlBuf = newControlBuffer(t.done)
	// BDP带宽延迟乘积估算,动态自动调流控窗口
	if !config.StaticWindowSize {
		t.bdpEst = &bdpEstimator{
			bdp:               initialWindowSize,
			updateFlowControl: t.updateFlowControl,
		}
	}
	// 连接全局唯一ID
	t.connectionID = atomic.AddUint64(&serverConnectionCounter, 1)
	t.framer.writer.Flush() // 把SETTINGS、WindowUpdate刷到网络
	// defer:出错就Close transport
	defer func() {
		if err != nil {
			t.Close(err)
		}
	}()

字段按职责分组:

几个值得单独说的点

  1. peer(http2_server.go:255)------你在业务代码里见过的老朋友

peer.FromContext(ctx) 拿到的就是它:客户端地址 + 服务端地址 + AuthInfo(TLS 证书信息,就是第一个问题里 ServerHandshake 返回的那个 tlsInfo)。建 transport 时打包一次,之后每个 RPC 原样带给你的 handler。

  1. fc: &trInFlow{limit: uint32(icwz)}(:269)------前面聊的流控在这里落地

这就是那张"连接级信用卡"的账本。初始额度 limit 填的就是前面 WriteWindowUpdate(0, delta) 扩到的大小 icwz------告诉对面能发多少,自己这边的账本就得记多少。之后每收一个 DATA 帧走 onData 扣账,攒到额度 1/4 就还一次。

  1. setResetPingStrikes(:279)------为什么是个匿名函数而不是方法

pingStrikes 是客户端乱发 ping 的记过计数器(上一个问题里 kep.MinTime 管制违规就 pingStrikes++,超限直接 GOAWAY(too_many_pings),:929-931)。而 setResetPingStrikes 会被挂到 controlBuf 里几种帧对象的回调上(:1074、:1142、:1186)------服务端自己成功写出 ping/goAway/数据帧时,说明连接是活的,把记过清零。写成闭包而不是方法,注释说了:避免把方法转闭包时产生一次堆分配(这个回调要频繁塞进帧对象里)。

  1. channelz.RegisterSocket(:286)------把这条连接登记进"监控数据库"

channelz 是 gRPC 内置的自省系统(grpc_channelz 服务能查)。这里把 socket 登记到树里,登记时顺手采集:socket 选项、本地/远端地址、TLS 安全信息(如果 authInfo 实现了 ChannelzSecurityInfo------TLS 的 tlsInfo 就实现了,会把密码套件、对端证书报上去)。父节点是 config.ChannelzParent,也就是这条连接所属的 Server。

  1. controlBuf(:300)------读写两个 goroutine 之间的"邮箱"

这是 gRPC 写路径的核心设计:全连接只有一个写者(马上要启动的 loopyWriter goroutine,:349)。读 goroutine、你的 handler、流控逻辑......谁想写帧(响应头、数据、WINDOW_UPDATE、ping、GOAWAY)都不许直接碰 socket,只能往 controlBuf 这个无锁队列里投递,由 loopyWriter 一个人取出来按序写。好处:帧之间不会交错、不用给 conn 加锁、还能做优先级合并。

  1. bdpEst(:302)------流控窗口的"自动挡"

呼应流控那次:除了建连时手动扩窗(InitialConnWindowSize),还有这个 BDP 估算器跑"自动挡"------定期发 ping 测带宽×延迟积,发现链路能吃下更大窗口就调 t.updateFlowControl 发 WINDOW_UPDATE 扩窗。StaticWindowSize 为 true(用户显式要静态窗口)就不启用。

  1. t.framer.writer.Flush()(:309)------本段最不能忽略的一行

framer 的 writer 是带缓冲的。前面写的 SETTINGS 帧、WINDOW_UPDATE 帧此刻还舒舒服服躺在 bufio 缓冲区里,一个字节都没上网。而函数马上要阻塞读客户端的 preface(:319)。不 Flush 会怎样?------对面在等你的 SETTINGS,你在等对面的 preface,两边互相干等,死锁。这一行就是"把话说完,再开始听"。

一句话总结

装配 transport:连接、流控账本、keepalive 策略、监控登记、读写协调频道各就各位,最后 Flush 把攒在缓冲区里的帧真正发出去------机器造好了,下一行就开始读客户端的招呼(preface)了。

读取客户端http2魔数:

读取客户端发来的第一个Settings帧:

① 为什么会有 ACK? SETTINGS 帧有两种身份:装着真实设置的帧,和"确认收到你的设置"的空帧(ACK)。我们在建连时发过自己的 SETTINGS(:217),客户端回敬的 ACK 也会走进这个函数------对 ACK 无事可做,直接返回。

② 拆包分类:一项特殊,其余打包

  • SettingMaxHeaderListSize:客户端在宣布"你发给我的响应头不许超过这么大"。它约束的是我们的发送侧,所以单独拆出来,存进 t.maxSendHeaderListSize(对比:收方向的限制叫 maxRecvHeaderListSize)。

  • 其余设置(INITIAL_WINDOW_SIZE、HEADER_TABLE_SIZE 等)打包进 ss,交给 incomingSettings 这个队列项。

③ executeAndPut------这段代码最精妙的一行

看它的实现(controlbuf.go:371-392):先拿 c.mu 锁,锁内执行回调 f,然后把 incomingSettings 入队,解锁。关键是这把锁和 loopyWriter 取项目用的是同一把锁。于是"改 maxSendHeaderListSize"和"这条设置排在队列第 N 位"变成了一个原子动作------中间不可能被写 goroutine 插队,保证顺序:排在这条之前的帧用旧规则,之后的用新规则。如果改成"先改状态、再入队"两步,中间就可能被写者穿插,顺序就乱了。

入队之后呢(loopyWriter 消费时)

启动后台协程.loopyWriter:写循环协程,统一输出所有http2帧(response、data、ping、goaway等):

run方法:

Go 复制代码
func (l *loopyWriter) run() (err error) {
	defer func() {
		if l.logger.V(logLevel) {
			l.logger.Infof("loopyWriter exiting with error: %v", err)
		}
		// 非IO错误场景,退出前尽量把缓冲区残留数据刷出去
		if !isIOError(err) {
			l.framer.writer.Flush()
		}
		l.cbuf.finish() // 关闭controlBuf,唤醒所有等待写入的goroutine,返回错误
	}()
	for {
		// get(true):阻塞等待,直到拿到一个任务item或者队列关闭出错
		it, err := l.cbuf.get(true)
		if err != nil {
			return err
		}
		// 处理单个输出任务:组装http2帧,写入framer的内存buffer(还没真正TCP send)
		if err = l.handle(it); err != nil {
			return err
		}
		// processData:处理待发送DATA帧,受流控窗口限制,能发多少就写多少到buffer
		if _, err = l.processData(); err != nil {
			return err
		}
		gosched := true
	hasdata:
		// 批量消费模式:非阻塞拉取队列剩余任务,做批处理
		for {
			// get(false):非阻塞,没有任务立刻返回 nil
			it, err := l.cbuf.get(false)
			if err != nil {
				return err
			}
			if it != nil {
				// 还有任务,继续处理,不刷网络
				if err = l.handle(it); err != nil {
					return err
				}
				if _, err = l.processData(); err != nil {
					return err
				}
				continue hasdata // 回到循环头部继续取
			}
			// 队列已经拿不到新任务,处理积压DATA流控数据
			isEmpty, err := l.processData()
			if err != nil {
				return err
			}
			if !isEmpty {
				// 还有受流控限制还没发完的数据,继续循环
				continue hasdata
			}
			// gosched 优化:缓冲区数据量很小,让出CPU给其他goroutine,攒更多数据再Flush,减少syscall
			if gosched {
				gosched = false
				if l.framer.writer.offset < minBatchSize {
					runtime.Gosched()
					continue hasdata
				}
			}
			// 缓冲区达到阈值,执行真正网络IO,把buffer全部发给对端
			l.framer.writer.Flush()
			break hasdata
		}
	}
}

handle方法:

十三种邮件,按性质分四类:

① 回应对方的(收方向触发的回执)

② 我方主动发货的(真正的业务输出)

③ 纯记账的(不产生任何网络输出)

④ 控制信号(不是帧,是命令)

两个值得注意的细节

1. 看返回值就能分出"谁真写网络"

返回 error 的 handler(headerFrame、ping、goAway、各种 settings/windowUpdate......)都会真的往 conn 上写字节------写失败就是连接出问题,错误要传回 run() 触发退出。不返回 error 的四个(incomingWindowUpdate、dataFrame→preprocessData、registerStream、outFlowControlSizeRequest)只做记账和排队,不碰 socket。

2. *dataFrame 是最特殊的:送来只挂号,不看病

preprocessData(controlbuf.go:787)只把数据帧挂到对应流的待发列表上就返回。真正写字节的是 run() 循环里的 processData()------这就是为什么 run() 每处理完一件邮件都要调一次 processData。目的前面讲过:数据要在流之间轮转(每流 16KB 一轮)、和控制帧穿插,不能让某个大流霸占连接。

一句话总结

分拣台:13 种邮件各认各的门牌------收执类回应对方、发货类写出帧、记账类只动账本、closeConnection 一到直接拉闸关店。数据邮件最傲娇:挂号后去排队,由 processData 统一叫号发货。

processData方法:

Go 复制代码
func (l *loopyWriter) processData() (bool, error) {
	// sendQuota:连接级别剩余可发送字节配额;配额为0直接返回,没有数据可发送
	if l.sendQuota == 0 {
		return true, nil
	}
	// 从待发送队列取出第一个stream(多个stream轮询,公平发送)
	str := l.activeStreams.dequeue()
	if str == nil {
		return true, nil
	}
	reader := &str.reader
	// peek拿到该流队首待发送的dataFrame,只偷看不弹出
	dataItem := str.itl.peek().(*dataFrame)
	// 第一次处理这个dataItem:初始化reader,释放原始buffer内存
	if !dataItem.processing {
		dataItem.processing = true
		reader.Reset(dataItem.data)
		dataItem.data.Free()
	}
	// ========== 分支1:空DATA帧 ==========
	if len(dataItem.h) == 0 && reader.Remaining() == 0 {
		// 写空DATA帧,可携带endStream标记,表示该RPC流结束
		if err := l.framer.writeData(dataItem.streamID, dataItem.endStream, nil); err != nil {
			return false, err
		}
		str.itl.dequeue() // 消费掉这个空dataFrame
		reader.Close()
		if str.itl.isEmpty() {
			str.state = empty
		} else if trailer, ok := str.itl.peek().(*headerFrame); ok {
			// 空data之后紧跟trailer(HeaderFrame),直接写trailer结束流
			if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
				return false, err
			}
			if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
				return false, err
			}
		} else {
			// 还有数据,放回active队列,下一轮继续发送
			l.activeStreams.enqueue(str)
		}
		return false, nil
	}
	// ========== 计算本次最多可发送字节,双层流控约束 ==========
	maxSize := http2MaxFrameLen // HTTP2协议最大单帧 16KB
	// ① Stream级别流控:本stream还能发多少字节
	if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 {
		str.state = waitingOnStreamQuota // 单流窗口耗尽,标记状态
		return false, nil
	} else if maxSize > strQuota {
		maxSize = strQuota
	}
	// ② Connection级别流控:整个TCP连接剩余配额
	if maxSize > int(l.sendQuota) {
		maxSize = int(l.sendQuota)
	}
	// h:grpc‑message压缩头;reader:真实业务payload
	hSize := min(maxSize, len(dataItem.h))
	dSize := min(maxSize-hSize, reader.Remaining())
	remainingBytes := len(dataItem.h) + reader.Remaining() - hSize - dSize
	size := hSize + dSize
	// 组装待发送buffer
	l.writeBuf = l.writeBuf[:0]
	if hSize > 0 {
		l.writeBuf = append(l.writeBuf, dataItem.h[:hSize])
	}
	if dSize > 0 {
		var err error
		l.writeBuf, err = reader.Peek(dSize, l.writeBuf)
		if err != nil {
			l.logger.Errorf("unexpected error while reading Data frame payload: %v", err)
			return false, err
		}
	}
	// 配额已经校验通过,补充该stream的write quota统计
	str.wq.replenish(size)
	// 是否设置endStream:当前是这条消息最后一块,且没有剩余字节
	var endStream bool
	if dataItem.endStream && remainingBytes == 0 {
		endStream = true
	}
	// 每一块数据写出的回调(监控、埋点)
	if dataItem.onEachWrite != nil {
		dataItem.onEachWrite()
	}
	// 写入framer内存缓冲区(尚未真正TCP发送,等待后续Flush)
	err := l.framer.writeData(dataItem.streamID, endStream, l.writeBuf)
	// 移动读指针,丢弃已经写出的数据
	reader.Discard(dSize)
	// 超大writeBuf主动置nil释放内存,避免长连接内存泄漏
	if cap(l.writeBuf) > maxWriteBufSize {
		l.writeBuf = nil
	} else {
		clear(l.writeBuf)
	}
	if err != nil {
		return false, err
	}
	// 更新统计:stream已发出字节、连接全局配额扣减
	str.bytesOutStanding += size
	l.sendQuota -= uint32(size)
	// 截断已经发送的header部分
	dataItem.h = dataItem.h[hSize:]
	// 当前dataFrame全部字节发送完毕,弹出队列
	if remainingBytes == 0 {
		reader.Close()
		str.itl.dequeue()
	}
	// 判断stream后续状态
	if str.itl.isEmpty() {
		// 该stream没有更多待发送项,标记empty
		str.state = empty
	} else if trailer, ok := str.itl.peek().(*headerFrame); ok {
		// 下一项是trailer,直接写trailer帧结束RPC流
		if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
			return false, err
		}
		if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
			return false, err
		}
	} else if int(l.oiws)-str.bytesOutStanding <= 0 {
		// stream层流控窗口耗尽,标记等待窗口更新,不再入队
		str.state = waitingOnStreamQuota
	} else {
		// 还有数据,配额尚有,放回activeStreams队列,下一轮继续发送分片
		l.activeStreams.enqueue(str)
	}
	return false, nil
}

上面newHTTP2Transport任务就完成了.可以进行业务的处理了.

serveStreams方法:

Go 复制代码
func (s *Server) serveStreams(ctx context.Context, st transport.ServerTransport, rawConn net.Conn) {
	// 把底层net.Conn、peer信息注入context,业务代码可获取
	ctx = transport.SetConnection(ctx, rawConn)
	ctx = peer.NewContext(ctx, st.Peer())
	// statsHandler:连接级别监控埋点,开启ConnBegin事件
	if s.statsHandler != nil {
		ctx = s.statsHandler.TagConn(ctx, &stats.ConnTagInfo{
			RemoteAddr: st.Peer().Addr,
			LocalAddr:  st.Peer().LocalAddr,
		})
		s.statsHandler.HandleConn(ctx, &stats.ConnBegin{})
	}
	defer func() {
		// 整个transport连接结束,关闭传输层
		st.Close(errors.New("finished serving streams for the server transport"))
		// 上报连接结束监控事件 ConnEnd
		if s.statsHandler != nil {
			s.statsHandler.HandleConn(ctx, &stats.ConnEnd{})
		}
	}()
	// streamQuota:**单连接维度RPC并发控制器**,maxConcurrentStreams
	streamQuota := newHandlerQuota(s.opts.maxConcurrentStreams)
	// 核心回调:st.HandleStreams,transport读循环每收到一个新Stream,就调用传入的回调
	st.HandleStreams(ctx, func(stream *transport.ServerStream) {
		s.handlersWG.Add(1)
		streamQuota.acquire() // 获取单连接并发许可;超过maxConcurrentStreams会阻塞
		f := func() {
			defer streamQuota.release() // RPC处理结束释放并发配额
			defer s.handlersWG.Done()   // Server全局WaitGroup,用于Server.GracefulStop等待全部RPC结束
			s.handleStream(st, stream)  // 进入真正的RPC处理逻辑:拦截器、路由、执行业务handler
		}
		// numServerWorkers > 0:开启服务端worker池模式(goroutine池)
		if s.opts.numServerWorkers > 0 {
			select {
			case s.serverWorkerChannel <- f:
				// 成功投递到worker池,由池内goroutine执行f,直接return
				return
			default:
				// worker池全部忙满,channel满,走fallback:新建goroutine执行
			}
		}
		// 没有worker池 / worker池已满:直接go f()新开goroutine处理RPC
		go f()
	})
}

HandleStreams方法:

operateHeaders方法:

Go 复制代码
func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeadersFrame, handle func(*ServerStream)) error {
	// maxStreamMu:保护maxStreamID,校验streamID单调递增(HTTP2规定服务端收到streamID必须奇数,严格递增)
	t.maxStreamMu.Lock()
	defer t.maxStreamMu.Unlock()
	streamID := frame.Header().StreamID
	// Header列表超过MaxHeaderListSize被截断,直接RST_STREAM
	if frame.Truncated {
		t.controlBuf.put(&cleanupStream{
			streamID: streamID,
			rst:      true,
			rstCode:  http2.ErrCodeFrameSize,
			onWrite:  func() {},
		})
		return nil
	}
	// HTTP2规范:客户端发起流ID一定是奇数;且必须严格大于上一次maxStreamID,不能复用旧ID
	if streamID%2 != 1 || streamID <= t.maxStreamID {
		return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame)
	}
	t.maxStreamID = streamID
	// 初始化ServerStream对象
	s := &ServerStream{
		Stream: Stream{
			id: streamID,
			fc: inFlow{limit: uint32(t.initialWindowSize)}, // stream级别接收流控窗口
		},
		st:               t,
		headerWireLength: int(frame.Header().Length),
	}
	s.Stream.buf.init() // stream接收buffer初始化,存放请求DATA payload
	var (
		isGRPC      = false        // 是否合法gRPC请求(content‑type校验)
		contentType = ""
		mdata       = make(metadata.MD, len(frame.Fields)) // metadata,存储http header
		httpMethod  string
		protocolError bool
		headerError   *status.Status
		timeoutSet bool
		timeout    time.Duration
	)
	// ========== 遍历解析所有HTTP2 header字段 ==========
	for _, hf := range frame.Fields {
		switch hf.Name {
		case "content-type":
			contentSubtype, validContentType := grpcutil.ContentSubtype(hf.Value)
			if !validContentType {
				contentType = hf.Value
				break
			}
			mdata[hf.Name] = append(mdata[hf.Name], hf.Value)
			s.contentSubtype = contentSubtype
			isGRPC = true // content‑type合法,标记为gRPC请求
		case "grpc-accept-encoding":
			mdata[hf.Name] = append(mdata[hf.Name], hf.Value)
			if hf.Value == "" {
				continue
			}
			compressors := hf.Value
			if s.clientAdvertisedCompressors != "" {
				compressors = s.clientAdvertisedCompressors + "," + compressors
			}
			s.clientAdvertisedCompressors = compressors // 客户端支持的压缩算法列表
		case "grpc-encoding":
			s.recvCompress = hf.Value // 请求payload使用的压缩算法
		case ":method":
			httpMethod = hf.Value // http方法,gRPC必须POST
		case ":path":
			s.method = hf.Value // gRPC完整method路径,例如 /helloworld.Greeter/SayHello
		case "grpc-timeout":
			timeoutSet = true
			var err error
			if timeout, err = decodeTimeout(hf.Value); err != nil {
				headerError = status.Newf(codes.Internal, "malformed grpc-timeout: %v", err)
			}
		// A41规范:HTTP2不允许Connection头,直接判定协议错误
		case "connection":
			if t.logger.V(logLevel) {
				t.logger.Infof("Received a HEADERS frame with a :connection header which makes the request malformed, as per HTTP/2 spec")
			}
			protocolError = true
		default:
			// 普通自定义metadata,跳过保留头;解码二进制metadata(base64)
			if isReservedHeader(hf.Name) && !isWhitelistedHeader(hf.Name) {
				break
			}
			v, err := decodeMetadataHeader(hf.Name, hf.Value)
			if err != nil {
				headerError = status.Newf(codes.Internal, "malformed binary metadata %q in header %q: %v", hf.Value, hf.Name, err)
				t.logger.Warningf("Failed to decode metadata header (%q, %q): %v", hf.Name, hf.Value, err)
				break
			}
			mdata[hf.Name] = append(mdata[hf.Name], v)
		}
	}
	// A41规范校验::authority / host不能多个值
	if len(mdata[":authority"]) > 1 || len(mdata["host"]) > 1 {
		errMsg := fmt.Sprintf("num values of :authority: %v, num values of host: %v, both must only have 1 value as per HTTP/2 spec", len(mdata[":authority"]), len(mdata["host"]))
		if t.logger.V(logLevel) {
			t.logger.Infof("Aborting the stream early: %v", errMsg)
		}
		t.writeEarlyAbort(streamID, s.contentSubtype, status.New(codes.Internal, errMsg), http.StatusBadRequest, !frame.StreamEnded())
		return nil
	}
	// 协议错误,RST_STREAM重置流
	if protocolError {
		t.controlBuf.put(&cleanupStream{
			streamID: streamID,
			rst:      true,
			rstCode:  http2.ErrCodeProtocol,
			onWrite:  func() {},
		})
		return nil
	}
	// content‑type不对,不是gRPC请求,提前返回错误响应
	if !isGRPC {
		t.writeEarlyAbort(streamID, s.contentSubtype, status.Newf(codes.InvalidArgument, "invalid gRPC request content-type %q", contentType), http.StatusUnsupportedMediaType, !frame.StreamEnded())
		return nil
	}
	// header解析出错,提前写错误应答
	if headerError != nil {
		t.writeEarlyAbort(streamID, s.contentSubtype, headerError, http.StatusBadRequest, !frame.StreamEnded())
		return nil
	}
	// A41规范:host与:authority兼容处理
	if len(mdata[":authority"]) == 0 {
		if host, ok := mdata["host"]; ok {
			mdata[":authority"] = host
			delete(mdata, "host")
		}
	} else {
		delete(mdata, "host")
	}
	// HEADERS帧同时带end_stream标记:没有DATA帧,请求已经读完
	if frame.StreamEnded() {
		s.state = streamReadDone
	}
	// 构建stream上下文,带超时/取消
	if timeoutSet {
		s.ctx, s.cancel = context.WithTimeout(ctx, timeout)
	} else {
		s.ctx, s.cancel = context.WithCancel(ctx)
	}
	// metadata注入ctx,业务代码 metadata.FromIncomingContext 获取
	if len(mdata) > 0 {
		s.ctx = metadata.NewIncomingContext(s.ctx, mdata)
	}
	t.mu.Lock()
	// transport已经关闭,直接取消stream,丢弃请求
	if t.state != reachable {
		t.mu.Unlock()
		s.cancel()
		return nil
	}
	// 超过单连接最大并发流 maxStreams(MAX_CONCURRENT_STREAMS),返回 REFUSED_STREAM
	if uint32(len(t.activeStreams)) >= t.maxStreams {
		t.mu.Unlock()
		t.controlBuf.put(&cleanupStream{
			streamID: streamID,
			rst:      true,
			rstCode:  http2.ErrCodeRefusedStream,
			onWrite:  func() {},
		})
		s.cancel()
		return nil
	}
	// gRPC要求 :method 必须是POST
	if httpMethod != http.MethodPost {
		t.mu.Unlock()
		errMsg := fmt.Sprintf("Received a HEADERS frame with :method %q which should be POST", httpMethod)
		if t.logger.V(logLevel) {
			t.logger.Infof("Aborting the stream early: %v", errMsg)
		}
		t.writeEarlyAbort(streamID, s.contentSubtype, status.New(codes.Internal, errMsg), http.StatusMethodNotAllowed, !frame.StreamEnded())
		s.cancel()
		return nil
	}
	// tap钩子,流量拦截
	if t.inTapHandle != nil {
		var err error
		if s.ctx, err = t.inTapHandle(s.ctx, &tap.Info{FullMethodName: s.method, Header: mdata}); err != nil {
			t.mu.Unlock()
			if t.logger.V(logLevel) {
				t.logger.Infof("Aborting the stream early due to InTapHandle failure: %v", err)
			}
			stat, ok := status.FromError(err)
			if !ok {
				stat = status.New(codes.PermissionDenied, err.Error())
			}
			t.writeEarlyAbort(s.id, s.contentSubtype, stat, http.StatusOK, !frame.StreamEnded())
			return nil
		}
	}
	// 超时设置极小,ctx已经过期,提前终止流
	if s.ctx.Err() != nil {
		t.mu.Unlock()
		st := status.New(codes.DeadlineExceeded, context.DeadlineExceeded.Error())
		t.writeEarlyAbort(s.id, s.contentSubtype, st, http.StatusOK, !frame.StreamEnded())
		return nil
	}
	// 加入活跃流map
	t.activeStreams[streamID] = s
	if len(t.activeStreams) == 1 {
		t.idle = time.Time{} // 连接不再空闲
	}
	// grpc‑timeout超时定时器:超时触发closeStream,发送RST_CANCEL
	if timeoutSet {
		cancelUpdated := make(chan struct{})
		timer := internal.TimeAfterFunc(timeout, func() {
			<-cancelUpdated
			t.closeStream(s, true, http2.ErrCodeCancel, false)
		})
		// 包装cancel,停止定时器
		oldCancel := s.cancel
		s.cancel = func() {
			oldCancel()
			timer.Stop()
		}
		close(cancelUpdated)
	}
	t.mu.Unlock()
	// channelz监控
	if channelz.IsOn() {
		t.channelz.SocketMetrics.StreamsStarted.Add(1)
		t.channelz.SocketMetrics.LastRemoteStreamCreatedTimestamp.Store(time.Now().UnixNano())
	}
	// 初始化读、写相关成员
	s.readRequester = s
	s.ctxDone = s.ctx.Done()
	s.Stream.wq.init(defaultWriteQuota, s.ctxDone) // stream写配额
	s.trReader = transportReader{
		reader: recvBufferReader{
			ctx:     s.ctx,
			ctxDone: s.ctxDone,
			recv:    &s.buf,
		},
		windowHandler: s,
	}
	// 投递registerStream到controlBuf,loopyWriter注册该stream的写配额
	t.controlBuf.put(&registerStream{
		streamID: s.id,
		wq:       &s.wq,
	})
	// 关键回调!把stream交给上层 serveStreams 的handle回调
	handle(s)
	return nil
}

operateHeaders(http2_server.go:377)是服务端每个 RPC 的产房:读循环每收到一个 HEADERS 帧(= 客户端发起一个新 RPC)就调它一次,任务是验货 → 拆包 → 把流组装成型 → 移交业务层。它就是传输层和业务层的交汇点,前面读过的所有伏笔在这条函数里集体收线。

九个阶段

① 三道进门检查

HTTP/2 规定客户端开的流 ID 必须是奇数、严格递增------拿这条就能挡掉一堆畸形流量。

② 搭流骨架

inFlow 就是前面聊流控时说的"流级信用卡账本",初始额度是建连时协商的 initialWindowSize。

③ 拆头部字段(大 for 循环)

④ 快速失败四连(不建流、直接回绝)

非法 :authority/host 数量 → 400;有 connection 头 → RST(PROTOCOL_ERROR);不是 gRPC → 415;grpc-timeout/metadata 畸形 → 400。全部走 writeEarlyAbort------尽早回绝、绝不浪费资源。

⑤ :authority 规范化:没有就用 host 顶替、都有则丢弃 host(gRPC A41 提案)。

⑥ ctx 装修------三个业务侧 API 的源头:

加上 serveStreams 里装的 peer------handler 里三大件(ctx.Done()、metadata、peer)在这条链上配齐。

⑦ 过五关(t.mu 临界区内)

  • transport 已挂 → 取消走人

  • len(activeStreams) >= maxStreams → RST(REFUSED_STREAM)------注意选这个码很讲究:被拒流是"可重试"信号,客户端会换连接/稍后重发,而不是当成业务错误

  • :method 非 POST → 405

  • inTapHandle(服务端 tap 中间件,做鉴权/风控的钩子)拒绝 → PermissionDenied 回绝

  • 超时设得太短已经过期 → DeadlineExceeded 回绝

⑧ 登记 + 上闹钟

⑨ 配齐读写装备 → 移交业务

最后一行 handle(s) 就是前面 serveStreams 里那个 callback:记名 → 过闸 → go handleStream → 查方法表 → sd.Handler → 你的业务代码开跑。

最后就会进入到handleStream方法:

Go 复制代码
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) {
	// 取出stream的context(operateHeaders构建,包含timeout、metadata、cancel)
	ctx := stream.Context()
	// 将*Server对象注入context,拦截器可以拿到server实例
	ctx = contextWithServer(ctx, s)
	var ti *traceInfo
	// 开启内置trace调试追踪(grpc内置trace,非opentelemetry)
	if EnableTracing {
		tr := newTrace("grpc.Recv."+methodFamily(stream.Method()), stream.Method())
		ctx = newTraceContext(ctx, tr)
		ti = &traceInfo{
			tr: tr,
			firstLine: firstLine{
				client:     false,
				remoteAddr: t.Peer().Addr,
			},
		}
		if dl, ok := ctx.Deadline(); ok {
			ti.firstLine.deadline = time.Until(dl)
		}
	}
	sm := stream.Method()
	// :path为空,畸形请求
	if sm == "" {
		s.handleMalformedMethodName(stream, ti)
		return
	}
	// 严格路径校验:gRPC method路径必须以 `/` 开头,格式 `/ServiceName/MethodName`
	if sm[0] != '/' {
		// 环境变量可临时关闭严格校验(不推荐,安全风险)
		if envconfig.DisableStrictPathChecking {
			if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
				channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream received malformed method name %q. Allowing it because the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING is set to true, but this option will be removed in a future release.", sm)
			}
		} else {
			if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
				channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream rejected malformed method name %q. To temporarily allow such requests, set the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to true. Note that this is not recommended as it may allow requests to bypass security policies.", sm)
			}
			s.handleMalformedMethodName(stream, ti)
			return
		}
	} else {
		// 去掉开头的 `/`,例如 /helloworld.Greeter/SayHello → helloworld.Greeter/SayHello
		sm = sm[1:]
	}
	// 切割 service / method
	pos := strings.LastIndex(sm, "/")
	if pos == -1 {
		s.handleMalformedMethodName(stream, ti)
		return
	}
	service := sm[:pos]
	method := sm[pos+1:]
	// statsHandler 监控埋点:RPCTagInfo + InHeader事件
	if s.statsHandler != nil {
		md, _ := metadata.FromIncomingContext(ctx)
		ctx = s.statsHandler.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: stream.Method()})
		s.statsHandler.HandleRPC(ctx, &stats.InHeader{
			FullMethod:  stream.Method(),
			RemoteAddr:  t.Peer().Addr,
			LocalAddr:   t.Peer().LocalAddr,
			Compression: stream.RecvCompress(),
			WireLength:  stream.HeaderWireLength(),
			Header:      md,
		})
	}
	// 把更新后的ctx回写进stream对象,后续Recv/Send可拿到新ctx
	stream.SetContext(ctx)
	// 在server注册表里查找service
	srv, knownService := s.services[service]
	if knownService {
		// 命中一元RPC方法
		if md, ok := srv.methods[method]; ok {
			s.processUnaryRPC(ctx, stream, srv, md, ti)
			return
		}
		// 命中流式RPC(客户端流/服务端流/双向流)
		if sd, ok := srv.streams[method]; ok {
			s.processStreamingRPC(ctx, stream, srv, sd, ti)
			return
		}
	}
	// 走到这里:service不存在,或者service存在但method不存在
	// 如果配置unknownStreamDesc,走unknown handler,可以拦截所有未注册方法
	if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil {
		s.processStreamingRPC(ctx, stream, nil, unknownDesc, ti)
		return
	}
	// 返回 Unimplemented 状态码
	var errDesc string
	if !knownService {
		errDesc = fmt.Sprintf("unknown service %v", service)
	} else {
		errDesc = fmt.Sprintf("unknown method %v for service %v", method, service)
	}
	if ti != nil {
		ti.tr.LazyPrintf("%s", errDesc)
		ti.tr.SetError()
	}
	// 向stream写status,返回给客户端
	if err := stream.WriteStatus(status.New(codes.Unimplemented, errDesc)); err != nil {
		if ti != nil {
			ti.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true)
			ti.tr.SetError()
		}
		channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream failed to write status: %v", err)
	}
	if ti != nil {
		ti.tr.Finish()
	}
}

processUnaryRPC方法:

Go 复制代码
func (s *Server) processUnaryRPC(ctx context.Context, stream *transport.ServerStream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
	sh := s.statsHandler
	// stats / trace / channelz 监控埋点,defer统一收尾(减少defer数量,降低栈开销)
	if sh != nil || trInfo != nil || channelz.IsOn() {
		if channelz.IsOn() {
			s.incrCallsStarted()
		}
		var statsBegin *stats.Begin
		if sh != nil {
			statsBegin = &stats.Begin{
				BeginTime:      time.Now(),
				IsClientStream: false,
				IsServerStream: false,
			}
			sh.HandleRPC(ctx, statsBegin)
		}
		if trInfo != nil {
			trInfo.tr.LazyLog(&trInfo.firstLine, false)
		}
		// defer按逆序执行:trace结束 → stats.End → channelz计数
		defer func() {
			if trInfo != nil {
				if err != nil && err != io.EOF {
					trInfo.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true)
					trInfo.tr.SetError()
				}
				trInfo.tr.Finish()
			}
			if sh != nil {
				end := &stats.End{
					BeginTime: statsBegin.BeginTime,
					EndTime:   time.Now(),
				}
				if err != nil && err != io.EOF {
					end.Error = toRPCErr(err)
				}
				sh.HandleRPC(ctx, end)
			}
			if channelz.IsOn() {
				if err != nil && err != io.EOF {
					s.incrCallsFailed()
				} else {
					s.incrCallsSucceeded()
				}
			}
		}()
	}
	// binarylog:二进制日志,用于抓包调试,记录请求/响应原始报文
	var binlogs []binarylog.MethodLogger
	if ml := binarylog.GetMethodLogger(stream.Method()); ml != nil {
		binlogs = append(binlogs, ml)
	}
	if s.opts.binaryLogger != nil {
		if ml := s.opts.binaryLogger.GetMethodLogger(stream.Method()); ml != nil {
			binlogs = append(binlogs, ml)
		}
	}
	if len(binlogs) != 0 {
		md, _ := metadata.FromIncomingContext(ctx)
		logEntry := &binarylog.ClientHeader{
			Header:     md,
			MethodName: stream.Method(),
			PeerAddr:   nil,
		}
		if deadline, ok := ctx.Deadline(); ok {
			logEntry.Timeout = time.Until(deadline)
			if logEntry.Timeout < 0 {
				logEntry.Timeout = 0
			}
		}
		if a := md[":authority"]; len(a) > 0 {
			logEntry.Authority = a[0]
		}
		if peer, ok := peer.FromContext(ctx); ok {
			logEntry.PeerAddr = peer.Addr
		}
		for _, binlog := range binlogs {
			binlog.Log(ctx, logEntry)
		}
	}
	// ========== 压缩/解压缩处理 ==========
	var comp, decomp encoding.Compressor
	var cp Compressor
	var dc Decompressor
	var sendCompressorName string
	// 请求解压:优先使用server option配置dc,其次全局注册的decompressor
	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)
			stream.WriteStatus(st)
			return st.Err()
		}
	}
	// 响应压缩:优先server option cp;否则复用客户端请求的压缩算法
	if s.opts.cp != nil {
		cp = s.opts.cp
		sendCompressorName = cp.Type()
	} else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity {
		comp = encoding.GetCompressor(rc)
		if comp != nil {
			sendCompressorName = comp.Name()
		}
	}
	if sendCompressorName != "" {
		if err := stream.SetSendCompress(sendCompressorName); err != nil {
			return status.Errorf(codes.Internal, "grpc: failed to set send compressor: %v", err)
		}
	}
	var payInfo *payloadInfo
	if sh != nil || len(binlogs) != 0 {
		payInfo = &payloadInfo{}
		defer payInfo.free()
	}
	// ========== 1.读取、解压请求payload(一元RPC读一次请求) ==========
	d, err := recvAndDecompress(&parser{r: stream, bufferPool: s.opts.bufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp, true)
	if err != nil {
		if e := stream.WriteStatus(status.Convert(err)); e != nil {
			channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
		}
		return err
	}
	// buffer内存释放,防止内存泄漏
	freed := false
	dataFree := func() {
		if !freed {
			d.Free()
			freed = true
		}
	}
	defer dataFree()
	// df:解码回调,被Handler内部调用,做protobuf反序列化
	df := func(v any) error {
		defer dataFree()
		if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil {
			return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err)
		}
		// stats上报 InPayload
		if sh != nil {
			sh.HandleRPC(ctx, &stats.InPayload{
				RecvTime:         time.Now(),
				Payload:          v,
				Length:           d.Len(),
				WireLength:       payInfo.compressedLength + headerLen,
				CompressedLength: payInfo.compressedLength,
			})
		}
		// binarylog记录客户端消息
		if len(binlogs) != 0 {
			cm := &binarylog.ClientMessage{
				Message: d.Materialize(),
			}
			for _, binlog := range binlogs {
				binlog.Log(ctx, cm)
			}
		}
		if trInfo != nil {
			trInfo.tr.LazyLog(&payload{sent: false, msg: v}, true)
		}
		return nil
	}
	// 把transport stream放入ctx,拦截器/业务代码可以拿到stream对象
	ctx = NewContextWithServerTransportStream(ctx, stream)
	// ========== 2.执行拦截器链 + 业务handler ==========
	// md.Handler:生成的wrapper函数,内部执行unaryInt拦截器链,再调用用户写的业务方法
	reply, appErr := md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)
	// ========== 分支A:业务返回错误 appErr != nil ==========
	if appErr != nil {
		appStatus, ok := status.FromError(appErr)
		if !ok {
			// 普通error转status;context取消/超时做特殊转换
			appStatus = status.FromContextError(appErr)
			appErr = appStatus.Err()
		}
		if trInfo != nil {
			trInfo.tr.LazyLog(stringer(appStatus.Message()), true)
			trInfo.tr.SetError()
		}
		// 往stream写入错误status(trailer)
		if e := stream.WriteStatus(appStatus); e != nil {
			channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
		}
		// binarylog记录header、trailer、错误
		if len(binlogs) != 0 {
			if h, _ := stream.Header(); h.Len() > 0 {
				sh := &binarylog.ServerHeader{Header: h}
				for _, binlog := range binlogs {
					binlog.Log(ctx, sh)
				}
			}
			st := &binarylog.ServerTrailer{
				Trailer: stream.Trailer(),
				Err:     appErr,
			}
			for _, binlog := range binlogs {
				binlog.Log(ctx, st)
			}
		}
		return appErr
	}
	// ========== 分支B:业务正常,返回reply应答对象 ==========
	if trInfo != nil {
		trInfo.tr.LazyLog(stringer("OK"), false)
	}
	opts := &transport.WriteOptions{Last: true}
	// 业务代码可能中途调用stream.SetSendCompress修改压缩算法,重新获取
	if stream.SendCompress() != sendCompressorName {
		comp = encoding.GetCompressor(stream.SendCompress())
	}
	// ========== 3.发送响应消息(protobuf序列化+压缩,投递loopyWriter) ==========
	if err := s.sendResponse(ctx, stream, reply, cp, opts, comp); err != nil {
		if err == io.EOF {
			return err
		}
		if sts, ok := status.FromError(err); ok {
			if e := stream.WriteStatus(sts); e != nil {
				channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
			}
		} else {
			switch st := err.(type) {
			case transport.ConnectionError:
				// 连接断开,无需处理
			default:
				panic(fmt.Sprintf("grpc: Unexpected error (%T) from sendResponse: %v", st, st))
			}
		}
		if len(binlogs) != 0 {
			h, _ := stream.Header()
			sh := &binarylog.ServerHeader{Header: h}
			st := &binarylog.ServerTrailer{Trailer: stream.Trailer(), Err: appErr}
			for _, binlog := range binlogs {
				binlog.Log(ctx, sh)
				binlog.Log(ctx, st)
			}
		}
		return err
	}
	// binarylog记录服务端应答消息
	if len(binlogs) != 0 {
		h, _ := stream.Header()
		sh := &binarylog.ServerHeader{Header: h}
		sm := &binarylog.ServerMessage{Message: reply}
		for _, binlog := range binlogs {
			binlog.Log(ctx, sh)
			binlog.Log(ctx, sm)
		}
	}
	if trInfo != nil {
		trInfo.tr.LazyLog(&payload{sent: true, msg: reply}, true)
	}
	// 写入成功状态码 OK(grpc‑status=0放到trailer)
	if len(binlogs) != 0 {
		st := &binarylog.ServerTrailer{Trailer: stream.Trailer(), Err: appErr}
		for _, binlog := range binlogs {
			binlog.Log(ctx, st)
		}
	}
	return stream.WriteStatus(statusOK)
}
相关推荐
小小、码农1 小时前
〖Linux文件系统〗:彻底打通文件 IO 全链路
linux·开发语言·数据结构·c++·系统
我的div丢了肿么办1 小时前
go语言中如何安装第3方的包
后端·go
弹简特1 小时前
【Java项目-企悦抽】13-奖品管理模块-奖品列表和创建奖品的实现
java·开发语言·网络·springboot
Rosanci1 小时前
从零到合入主线:我的 DeepSeek Harness 开源贡献实战手记
开发语言·前端·开源
小蒜学长1 小时前
基于Spring Boot+Vue的“禾源”农产品销售系统设计与实现(代码+数据库+LW)
java·spring boot·后端·农产品销售系统·产品溯源
弹简特1 小时前
【Java项目-企悦抽】14-活动管理模块01-创建活动的实现
java·开发语言·springboot
用户EasyAdminBlazor1 小时前
EasyAdminBlazor 审批事务:为什么审批失败必须全部回滚?
后端
迅猛龙办公室2 小时前
Python实现求两个数字之和
java·开发语言·python
计算机魔术师2 小时前
我让AI教学生写前端,三天后课堂变了——Web教育者的集体反思
后端