前言 grpc发送过程总结起来可以分为两步,一步是写入内存,也就是写入ByteBuf,写入ByteBuf的目的是因为Java NIO对于数据有要求,第二步就是通过netty发送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static <ReqT, RespT> void asyncUnaryRequestCall ( ClientCall<ReqT, RespT> call, ReqT req, StartableListener<RespT> responseListener) { startCall(call, responseListener); try { call.sendMessage(req); call.halfClose(); } catch (RuntimeException e) { throw cancelThrow(call, e); } catch (Error e) { throw cancelThrow(call, e); } }
call.sendMessage(req);这句命令就是处理我们的数据,而在正式处理我们发送的数据之前,会调用startCall(call, responseListener);这个命令,这个命令其中一个功能就是写入http2头部,写入头部的逻辑最后会调用下面的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private void writeHeadersInternal (Metadata headers, byte [] requestPayload) { AsciiString defaultPath = (AsciiString)NettyClientStream.methodDescriptorAccessor.geRawMethodName(NettyClientStream.this .method); if (defaultPath == null ) { defaultPath = new AsciiString ("/" + NettyClientStream.this .method.getFullMethodName()); NettyClientStream.methodDescriptorAccessor.setRawMethodName(NettyClientStream.this .method, defaultPath); } boolean get = requestPayload != null ; AsciiString httpMethod; if (get) { defaultPath = new AsciiString (defaultPath + "?" + BaseEncoding.base64().encode(requestPayload)); httpMethod = Utils.HTTP_GET_METHOD; } else { httpMethod = Utils.HTTP_METHOD; } Http2Headers http2Headers = Utils.convertClientHeaders(headers, NettyClientStream.this .scheme, defaultPath, NettyClientStream.this .authority, httpMethod, NettyClientStream.this .userAgent); ChannelFutureListener failureListener = new ChannelFutureListener () { public void operationComplete (ChannelFuture future) throws Exception { if (!future.isSuccess()) { Status s = NettyClientStream.this .transportState().handler.getLifecycleManager().getShutdownStatus(); if (s == null ) { s = NettyClientStream.this .transportState().statusFromFailedFuture(future); } if (NettyClientStream.this .transportState().isNonExistent()) { NettyClientStream.this .transportState().transportReportStatus(s, RpcProgress.MISCARRIED, true , new Metadata ()); } else { NettyClientStream.this .transportState().transportReportStatus(s, RpcProgress.PROCESSED, true , new Metadata ()); } } } }; NettyClientStream.this .writeQueue.enqueue(new CreateStreamCommand (http2Headers, NettyClientStream.this .transportState(), NettyClientStream.this .shouldBeCountedForInUse(), get), !NettyClientStream.this .method.getType().clientSendsOneMessage() || get).addListener(failureListener); }
该方法就是创建一个CreateStreamCommand,并放入队列中,消息体后续也会加入这个队列,需要注意,如果这里是多元流或者没有消息体,那么将会直接flsuh,即会直接发送,否则会等到消息体加入队列之后,或者主动调用flush方法。
写入内存 这一步总结一下就是数据写入ByteBuf的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 final void sendMessage (final ReqT message) { State savedState = state; if (savedState.passThrough) { savedState.winningSubstream.stream.writeMessage(method.streamRequest(message)); return ; } class SendMessageEntry implements BufferEntry { @Override public void runWith (Substream substream) { substream.stream.writeMessage(method.streamRequest(message)); substream.stream.flush(); } } delayOrExecute(new SendMessageEntry ()); }
这里面的runwith方法,其中substream.stream.writeMessage(method.streamRequest(message));就是将数据写入内存,而substream.stream.flush();就可以理解为发送数据
1 2 3 4 5 6 public void writeTo (OutputStream output) throws IOException { int bufferSize = CodedOutputStream.computePreferredBufferSize(this .getSerializedSize()); CodedOutputStream codedOutput = CodedOutputStream.newInstance(output, bufferSize); this .writeTo(codedOutput); codedOutput.flush(); }
这个方法是将消息转为protobuf,使用protobuf也是grpc效率高的一个原因,AbstractMessageLite也就是grpc生成的消息类的父类,所以在这里调用writeTo方法的其实就是grpc根据我们定义数据生成的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void writeRaw (byte [] b, int off, int len) { while (len > 0 ) { if (buffer != null && buffer.writableBytes() == 0 ) { commitToSink(false , false ); } if (buffer == null ) { buffer = bufferAllocator.allocate(len); } int toWrite = min(len, buffer.writableBytes()); buffer.write(b, off, toWrite); off += toWrite; len -= toWrite; } }
可以看到写入内存实际上就是写入MessageFramer类的buffer变量中,后续发送时,就是发送buffer变量
1 2 3 4 5 6 public ByteBuf writeBytes (byte [] src, int srcIndex, int length) { this .ensureWritable(length); this .setBytes(this .writerIndex, src, srcIndex, length); this .writerIndex += length; return this ; }
后面是调用了netty ByteBuf的writeBytes方法,就是将数据写入到ByteBuf
1 public native void copyMemory (Object var1, long var2, Object var4, long var5, long var7) ;
调用到这里其实已经是netty的范围了,ByteBuf最后会调用到一个本地方法,copyMemory的五个参数分别是
srcBase:源对象的基地址,即要复制数据的起始位置。
srcOffset:源对象的偏移量,即要复制数据的起始偏移量。
destBase:目标对象的基地址,即要复制数据的目标位置。
destOffset:目标对象的偏移量,即要复制数据的目标偏移量。
length:要复制的数据长度。
发送数据 从上面io.grpc.internal.RetriableStream#sendMessage方法的runwith方法中的substream.stream.flush();,就是开启刷新步骤,也就是发送流程
1 2 3 4 5 6 private void commitToSink (boolean endOfStream, boolean flush) { WritableBuffer buf = buffer; buffer = null ; sink.deliverFrame(buf, endOfStream, flush, messagesBuffered); messagesBuffered = 0 ; }
这个方法将上面提到的的buffer写入sink,就是正式发送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void writeFrameInternal (WritableBuffer frame, boolean endOfStream, boolean flush, final int numMessages) { Preconditions.checkArgument(numMessages >= 0 ); ByteBuf bytebuf = frame == null ? Unpooled.EMPTY_BUFFER : ((NettyWritableBuffer)frame).bytebuf().touch(); final int numBytes = bytebuf.readableBytes(); if (numBytes > 0 ) { NettyClientStream.this .onSendingBytes(numBytes); NettyClientStream.this .writeQueue.enqueue(new SendGrpcFrameCommand (NettyClientStream.this .transportState(), bytebuf, endOfStream), flush).addListener(new ChannelFutureListener () { public void operationComplete (ChannelFuture future) throws Exception { if (future.isSuccess() && NettyClientStream.this .transportState().http2Stream() != null ) { NettyClientStream.this .transportState().onSentBytes(numBytes); NettyClientStream.this .getTransportTracer().reportMessageSent(numMessages); } } }); } else { NettyClientStream.this .writeQueue.enqueue(new SendGrpcFrameCommand (NettyClientStream.this .transportState(), bytebuf, endOfStream), flush); } }
这里将消息包装成SendGrpcFrameCommand放入队列中进行处理,这里的队列就是最上面提到过http2header也加入的队列
1 2 3 4 5 6 7 8 9 10 11 ChannelFuture enqueue (QueuedCommand command, boolean flush) { Preconditions.checkArgument(command.promise() == null , "promise must not be set on command" ); ChannelPromise promise = this .channel.newPromise(); command.promise(promise); this .queue.add(command); if (flush) { this .scheduleFlush(); } return promise; }
这个方法就是将消息放入队列并处理的方法,在上面http2header添加的时候,flush要根据条件判断,但是在添加我们的消息的时候,flush就是true,直接调用刷新方法,而this.scheduleFlush();命令会从队列中取出封装好的消息体,并调用他们的run方法
1 2 3 public final void run (Channel channel) {channel.write(this , this .promise); }
1 2 3 public final void run (Channel channel) {channel.write(this , this .promise); }
无论是http2header的CreateStreamCommand,还是我们写入消息的SendGrpcFrameCommand,他们的run方法都是一样的,都是调用netty的channel的write方法进行发送,至于channel的flush方法实在前面提到的scheduleFlush方法中执行的。我们知道channel需要添加channelHandler才能处理消息,而grpc实现了一个NettyClientHandler,下面就是NettyClientHandler的写入方法的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void write (ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof CreateStreamCommand) { this .createStream((CreateStreamCommand)msg, promise); } else if (msg instanceof SendGrpcFrameCommand) { this .sendGrpcFrame(ctx, (SendGrpcFrameCommand)msg, promise); } else if (msg instanceof CancelClientStreamCommand) { this .cancelStream(ctx, (CancelClientStreamCommand)msg, promise); } else if (msg instanceof SendPingCommand) { this .sendPingFrame(ctx, (SendPingCommand)msg, promise); } else if (msg instanceof GracefulCloseCommand) { this .gracefulClose(ctx, (GracefulCloseCommand)msg, promise); } else if (msg instanceof ForcefulCloseCommand) { this .forcefulClose(ctx, (ForcefulCloseCommand)msg, promise); } else { if (msg != NOOP_MESSAGE) { throw new AssertionError ("Write called for unexpected type: " + msg.getClass().getName()); } ctx.write(Unpooled.EMPTY_BUFFER, promise); } }
总结 该篇讲到了我们自己写入的消息是如何发送出去的,但是从写入内存到发送数据的步骤,严格来讲这其实属于netty的范畴,因为netty写入数据也是写入内存到发送数据,只是grpc按照需求实现了一遍,包括http2header,这里对于http2header其实并没有讲到很细,但是其实http2header也经历了写入内存到发送数据的步骤,只是http2header的写入使用的就是netty默认的逻辑,总结起来就是消息体的写入,grpc有自己的实现,而对于http2header的写入,使用的就是netty默认的逻辑