之前我们分析Netty服务端的启动和连接的过程,接下来我们分析下Netty中Server端消息的读取和写入流程。
我们首先看看Server端消息读取过程。
通过前几节的分析,Netty中Server端通过worker线程组的线程,在不断监听轮询读取事件,主要的处理在processSelectedKey
中,
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop == this) {
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
}
return;
}
try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
可以看到这里最后通过unsafe.read()
来实现:
public interface NioUnsafe extends Unsafe {
SelectableChannel ch();
void finishConnect();
void read();
void forceFlush();
}
主要有这两个实现,在之前也说过NioByteUnsafe是用来读取SocketChannel的数据,NioMessageUnsafe用来读取ServerSocketChannel数据
,这里读取客户端数据就是通过NioByteUnsafe
来进行读取:
// NioByteUnsafe
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
而真正读取底层网络数据是通过
// NioSocketChannel.java
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
这里在NioSocketChannel
调用持有的底层网络Socket将网络数据读取到ByteBuf 中,这样就将底层网络数据读取到Netty中,读取完数据之后,就会触发ChannelHandler事件。在unsafe中读取完网路数据之后就会触发pipeline.fireChannelRead
。然后依次向后执行ChannelHandler相关事件,到这里数据就读取完成。
当我们在Netty中需要写入相关消息,一般都是通过Channel写入,典型的是在Handler中处理,代码如下:
class HelloWorldHandler extends ChannelInboundHandlerAdapter
{
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String resp = "Hello world";
ctx.channel().writeAndFlush(resp);
}
}
当我们使用channel进行写入的时候,底层是怎么处理的呢 ? 我们随着channel.writeAndFlush进入:
//调用实际上是AbstractChannel中的writeAndFlush,可以明显的看到,调用的是pipline的的写入方法
public ChannelFuture writeAndFlush(Object msg) {
return pipeline.writeAndFlush(msg);
}
在pipline中的是调用tail节点的writeAndFlush
实际处理逻辑如下:
public final ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
return tail.writeAndFlush(msg, promise);
}
实际调用是在: AbstractChannelHandlerContext
private void write(Object msg, boolean flush, ChannelPromise promise) {
ObjectUtil.checkNotNull(msg, "msg");
try {
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);//方便后面内存释放,如果ByteBuf相关会进行释放计数,普通消息不会
return;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);//方便后面内存释放
throw e;
}
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);//找到下一个outoundhandler
final Object m = pipeline.touch(msg, next); // 以上面例子来说,这里返回的就是resp本身
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {//是否在IO线程中
if (flush) {
next.invokeWriteAndFlush(m, promise); //在IO线程中执行后面handler的write逻辑
} else {
next.invokeWrite(m, promise);
}
} else {//不在IO线程中提交任务给IO线程去处理
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
task.cancel();
}
}
}
正常情况,最后分别调用HeadContext的write
和flush
方法,我们跟中在DefaultPipline
中的HeadContext
,
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
unsafe.write(msg, promise);
}
public void flush(ChannelHandlerContext ctx) {
unsafe.flush();
}
底层调用了unsafe的write方法,进一步跟踪代码:
public final void write(Object msg, ChannelPromise promise) {
assertEventLoop(); // 写入一定是在IO线程中
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
ReferenceCountUtil.release(msg); //如果是可回收的类型消息,如ByteBuffer,这里计算减一,方便后面回收内存资源
return;
}
int size;
try {
msg = filterOutboundMessage(msg);
size = pipeline.estimatorHandle().size(msg);
if (size < 0) {
size = 0;
}
} catch (Throwable t) {
safeSetFailure(promise, t);
ReferenceCountUtil.release(msg);
return;
}
outboundBuffer.addMessage(msg, size, promise);
}
@Override
public final void flush() {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
return;
}
outboundBuffer.addFlush();
flush0();
}
在filterOutboundMessage
中对msg进行了封装,完成堆外内存的转换:
protected final Object filterOutboundMessage(Object msg) {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (buf.isDirect()) {
return msg;
}
return newDirectBuffer(buf);
}
if (msg instanceof FileRegion) {
return msg;
}
throw new UnsupportedOperationException(
"unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}
从上面可以看出,如果不是ByteBuf
类型,发送会抛出异常,也就是我们经常会在服务端handler里面一般会加入编码器和解码器
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception
{
socketChannel.pipeline()
.addLast("encoder",new StringEncoder())
.addLast("decoder",new StringDecoder())
.addLast("hello world hanlder",new HelloWorldHandler());
}
});
如果不加入encoder和decoder,则发送消息:
bootstrap.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception
{
socketChannel.pipeline()
//.addLast("encoder",new StringEncoder())
//.addLast("decoder",new StringDecoder())
.addLast("hello world hanlder",new HelloWorldHandler());
}
});
// ------------华丽的分割线----------------------------------------
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
System.out.println("channel Active and write back......");
String resp = "Hello world";
ChannelFuture future = ctx.channel().writeAndFlush(resp);
future.sync();
System.out.println("success:"+future.isSuccess());
}
因此必须在handler中添加编解码器,将发送的消息缓缓为ByteBuf
类型。例如在StringEncoder
中encode
方法:
protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
if (msg.length() == 0) {
return;
}
out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset)); // 将msg转换为ByteBuf类型
}
回到之前的处理,
outboundBuffer.addMessage(msg, size, promise);
这里实际上是完成了将消息加入到了buffer缓冲里面:
//形成了一个链表结构
public void addMessage(Object msg, int size, ChannelPromise promise) {
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
if (tailEntry == null) {
flushedEntry = null;
} else {
Entry tail = tailEntry;
tail.next = entry;
}
tailEntry = entry;
if (unflushedEntry == null) {
unflushedEntry = entry;
}
incrementPendingOutboundBytes(entry.pendingSize, false);
}
然后调用 flush
方法,完成实际写入,最终在NioSocketChannel
的doWrite
完成实际的写入, 而最终是关联到channel里面的socket完成了实际的写入。
我们看下在Netty中ChannelHandler
的类型,主要分为Inbound和Outbound两种:
可以看到,这里不管Inbound和Outbound都继承了ChannelHandlerAdapter
,那这个ChannelHandlerAdapter
具体是干嘛的呢 ?说白了,这个ChannelHandlerAdapter
默认实现了ChannelHandler
中一些不重要的方法(都是空实现)
,这样我们去实现的时候,不用在去空实现一些无关紧要的方法。
我们看下Netty中一些常见的Handler:
SimpleChannelInboundHandler
实现SimpleChannelInboundHandler
的channelRead0
后,不用编程释放内存,在SimpleChannelInboundHandler.channelRead
实现了内存的释放:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}
ChannelDuplexHandler
这是一个双通道的ChannelHandler
,同时继承Inbound和Outbound:
public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {
...
}
AbstractTrafficShapingHandler
AbstractTrafficShapingHandler在Netty中用来进行流量整形,有三个实现:
在Netty中可以通过高低水位和流量整形来进行读取写入速度的控制。
上面介绍的就是介于流量整形来进行读写速度的控制。在ChannelConfig
中提供了setWriteBufferLowWaterMark
和setWriteBufferHighWaterMark
来设置高低水位线,我们可以在启动时候自定义实现的ChannelInitializer
增加这个逻辑处理