netty 默认Channel管道线-添加通道处理器

本文详细介绍了Netty中管道线的管理方式,包括如何添加、删除和替换通道处理器。通过具体的源码分析,展示了不同场景下处理器上下文的管理机制。
netty Inboudn/Outbound通道Invoker:[url]http://donald-draper.iteye.com/blog/2388233[/url]
netty 异步任务-ChannelFuture:[url]http://donald-draper.iteye.com/blog/2388297[/url]
netty 管道线定义-ChannelPipeline:[url]http://donald-draper.iteye.com/blog/2388453[/url]
netty 默认Channel管道线初始化:[url]http://donald-draper.iteye.com/blog/2388613[/url]
引言:
上一篇文章我们看了默认Channel管道线初始化,先来回顾一下:
每个通道拥有一个Channel管道线;管道线用于管理,通道事件处理Handler ChannelHandler,管道线管理通道处理器的方式,为通道处理器器上下文模式,即每个通道处理器在管道中,是以通道上下文的形式存在;通道上下文关联一个通道处理器,通道上下文描述通道处理器的上下文,通道上下文拥有一个前驱和后继上下文,即通道上下文在管道线中是一个双向链表,通道处理器上下文通过inbound和oubound两个布尔标志,判断通道处理器是inbound还是outbound。上下文链表的头部为HeadContext,尾部为TailContext。
头部上下文HeadContext的outbound的相关操作,直接委托给管道线所属通道的unsafe(Native API),inbound事件直接触发通道处理器上下文的相关事件,以便通道处理器上下文关联的通道Handler处理相关事件,但读操作实际是通过Channel读取。HeadContext的通道注册方法channelRegistered,主要是执行通道处理器添加回调任务链中的任务。处理器添加回调任务主要是触发触发上下文关联通道处理器的handlerAdded事件,更新上下文状态为添加完毕状态,如果过程中有异常发生,则移除通道上下文。channelUnregistered方法,主要是在通道从选择器反注册时,清空管道线程的通道处理器上下文,并触发上下文关联的通道处理器handlerRemoved事件,更新上下文状态为已移除。
Channel的管道线的通道处理器上下文链的尾部TailContext是一个傀儡,不同于尾部上下文,头部上下文,在处理inbound事件时,触发通道处理器上下文相关的方法,在处理outbound事件时,委托给管道线关联的Channle的内部unsafe。
默认Channel管道实现内部有两个回调任务PendingHandlerAdded/RemovedTask,一个是添加通道处理器上下文回调任务,一个是移除通道上下文回调任务,主要是触发上下文关联通道处理器的处理器添加移除事件,并更新相应的上下文状态为已添加或已移除。
管道构造,主要是检查管道通道是否为空,初始化管道上下文链的头部与尾部上下文。
netty通道处理器上下文可以说,是Mina中Hanlder和过滤器的集合,整合两者功能,管道线有点Mina过滤链的意味,HeadContext相当于Mina过滤链的头部过滤器,TailContext相当于Mina过滤链的尾部过滤器。
今天我们来看管道线如果添加和移除及替换通道处理器。
先来看添加通道处理器:
@Override
public final ChannelPipeline addFirst(String name, ChannelHandler handler) {
//根据事件执行器,处理器名及处理器,添加处理器到管道
return addFirst(null, name, handler);
}
//根据事件执行器,处理器名及处理器,添加处理器到管道
@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//检查处理器是否为共享模式
checkMultiplicity(handler);
//检查处理器名
name = filterName(name, handler);
//根据事件执行器,处理器名,及处理器,构造处理器上下文
newCtx = newContext(group, name, handler);
//添加处理上限文到管道上下文链
addFirst0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
//如果通道没有到事件循环,上下文添加到管道,需要添加一个回调任务,
//当通道注册到事件循环时,触发通道处理器的handlerAdded事件
if (!registered) {
newCtx.setAddPending();//更新上下文状态为正在添加
//创建添加通道处理器回调任务,并将任务添加管道的回调任务链中
callHandlerCallbackLater(newCtx, true);
return this;
}
//获取上下文的时间执行器
EventExecutor executor = newCtx.executor();
//如果事件执行器不在事件循环中
if (!executor.inEventLoop()) {
newCtx.setAddPending();//更新上下文状态为正在添加
//创建一个线程,用于调用通道处理器的handlerAdded事件方法,及更新上下文状态为已添加
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
//调用通道处理器的handlerAdded事件方法,更新上下文状态为已添加
callHandlerAdded0(newCtx);
return this;
}

