Kitex源码解析:客户端发送消息的过程

776 阅读3分钟

Kitex版本:v0.9.1

namespace go api

struct Request {
    1: string message
}

struct Response {
    1: string message
}

service Echo {
    Response echo(1: Request req)
}

以简单的echo.thrift为例,使用

kitex -module github.com/xiaoyi-byte/demo -service echo idl/echo.thrift

生成脚手架代码

client端调用中间件的过程

在生成的代码中,client端通过调用Echo函数向server端发送rpc请求,

image.png 进入p.c.Call函数中

image.png

image.png kc是*KClient对象,其中KClient中的eps是一个endpoint.Endpoint对象

image.png endpoint.Endpoint表示一个method,在这里kc.eps调用时step in可以看到执行的是rpcTimeoutMW的返回值中的函数,于是我们可以合理推断,client端应该是在之前某个地方调用了rpcTimeoutMW,将kc.eps设置成rpcTimeoutMW的返回值中的函数。

通过查找我们可以发现*kClient的initMiddlewares函数调用了rpcTimeoutMW,将其返回值添加到kc的[]endpoint.Middleware中

image.png

image.png

在*KClient的init函数中调用了initMiddlewares函数

image.png 在NewClient函数中调用了init函数

image.png 而在kitex_gen中的client的NewClient函数也调用了client.NewClient方法,而我们写的main函数正是通过这个kitex_gen中的client的NewClient函数来创建client的

image.png 总结:

image.png 回到我们最初的问题,在kc.eps执行时step in可以看到执行的是rpcTimeoutMW的返回值中的函数,而rpcTimeoutMW返回的是一个中间件(func(next endpoint.Endpoint) endpoint.Endpoint),需要执行这个中间件函数才能将其返回值 endpoint.Endpoint 设置成kc.eps,在哪执行的?或者说在哪将kc.eps设置成rpcTimeoutMW的返回值。 通过debug,确认kc.eps的值可以发现,这个过程是在kc.buildInvokeChain()中执行的

image.png 在buildInvokeChain函数中将kc.eps设置为endpoint.Chain(kc.mws...)(innerHandlerEp)

image.png

image.png chain函数返回一个中间件,这个中间件将传入chain函数的所有中间件按倒序链接起来,由于mws的第0个中间件是DummyMiddleware,它会直接返回传入的Endpoint,因此return next会返回mws的第一个中间件的返回值,即rpcTimeoutMW函数返回的Middleware的返回值。由于传入chain函数的参数是innerHandlerEp(invokeHandleEndpoint函数的返回值),也就是说它是最先被链接上的,因此是最后被执行的,而在innerHandlerEp中客户端执行了编码发送等操作。

客户端编解码和发请求

invokeHandleEndpoint函数返回的Endpoint是所有中间件中最后被执行的,在这个Endpoint中执行了编解码和发请求等操作,因此我们之间查看invokeHandleEndpoint函数

image.png

image.png 这个cli.Send和Recv是向服务端发送和接收消息的函数,目前我们只关注Send函数

image.png 可以看到Send函数是阻塞的,再进入到c.transHdlr.Write(ctx, c.conn, req)函数中

image.png 这里会依次执行OutBoundHandler,这里会有默认的元信息透传 MetaHandler,元信息透传是基于传输协议透传一些 RPC 额外的信息给下游,同时读取传输协议中上游透传的信息,参考www.cloudwego.io/zh/docs/kit…

接着会执行p.netHdlr.Write(ctx, conn, sendMsg),进入该函数中 image.png 可以看到这里设置了发送消息payload的编码方式并对消息进行编码,然后调用bufWriter.Flush()将消息发送出去,在该函数中一直step in进入netpoll/connection_impl.go中的Flush函数中,该函数会将已经分配的数据发送出去。

image.png 进入c.flush()中,该函数会调用sendmsg函数发送消息,而sendmsg函数是对syscall.SYS_SENDMSG的封装 image.png

image.png 通过系统调用sendmsg才真正将消息发送出去。