完整的示例代码可以参考前面的文章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):
-
tls.Server(rawConn, c.config):把裸 TCP conn 包装成 Go 标准库 crypto/tls 的服务端 TLS conn。注意此时还没有任何网络交互,只是包装。
-
conn.Handshake():阻塞执行真正的 TLS 握手------证书交换、密钥协商(ECDHE)、(如果配了 mTLS)验证客户端证书。失败就关闭连接返回 err。
-
conn.ConnectionState():拿到握手结果(协商出的协议、密码套件、对端证书链等)。
-
ALPN 检查:协商出的应用层协议为空说明客户端没做 ALPN。HTTP/2 over TLS 必须通过 ALPN 协商出 h2,所以 1.67+ 默认直接拒绝连接(GRPC_ENFORCE_ALPN_ENABLED 环境变量控制,见 internal/envconfig)。
-
构造 TLSInfo:这就是返回的第二个值 authInfo 的真身,里面装着 tls.ConnectionState(含对端证书),安全级别标为 PrivacyAndIntegrity。
-
提取 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)
}
}()
字段按职责分组:

几个值得单独说的点
- peer(http2_server.go:255)------你在业务代码里见过的老朋友
peer.FromContext(ctx) 拿到的就是它:客户端地址 + 服务端地址 + AuthInfo(TLS 证书信息,就是第一个问题里 ServerHandshake 返回的那个 tlsInfo)。建 transport 时打包一次,之后每个 RPC 原样带给你的 handler。
- fc: &trInFlow{limit: uint32(icwz)}(:269)------前面聊的流控在这里落地
这就是那张"连接级信用卡"的账本。初始额度 limit 填的就是前面 WriteWindowUpdate(0, delta) 扩到的大小 icwz------告诉对面能发多少,自己这边的账本就得记多少。之后每收一个 DATA 帧走 onData 扣账,攒到额度 1/4 就还一次。
- setResetPingStrikes(:279)------为什么是个匿名函数而不是方法
pingStrikes 是客户端乱发 ping 的记过计数器(上一个问题里 kep.MinTime 管制违规就 pingStrikes++,超限直接 GOAWAY(too_many_pings),:929-931)。而 setResetPingStrikes 会被挂到 controlBuf 里几种帧对象的回调上(:1074、:1142、:1186)------服务端自己成功写出 ping/goAway/数据帧时,说明连接是活的,把记过清零。写成闭包而不是方法,注释说了:避免把方法转闭包时产生一次堆分配(这个回调要频繁塞进帧对象里)。
- channelz.RegisterSocket(:286)------把这条连接登记进"监控数据库"
channelz 是 gRPC 内置的自省系统(grpc_channelz 服务能查)。这里把 socket 登记到树里,登记时顺手采集:socket 选项、本地/远端地址、TLS 安全信息(如果 authInfo 实现了 ChannelzSecurityInfo------TLS 的 tlsInfo 就实现了,会把密码套件、对端证书报上去)。父节点是 config.ChannelzParent,也就是这条连接所属的 Server。
- controlBuf(:300)------读写两个 goroutine 之间的"邮箱"
这是 gRPC 写路径的核心设计:全连接只有一个写者(马上要启动的 loopyWriter goroutine,:349)。读 goroutine、你的 handler、流控逻辑......谁想写帧(响应头、数据、WINDOW_UPDATE、ping、GOAWAY)都不许直接碰 socket,只能往 controlBuf 这个无锁队列里投递,由 loopyWriter 一个人取出来按序写。好处:帧之间不会交错、不用给 conn 加锁、还能做优先级合并。
- bdpEst(:302)------流控窗口的"自动挡"
呼应流控那次:除了建连时手动扩窗(InitialConnWindowSize),还有这个 BDP 估算器跑"自动挡"------定期发 ping 测带宽×延迟积,发现链路能吃下更大窗口就调 t.updateFlowControl 发 WINDOW_UPDATE 扩窗。StaticWindowSize 为 true(用户显式要静态窗口)就不启用。
- 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(®isterStream{
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)
}