添加通道处理器方法有一下几点要看:
1.
 //检查处理器是否为共享模式
checkMultiplicity(handler);


//检查处理器是否为共享模式
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
//如果非共享,且已添加,则抛出异常
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}

2.
//检查处理器名
name = filterName(name, handler);


private String filterName(String name, ChannelHandler handler) {
if (name == null) {
//如果处理器名为空,则产生处理器对应的名称
return generateName(handler);
}
//否者检查处理器名是否与管道内的处理器名是否相同
checkDuplicateName(name);
return name;
}

private void checkDuplicateName(String name) {
if (context0(name) != null) {
//如果处理器名已经存在,则抛出异常
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}

private AbstractChannelHandlerContext context0(String name) {
AbstractChannelHandlerContext context = head.next;
//检查管道内的处理器上下文是否存在与name相同的上下文
while (context != tail) {
if (context.name().equals(name)) {
return context;
}
context = context.next;
}
return null;
}

3.
//根据事件执行器,处理器名,及处理器,构造处理器上下文
newCtx = newContext(group, name, handler);

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

//这个上一篇文章已说,我们把它,贴过了:
final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
private final ChannelHandler handler;
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
@Override
public ChannelHandler handler() {
return handler;
}
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
}

默认的通达处理器关联一个通道处理器ChannelHandler。
//AbstractChannelHandlerContext
abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
implements ChannelHandlerContext, ResourceLeakHint {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannelHandlerContext.class);
volatile AbstractChannelHandlerContext next;//通道处理器上下文后继
volatile AbstractChannelHandlerContext prev;//通道处理器上下文前驱
/**
* {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} is about to be called.
*/
private static final int ADD_PENDING = 1;//添加状态
/**
* {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called.
*/
private static final int ADD_COMPLETE = 2;//添加完成状态
/**
* {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
*/
private static final int REMOVE_COMPLETE = 3;//移除完成状态
/**
* Neither {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}
* nor {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
*/
private static final int INIT = 0;//初始化状态

private final boolean inbound;//是否为inbound处理器上下文
private final boolean outbound;//是否为outbound处理器上下文
private final DefaultChannelPipeline pipeline;//上下文关联管道线
private final String name;//处理器上下文名
private final boolean ordered;//事件执行器是否为顺序执行器

// Will be set to null if no child executor should be used, otherwise it will be set to the
// child executor.
final EventExecutor executor;//事件执行器
private ChannelFuture succeededFuture;

// Lazily instantiated tasks used to trigger events to a handler with different executor.
// There is no need to make this volatile as at worse it will just create a few more instances then needed.
private Runnable invokeChannelReadCompleteTask;
private Runnable invokeReadTask;
private Runnable invokeChannelWritableStateChangedTask;
private Runnable invokeFlushTask;

private volatile int handlerState = INIT;
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
}

4.
//添加处理器上限文到管道上下文链头
private void addFirst0(AbstractChannelHandlerContext newCtx) {
//获取上下文链头
AbstractChannelHandlerContext nextCtx = head.next;
newCtx.prev = head;
newCtx.next = nextCtx;
head.next = newCtx;
nextCtx.prev = newCtx;
}

5.
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
//如果通道没有注册到事件循环,上下文添加到管道,需要添加一个回调任务,
//当通道注册到事件循环时,触发通道处理器的handlerAdded事件
if (!registered) {
newCtx.setAddPending();//更新上下文状态为正在添加
//创建添加通道处理器回调任务,并将任务添加管道的回调任务链中
callHandlerCallbackLater(newCtx, true);
return this;
}

