Netty源码分析之消息的收发

在前面已经说明了在接收新连接时,将向worker注册默认的READ事件。然后worker开始阻塞select监听事件。
发送消息的情况。
这种情况常常是由DownstreamHandler主动发起,在对接收的消息处理后主动发送消息ctx.sendMessage,由内向外经过由DownstreamHandler和encode组成的过滤链,在链的尽头由NioServerSocketPipelineSink处理MessageEvent事件。
1.首先将事件放入channel中的writeBufferQueue中。
 MessageEvent event = (MessageEvent) e;
            NioSocketChannel channel = (NioSocketChannel) event.getChannel();
            boolean offered = channel.writeBufferQueue.offer(event);
            assert offered;
            channel.worker.writeFromUserCode(channel);

2.调用writeFromUserCode()方法写数据,该方法会检测当前执行线程是否是对应worker的IO线程。如果不是,则向该worker添加写任务(执行writeFromTaskLoop,无须检测执行线程)并返回,。
3.对channel.writeLock加锁,防止在写的过程中另外的cleanUpWriterBuffer操作writeBufferQueue
4.依次写各个messge event中的消息写到channel中,如果阻塞无法写入,则向worker注册该channel的可写事件,如果已把事件队列全部写完则取消在worker上channel的可写事件。
5.完成后,在workder的IO线程的通知写完成事件。

在写消息时采用了类似自旋锁方式,在向管道不能正常写入数据时直接重复16次,如还不能写再向worker注册可写事件。在大量数据需要写出时,消息数据将在channel中的queue堆积,worker监听线程疲于发消息而不会去select,直到kernel buffer填满而不能再向channel写入数据时再停止此次写数据回到监听状态。

接收消息的情况
这各情况是从worker的select阻塞退出了开始的,退出阻塞后首先执行任务队列中的任务。然后依次处理各个selectorKey。
1.如果key上有WRITE事件,表明当前channel处于可写状态,调用write0,如同发送消息的情况。
2.如果Key上有READ事件,表明有新的数据可读,通过ReceiveBufferSizePredictor根据上次消息的大小来决定预测本次消息所需的缓存大小。从channel读取数据到缓存到,并向上行流通知消息接收事件。
3.依次调用事件处理链上的decoder,handler。

读消息时使用到了ReceiveBufferSizePredictor来预测这次数据包的大小,其预测方法:
初始化预测索引表格SIZE_TABLE,其中的数据呈类指数增长。当实际数据大小比预测值SIZE_TABLE[index]大时,预测索引向右移动4格index+=4;当比预测值两次小于当前索引值左侧一格的值SIZE_TABLE[index-4]时,则预测索引向左移1格index--;当实际值在预测值索引SIZE_TABLE[index]和SIZE_TABLE[index-1]之前,预测成功,不作调整。

读消息所使用的缓存分析:
1.使用ByteBuffer.allocateDirect向OS直接申请内存空间,并不由GC来管理。
2.通过normalizeCapacity(size)来归一化缓存大小,修改为比size大的最小1024的倍数
3.每个worker都有一个SocketReceiveBufferAllocator分配直接缓存DirectBuffer,这类缓存IO时不需要把数据从OS内存复制到Java堆中,因此性能比堆缓存高,但分配、释放的代价比堆缓存高,Allocator通过尽量重复使用已分配的缓存来降低了分配直接缓存的频率。   
     3.1当首次分配时,直接分配directBuffer。
     3.2如果上次分配的buffer比size小,则释放并重新分配缓存
     3.3如果size持续16小于上次分配buffer大小的80%,则释放并重要分配缓存
     3.4其它情况,则重复使用上次分配的directBuffer
  
    ByteBuffer get(int size) {
        if (buf == null) {
            return newBuffer(size);
        }
        if (buf.capacity() < size) {
            return newBuffer(size);
        }
        if (buf.capacity() * percentual / 100 > size) {
            if (++exceedCount == maxExceedCount) {
               return newBuffer(size);
            } else {
                buf.clear();
            }
        } else {
            exceedCount = 0;
            buf.clear();
        }
        return buf;
    }

4.在用Direct类型ByteBuffer接收完数据后,再处理创建HeapChannelBuffer,复制directBuffer中的字节数据到HeapChannelBuffer。这个类有两个write read两个索引,在复制完数据后,更新write索引,但不需要flip(),因为这时候读索引不会变。
bb.flip();

            final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes);
            buffer.setBytes(0, bb);
            buffer.writerIndex(readBytes);

            // Update the predictor.
            predictor.previousReceiveBufferSize(readBytes);

            // Fire the event.
            fireMessageReceived(channel, buffer);

猜你喜欢

转载自xklin04.iteye.com/blog/1873320