grpc客户端源码分析,重点分析grpc.Dial函数
函数原型分析
1 | func Dial(target string, opts ...DialOption) (*ClientConn, error) { |
首先看一下Dial函数的原型,两个参数,一个为target代表服务端的地址,一个为可变长度参数opts,为DialOption.
DialOption定义如下:
1 | type DialOption interface { |
可以看到是一个接口,定义了一个apply函数.我们可以猜测到Dial()函数中最后如何应用这些参数呢,如下:
1 | for opt := range opts { |
看看grpc中如何实现参数的生成:
1 | type funcDialOption struct { |
funcDialOption实现了apply函数,那么如何生成一个funcDialOption结构呢,使用newFuncDialOption函数生成,该函数需要定义一个函数f.例如:grpc.WithInsecure函数定义如下(所有参数相关的配置都在dialoptions.go文件中):
1 | func WithInsecure() DialOption { |
关键结构体
clientConn结构体
1 | type ClientConn struct { |
一个关键字段为dopts,代表dialOptions,其结构如下:
1 | type dialOptions struct { |
其中关键字段copts为一个transport.ConnectOptions,该结构体各字段如下:
1 | type ConnectOptions struct { |
调用流程
实际调用函数为DialContext,该函数其实就是进行clientConn结构体和dialOptions(cc.dopts)以及transport.ConnectOptions(cc.dopts.copts)的各字段初始化
调用流程如下:
- 应用配置参数
1
2
3for _, opt := range opts {
opt.apply(&cc.dopts)
} - 设置interceptor相关
1
2chainUnaryClientInterceptors(cc)
chainStreamClientInterceptors(cc)
拦截器串行执行后最终需要调用客户端实际调用rpc的函数,chainUnaryClientInterceptors的作用就是将各个拦截器串联之后放置到cc.unaryInt.最终只需调用cc.unaryInt就会将所有拦截器依次执行
- cc.dopts.copts.Dialer为实际连接服务端的代码,设置如下newProxyDialer会检测是否配置了代理(通过环境变量HTTP_PROXY或者HTTPS_PROXY),如果配置了代理则通过代理去连接.
1
2
3
4
5
6cc.dopts.copts.Dialer = newProxyDialer(
func(ctx context.Context, addr string) (net.Conn, error) {
network, addr := parseDialTarget(addr)
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
)
- cc.dopts.bs设置为backoff.Exponential,Exponetial实现了一个backoff.Strategy接口,该接口包含的函数原型如下:即通过retries次数返回一个需要等待重试的时间
1
Backoff(retries int) time.Duration
- cc.parsedTarget和cc.dopts.resolverBuilder设置grpc中的域名解析.根据target地址的设置返回不同的parsedTarget与resolverBuilder
结语
grpc.Dial()初始化各类参数,下一讲重点看clientConn的invoke函数,该函数负责实际执行rpc调用