先来看状态更新
//AbstractChannelHandlerContext
final void setAddPending() {
boolean updated = HANDLER_STATE_UPDATER.compareAndSet(this, INIT, ADD_PENDING);
assert updated; // This should always be true as it MUST be called before setAddComplete() or setRemoved().
}


 //创建添加通道处理器回调任务,并将任务添加管道的回调任务链中
callHandlerCallbackLater(newCtx, true);


private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// Find the tail of the linked-list.
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}

方法很容易理解,主要是根据added参数,确定是添加还是移除任务,并创建相应的回调任务,添加到管道的回调任务列表

这一贴出上一篇的添加和移除回调任务以便理解回调任务的作用:
//PendingHandlerAddedTask 处理器添加回调任务
private final class PendingHandlerAddedTask extends PendingHandlerCallback {

PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
super(ctx);
}
@Override
public void run() {
callHandlerAdded0(ctx);
}
//在通道注册到选择器时,调用
@Override
void execute() {
//获取通道上下文的事件执行器
EventExecutor executor = ctx.executor();
if (executor.inEventLoop()) {
//如果当前执行器在事务循环中直接委托为callHandlerAdded0
callHandlerAdded0(ctx);
} else {
try {
//否则执行器,直接执行处理器添加回调任务
executor.execute(this);
} catch (RejectedExecutionException e) {
if (logger.isWarnEnabled()) {
logger.warn(
"Can't invoke handlerAdded() as the EventExecutor {} rejected it, removing handler {}.",
executor, ctx.name(), e);
}
//异常则移除通道处理器上下文
remove0(ctx);
ctx.setRemoved();//标志为移除
}
}
}
}

6.
 //获取上下文的时间执行器
EventExecutor executor = newCtx.executor();
//如果事件执行器不在事件循环中
if (!executor.inEventLoop()) {
newCtx.setAddPending();//更新上下文状态为正在添加
//创建一个线程,用于调用通道处理器的handlerAdded事件方法,及更新上下文状态为已添加
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}


private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
//触发上下文关联通道处理器的handlerAdded事件
ctx.handler().handlerAdded(ctx);
//更新上下文状态为添加完毕
ctx.setAddComplete();
} catch (Throwable t) {
//异常发生移除通道上下文
boolean removed = false;
try {
//移除上下文
remove0(ctx);
try {
//触发通道处理器的handlerRemoved事件
ctx.handler().handlerRemoved(ctx);
} finally {
ctx.setRemoved();//标记为已移除
}
removed = true;
} catch (Throwable t2) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to remove a handler: " + ctx.name(), t2);
}
}

if (removed) {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() +
".handlerAdded() has thrown an exception; removed.", t));
} else {
fireExceptionCaught(new ChannelPipelineException(
ctx.handler().getClass().getName() +
".handlerAdded() has thrown an exception; also failed to remove.", t));
}
}
}


7.
 //调用通道处理器的handlerAdded事件方法,更新上下文状态为已添加
callHandlerAdded0(newCtx);

从上面可以看出,添加通道处理器,首次检查通道处理器是否为共享模式,如果非共享,且已添加,则抛出异常;检查通道处理器名在管道内,是否存在对应通道处理器上下文,已存在抛出异常;根据事件执行器,处理器名,及处理器,构造处理器上下文;
添加处理器上限文到管道上下文链;如果通道没有注册到事件循环,上下文添加到管道时,创建添加通道处理器回调任务,并将任务添加管道的回调任务链中,当通道注册到事件循环时,触发通道处理器的handlerAdded事件,已注册则创建一个线程,
用于调用通道处理器的handlerAdded事件方法,及更新上下文状态为已添加,并交由事件执行器执行;最好调用callHandlerAdded0方法,确保调用通道处理器的handlerAdded事件方法,更新上下文状态为已添加。

再来看添加处理器器到管道尾部操作:
@Override
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return addLast(null, name, handler);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
//与addFirst方法不同点,添加通道处理器上下文到管道上下文链尾
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}

