1, 先从protobuf开始吧。
protobuf是一个高效的序列化协议,protobuf分两部分,一部分是用c++编写的protoc编译器,用于把proto文件编译为java/c#/go/c++等语言的支持序列化反序列化的对象。

protoc -I [proto文件路径] --java_out [生成的类文件路径] [proto文件路径]
生成的java类都继承自protobuf-java中的类。
以后有时间分析一下protoc的源码!!!
2, 如正题,先从源码中的helloword开始。

下面开始分析server启动过程以及一个请求的处理过程。
3, 从类名就可以看出使用的构造器设计模式,此模式特别适用于配置参数特别多的类。lombok中的@Builder注解非常方便。


从源码中可以看到,此处通过jdk中的spi加载ServerProvider,最终加载到的是NettyServerProvider类。NettyServerProvider的builderForPort返回NettyServerBuilder类。

ServerProvider里的方法不多,用于注册服务,添加拦截器,初始化server状态。
最后调用build构造出server对象。构造的过程如下:


ServerImpl维护着整个server的状态,最关键的属性如下:
1,ObjectPool<? extends Executor> executorPool:执行请求的服务方法的线程池。
2,InternalHandlerRegistry registry:服务方法主注册表,运行时不能修改。
3,HandlerRegistry fallbackRegistry:fallback注册表,运行时可以动态修改。
4,List<ServerTransportFilter> transportFilters:监听client连接ready和terminal事件。
5, ServerInterceptor[] interceptors:服务拦截器,添加顺序和执行顺序相反,服务方法执行时以装饰器设计模式嵌入执行流程。
6, InternalServer transportServer:NettyServer。
7, Collection<ServerTransport> transports:accept到的client连接。
8, DecompressorRegistry decompressorRegistry:解码器注册表。
9, CompressorRegistry compressorRegistry:编码器注册表。
10, BinaryLogProvider binlogProvider:记录log。
11, Channelz channelz:记录server,channel的状态。
12, CallTracer serverCallTracer:统计rpc调用次数。
4, server构造之后开始调用start方法启动。

此处的重点是启动传输层时传入了ServerListener,用来client连接建立或终结时从传输层回调server。

netty启动过程,重点是childHandler设置的处理client请求的pipeline。

此处的重点是ServerTransportListener,在连接建立和收到client请求数据时从传输层回调server。


洗尽铅华,发现最终处理client的handler只有NettyServerHandler而已。

从类层次上看以看到NettyServerHandler只是一个解码器decoder,具体点就是Http2ConnectionHandler,由此可见,grpc的传输协议是http2,相比与http1.1效率有很大提升,据说主要在两方面:利用IO多播使一个连接可以请求多个资源,header压缩,有空详细分析http2协议。
在NettyServerHandler的构造过程中有一句至关重要: decoder().frameListener(new FrameListener())。熟悉netty codec机制的一看就明白,就是根据http2协议,每次解析到一帧都会通知。

重点是onDataRead,onHeadersRead,onSettingsRead(tcp握手完成)顾名思义。

现在万事具备了,server已经启动,启动的过程主要在注册各种服务,添加服务拦截器等,初始化netty。
当有请求到来时,ByteToMessageDecoder会按http2协议解析帧,当收到http header时,

此处最关键的时构造了NettyServerStream,此类的作用是一个请求完成后把响应信息返回给client,最重要的属性是WriteQueue writeQueue,响应信息的队列,缓存响应信息,等全部处理完,调用flush把所有响应信息返回给client。
另一个重点是通过listener回调通知server已经收到了数据包,第一个数据包永远都是header。header都有啥?

最关键的是method,服务方法的全限定名。
现在的流程从传输层转到了server。

此处设置了一个非常关键的JumpToApplicationThreadServerStreamListener,当IO线程收齐http body之后回调。

很明显,找到请求的服务,调用它。很神奇,只收到header就开始调用服务!!!netty的io是全异步的,调用的过程中已经在接收http body啦。


装饰器设计模式的应用,套了一层又一层,此处很容易明白为啥服务拦截器添加顺序和执行顺序相反啦。
终于开始调用方法了,根据请求的方法的类型调用对应的ServerCallHandler。

以最简单又最常见的UNARY为例吧,一个请求跟随一个响应。

此处的call.request(2)至关重要,2表示应该收到2个帧,注释里解释里原因。

当收到http body时,通过一大串listener的通知,最后到达onMessage,此时的request已经经过里protobuf的反序列化,转成里请求对象。
根据tcp协议,client发送完请求后,如果不是长连接,会关闭输入端,等待响应。一旦输入端关闭,就知道所有请求都发送完啦,终于可以调用服务方法啦。
那这个UnaryServerCallListener是怎么被调用呢。
就在上面的call.request(2)这句啦,



一直循环直到读完所有数据,但是这个循环本身并不直接操作IO,它只是把CompositeReadableBuffer unprocessed里面的内容收起起来判断是否读到了需要的数据。
毫无疑问,unprocessed里的内容是IO线程读出来的。
此时FrameListener收到header之后正忙着收http body。

通知listener收到了新的数据包


把收到的数据都放到CompositeReadableBuffer unprocessed缓存起来。
一个在监听帧,然后读数据放到unprocessed里缓存,一个是循环汇总unprocessed里的数据,终于交织在一起啦。
等http body读完之后,开始通知之前建立的一大串listener。中间会经过protobuf反序列化的过程,最终到达UnaryServerCallListener.onMessage。

然后等待netty收到输入关闭事件,通过一串listener,最终到达UnaryServerCallListener.onHalfClose事件。然后真正的到了执行服务方法的时候,method.invoke(request, responseObserver);服务方法执行完,得到的结果会回调responseObserver.onNext方法,






最后触发channel的flush,返回响应信息。
5, 再缕一下整个请求处理流程。
1,server在启动netty时传入了ServerListenerImpl,用于接收client连接建立的回调通知。
2, netty通知server client连接建立时,server返回给netty的是ServerTransportListenerImpl,用于netty在收到http请求帧时回调通知server。
3,netty收到http header时创建NettyServerStream,然后通过回调通知server,server根据http header里的method属性查询请求的服务方法,给NettyServerStream又设置一个JumpToApplicationThreadServerStreamListener,用于当收到http body时回调。
4, 收到http header时开始startCall,UnaryServerCallHandler.startCall时通过调用ServerCall.request方法,进入MessageDeframer的request方法,然后一直循环检查是否收到了指定帧数的http body,UnaryServerCallHandler.startCall返回UnaryServerCallListener,返回的listener又被wrap到ServerStreamListenerImpl里,wrap之后又被set到JumpToApplicationThreadServerStreamListener里。
5,整个listener链已经设置好了,就等着netty收到http body啦。
6, netty最终收齐了http body。然后第4步中startCall里那个循环检测到http body收齐了,结束循环。
7, 然后开始沿着listener链通知下去,请求信息在protobuf反序列化之后最终到达UnaryServerCallListener.onMessage保存起来
8, 等待netty收到输入端关闭的事件,然后执行服务方法。
9, 服务方法执行完,响应信息通过ServerCallStreamObserverImpl.onNext方法protobuf序列化之后,触发netty channel的flush事件,返回响应给client。
从请求到响应的整个流程结束!!!
网友评论