//与addFirst方法不同点,添加通道处理器上下文到管道上下文链尾
 addLast0(newCtx);

private void addLast0(AbstractChannelHandlerContext newCtx) {
//获取上下文链尾
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}



再看添加处理器到指定处理前面
 @Override
public final ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
return addBefore(null, baseName, name, handler);
}

@Override
public final ChannelPipeline addBefore(
EventExecutorGroup group, String baseName, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
final AbstractChannelHandlerContext ctx;
synchronized (this) {
checkMultiplicity(handler);
name = filterName(name, handler);
//获取指定处理器的上下文
ctx = getContextOrDie(baseName);
newCtx = newContext(group, name, handler);
//添加处理器到指定上下文的前面
addBefore0(ctx, newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}


//获取指定处理器的上下文
ctx = getContextOrDie(baseName);


private AbstractChannelHandlerContext getContextOrDie(String name) {
//获取name对应的通道处理器上下文
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
if (ctx == null) {
throw new NoSuchElementException(name);
} else {
return ctx;
}
}

@Override
public final ChannelHandlerContext context(String name) {
if (name == null) {
throw new NullPointerException("name");
}
//委托给context0
return context0(name);
}


 private AbstractChannelHandlerContext context0(String name) {
AbstractChannelHandlerContext context = head.next;
//遍历管道上下文链,找到name对应的上下文
while (context != tail) {
if (context.name().equals(name)) {
return context;
}
context = context.next;
}
return null;
}



 //添加处理器到指定上下文的前面
addBefore0(ctx, newCtx);


   private static void addBefore0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
newCtx.prev = ctx.prev;
newCtx.next = ctx;
ctx.prev.next = newCtx;
ctx.prev = newCtx;
}


再来看addAfter操作:
  @Override
public final ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler) {
return addAfter(null, baseName, name, handler);
}

@Override
public final ChannelPipeline addAfter(
EventExecutorGroup group, String baseName, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
final AbstractChannelHandlerContext ctx;

synchronized (this) {
checkMultiplicity(handler);
name = filterName(name, handler);
//获取baseName对应的上下文
ctx = getContextOrDie(baseName);

newCtx = newContext(group, name, handler);
//不同点,在这
addAfter0(ctx, newCtx);

// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we remove the context from the pipeline and add a task that will call
// ChannelHandler.handlerRemoved(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}

private static void addAfter0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
newCtx.prev = ctx;
newCtx.next = ctx.next;
ctx.next.prev = newCtx;
ctx.next = newCtx;
}


[size=medium][b]
总结:[/b][/size]
[color=blue]添加通道处理器到管道头部,首次检查通道处理器是否为共享模式,如果非共享,且已添加,则抛出异常;检查通道处理器名在管道内,是否存在对应通道处理器上下文,已存在抛出异常;根据事件执行器,处理器名,及处理器,构造处理器上下文;添加处理器上限文到管道上下文链头;如果通道没有注册到事件循环,上下文添加到管道时,创建添加通道处理器回调任务,并将任务添加管道的回调任务链中,当通道注册到事件循环时,触发通道处理器的handlerAdded事件,已注册则创建一个线程,用于调用通道处理器的handlerAdded事件方法,及更新上下文状态为已添加,并交由事件执行器执行;最好调用callHandlerAdded0方法,确保调用通道处理器的handlerAdded事件方法,更新上下文状态为已添加。其他last(添加到管道尾部),before(添加指定上下文的前面),after(添加指定上下文的后面)操作,基本上与addfirst思路基本相同,不同的是添加到管道上下文链的位置。[/color]
附:
以下方法很简单,了解一下即可
//添加多个通道处理器到管道的头部
@Override
public final ChannelPipeline addFirst(ChannelHandler... handlers) {
return addFirst(null, handlers);
}

@Override
public final ChannelPipeline addFirst(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
//检查处理器数组长度
if (handlers.length == 0 || handlers[0] == null) {
return this;
}

int size;
//检查处理器数组中的元素是否为null
for (size = 1; size < handlers.length; size ++) {
if (handlers[size] == null) {
break;
}
}
//遍历处理器数组,添加处理器到管道
for (int i = size - 1; i >= 0; i --) {
ChannelHandler h = handlers[i];
addFirst(executor, null, h);
}

return this;
}
//添加多个通道处理器到管道的尾部
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}

@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}

for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}

return this;
}
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Unexpected HTTP/1.x request: POST /user/login at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:109) ~[grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:317) ~[grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:247) ~[grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:453) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:529) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:468) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [grpc-netty-shaded-1.54.0.jar:1.54.0] at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [grpc-netty-shaded-1.54.0.jar:1.54.0] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]
最新发布
08-26
<think>好的,我现在需要解决用户遇到的gRPC错误:“io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: Unexpected HTTP/1.x request: POST /user/login”。首先,我需要理解这个错误的原因。 首先看错误信息,核心是“Unexpected HTTP/1.x request”,这说明gRPC服务器期望接收HTTP/2请求,但实际收到了HTTP/1.x的请求。gRPC默认使用HTTP/2协议,而用户可能用HTTP/1.x的客户端(比如普通的REST客户端)访问了gRPC服务器的端口,导致协议不匹配。 接下来,用户的问题可能是在同一个端口上同时运行了gRPC服务和REST服务,或者客户端错误地用HTTP/1.x的方式调用了gRPC接口。例如,用户可能尝试用POST请求访问gRPC服务器的某个路径(如/user/login),而gRPC服务器只处理HTTP/2的gRPC请求,不识别HTTP/1.x的请求,因此抛出异常。 可能的原因包括: 1. 客户端错误地使用HTTP/1.x协议(如curl、Postman的普通请求)调用了gRPC服务端的端口,而gRPC服务端只监听HTTP/2请求。 2. 服务端配置错误,比如在同一个端口同时启用了HTTP/1.x(如REST)和HTTP/2(如gRPC),但未正确配置协议协商(如ALPN)。 3. 如果使用Netty作为传输层,可能未正确配置HTTP/2的支持,导致无法处理混合协议。 解决方法需要根据具体场景来调整: **情况1:客户端误用HTTP/1.x调用gRPC服务** gRPC客户端需要使用gRPC库(如gRPC的Java客户端、gRPC的Python客户端等),这些客户端会自动使用HTTP/2协议。如果用户用普通的HTTP客户端(如Postman发送POST到/user/login),会触发此错误。此时需要确认客户端是否使用正确的gRPC客户端库,而不是REST客户端。 **情况2:服务端同一端口混合HTTP/1.x和HTTP/2** 如果需要在同一个端口同时提供REST(HTTP/1.x)和gRPC(HTTP/2)服务,需要配置协议协商(ALPN,Application-Layer Protocol Negotiation)。Netty支持通过`Http2FrameCodec`和`HttpServerCodec`结合`ApplicationProtocolNegotiationHandler`来处理不同协议的请求。例如,对于HTTP/1.x请求路由到REST控制器,HTTP/2请求路由到gRPC服务。 **情况3:服务端未正确配置HTTP/2** 检查gRPC服务器的启动配置,确保使用了支持HTTP/2的传输层。例如,在Netty中,gRPC默认应该使用HTTP/2,但如果有其他拦截器或处理器干扰,可能导致协议协商失败。需要确认服务器管道ChannelPipeline)中是否正确添加了HTTP/2相关的处理器。 **示例解决步骤(以Spring Boot集成gRPC为例):** 如果用户希望在同一个端口同时支持REST和gRPC,需要配置ALPN。例如,使用`gRPC Spring Boot Starter`时,可能需要额外配置,或者手动设置NettyChannelPipeline。但更常见的做法是分开端口:gRPC使用一个端口(如50051),REST使用另一个端口(如8080),避免协议冲突。 **验证方法:** 使用gRPC客户端(如bloomrpc、grpcurl)调用服务,确认是否正常。如果正常,说明问题出在客户端使用了错误的协议;如果仍然报错,需要检查服务端配置。 总结,该错误的根本原因是HTTP版本不匹配,解决关键是确保客户端使用HTTP/2协议访问gRPC服务,或服务端正确配置协议协商以支持混合协议。 </think>这个错误的核心原因是 **gRPC 服务器期望接收 HTTP/2 请求,但实际收到了 HTTP/1.x 请求**。gRPC 基于 HTTP/2 协议设计,而客户端可能误将 HTTP/1.x(如 REST 风格的 POST 请求)发送到了 gRPC 服务的端口,导致协议不兼容。 ### **具体原因分析** gRPC 服务器(如基于 Netty 的实现)默认监听 HTTP/2 协议。当客户端使用 HTTP/1.x 协议(例如用 Postman 发送普通 POST 请求到 `/user/login`)访问时,服务器会检测到协议不匹配,并抛出 `Unexpected HTTP/1.x request` 异常。 ### **解决方法** 根据具体场景选择以下方案: #### **方案 1:确保客户端使用 gRPC 协议调用** gRPC 必须通过 **gRPC 客户端库**(如 Java 的 `io.grpc`、Python 的 `grpcio` 等)调用,这些库会自动使用 HTTP/2 协议。 - **错误示例**:用 `curl` 或 Postman 发送 `POST /user/login`(HTTP/1.x)。 - **正确示例**:使用 gRPC 客户端(如 `grpcurl` 工具或自定义 gRPC 客户端)调用服务。 #### **方案 2:同一端口混合支持 HTTP/1.x(REST)和 HTTP/2(gRPC)** 若需在同一端口同时提供 REST(HTTP/1.x)和 gRPC(HTTP/2)服务,需通过 **ALPN(应用层协议协商)** 配置协议兼容。具体步骤如下(以 Netty 为例): 1. **添加 ALPN 依赖**(针对 Java 8 及以下,Java 9+ 内置支持): ```xml <!-- 仅 Java 8 需要,用于 ALPN 支持 --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-tcnative-boringssl-static</artifactId> <version>2.0.54.Final</version> </dependency> ``` 2. **配置 Netty 服务器支持协议协商**: 在启动 gRPC 服务器时,通过 `NettyServerBuilder` 自定义管道添加 `ApplicationProtocolNegotiationHandler` 处理 HTTP/1.x 和 HTTP/2 的协商: ```java Server server = NettyServerBuilder.forPort(8080) .addService(new UserGrpcService()) .channelType(NioServerSocketChannel.class) .sslContext(/* 可选:SSL 上下文(若启用 HTTPS) */) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); // 支持 HTTP/1.x 和 HTTP/2 协商 p.addLast(new ApplicationProtocolNegotiationHandler("h2") { @Override protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { if ("h2".equals(protocol)) { // HTTP/2 协议:添加 gRPC 处理器 p.addLast(GrpcSslContexts.selectApplicationProtocolHandler(ctx.channel())); } else if ("http/1.1".equals(protocol)) { // HTTP/1.x 协议:添加 REST 处理器(如 Spring MVC) p.addLast(new HttpServerCodec()); p.addLast(new HttpObjectAggregator(1024 * 1024)); p.addLast(new RestRequestHandler()); // 自定义 REST 处理逻辑 } else { throw new IllegalStateException("Unsupported protocol: " + protocol); } } }); } }) .build(); server.start(); ``` #### **方案 3:分离 REST 和 gRPC 端口** 更简单的方式是为 REST 和 gRPC 分配不同端口(如 REST 用 8080,gRPC 用 50051),避免协议冲突。这是最常见的生产环境配置方式。 ### **验证方法** - 使用 `grpcurl` 工具调用 gRPC 服务(验证 HTTP/2 正常): ```bash grpcurl -plaintext localhost:50051 com.example.user.UserService/GetUser ``` - 使用 Postman 发送 HTTP/1.x 请求到 REST 端口(验证 REST 正常)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值