【T-IO】t-io 的学习笔记

学习版本:3.0.5.v20180626

ObjWithLock

  • 描述:

    自带读写锁的对象

  • 代码:

package org.tio.utils.lock;

import java.io.Serializable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

/**
 * 自带读写锁的对象.
 *
 * @author tanyaowu
 */
public class ObjWithLock<T> implements Serializable {
    /**
     * 
     */
    private T obj = null;

    /**
     * 
     */
    private ReentrantReadWriteLock lock = null;

    /**
     * 
     * @param obj
     * @author tanyaowu
     */
    public ObjWithLock(T obj) {
        this(obj, new ReentrantReadWriteLock());
    }

    /**
     * 
     * @param obj
     * @param lock
     * @author tanyaowu
     */
    public ObjWithLock(T obj, ReentrantReadWriteLock lock) {
        super();
        this.obj = obj;
        this.lock = lock;
    }

    /**
     * 
     * @return
     * @author tanyaowu
     */
    public ReentrantReadWriteLock getLock() {
        return lock;
    }

    /**
     * 获取写锁
     * @return
     */
    public WriteLock writeLock() {
        return lock.writeLock();
    }

    /**
     * 获取读锁
     * @return
     */
    public ReadLock readLock() {
        return lock.readLock();
    }

    /**
     * 
     * @return
     * @author tanyaowu
     */
    public T getObj() {
        return obj;
    }

    /**
     * 
     * @param obj
     * @author tanyaowu
     */
    public void setObj(T obj) {
        this.obj = obj;
    }

    private static final long serialVersionUID = -3048283373239453901L;
}
  • 学习小结:

    1. 自带锁的对象就是将原来的对象和并发包里的读写锁封装成一个新的封装对象。
    2. 知道了 ReentrantReadWriteLock 这个锁类,但是这个锁类的使用需要进一步学习。
    3. 自己动手实现一个这样的封装类。
    4. 继承实现一个 MapWithLock ,实现方法 put(), putIfAbsent(), putAll(), get(), clear(), size() 。
    5. 继承实现一个 ListWithLock ,实现方法 add(), clear(), remove(), size() 。
    6. 继承实现一个 SetWithLock ,实现方法 add(), clear(), remove(), size() 。

AioHandler

  • 描述:

    服务端处理器接口和客户端处理器接口都需要继承的接口,定义了 encode(), decode(), handler() 三个方法,分别是编码、解码和处理获取到的信息。client端还有一个heartbeatPacket()方法来提供心跳检测。自己想要实现的服务端和客户端处理器只需要分别实现ServerAioHandler和ClientAioHandler两个接口并补充需要实现的几个方法即可。这里以官方提供的 HelloWorld demo 进行学习。

    1. 编码:
/**
 * 编码:把业务消息包编码为可以发送的ByteBuffer
 * 总的消息结构:消息头 + 消息体
 * 消息头结构:    4个字节,存储消息体的长度
 * 消息体结构:   对象的json串的byte[]
 */
@Override
public ByteBuffer encode(Packet packet, GroupContext groupContext, ChannelContext channelContext) {
    HelloPacket helloPacket = (HelloPacket) packet;
    byte[] body = helloPacket.getBody();
    int bodyLen = 0;
    if (body != null) {
        bodyLen = body.length;
    }

    //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
    int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
    //创建一个新的bytebuffer
    ByteBuffer buffer = ByteBuffer.allocate(allLen);
    //设置字节序
    buffer.order(groupContext.getByteOrder());

    //写入消息头----消息头的内容就是消息体的长度
    buffer.putInt(bodyLen);

    //写入消息体
    if (body != null) {
        buffer.put(body);
    }
    return buffer;
}
    1. 传入的参数是 t-io 的对象 Pakcet ,GroupContext 和 ChannelContext。返回 java nio 原生的 ByteBuffer 。
    1. 首先,编码方法把Packet转换成HelloPacket,这是因为server准备发送的就是一个HelloPacket类格式的数据,自己可以定义一个Packet,继承Packet后可以仿照HelloPacket定义内部的属性和方法。
    1. 其次,需要确定需要发送的byte[]的总长度,这个长度是消息头长度+消息体长度。消息头长度可以定义是否加密、消息体长度等信息,消息体的长度就是packet转成byte[]后的长度,如果没有消息体,这个长度就是0。
    1. 然后,创建一个新的ByteBuffer,分配的长度就是消息的总长度,然后在消息头中定义消息体的长度,然后写入消息体,这样client解码的时候可以按照消息头中的消息体长度去解析消息体,获取到完整的包。

    2. 解码

/**
 * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
 * 总的消息结构:消息头 + 消息体
 * 消息头结构:    4个字节,存储消息体的长度
 * 消息体结构:   对象的json串的byte[]
 */
@Override
public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
    //提醒:buffer的开始位置并不一定是0,应用需要从buffer.position()开始读取数据
    //收到的数据组不了业务包,则返回null以告诉框架数据不够
    if (readableLength < HelloPacket.HEADER_LENGHT) {
        return null;
    }

    //读取消息体的长度
    int bodyLength = buffer.getInt();

    //数据不正确,则抛出AioDecodeException异常
    if (bodyLength < 0) {
        throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
    }

    //计算本次需要的数据长度
    int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
    //收到的数据是否足够组包
    int isDataEnough = readableLength - neededLength;
    // 不够消息体长度(剩下的buffe组不了消息体)
    if (isDataEnough < 0) {
        return null;
    } else //组包成功
    {
        HelloPacket imPacket = new HelloPacket();
        if (bodyLength > 0) {
            byte[] dst = new byte[bodyLength];
            buffer.get(dst);
            imPacket.setBody(dst);
        }
        return imPacket;
    }
}
    1. 首先,对可读长度进行一个判断,因为已知获取到的是一个HelloPacket规范的包,所以以HelloPacket协议的消息头长度作为判断。这里的协议可以为任何自定义的和client相互明确的协议,这样就可以确定包括消息头长度等基本信息。如果可读长度小于消息头的长度,那么这是一个半包,返回null。
    1. 其次,读取消息体的长度如果消息头的长度小于0,那么是一个错误的数据,抛出异常。
    1. 然后,将HelloPacket协议的消息头长度+接收到的实际消息体长度得到需要读取的数组长度,然后和可读长度比较,如果可读长度不够,那么是一个半包,返回null。如果长度足够,那么从ByteBuffer中获取消息体长度的数据并置入新的HelloPacket中返回。

    2. 处理消息

/**
 * 处理消息
 */
@Override
public void handler(Packet packet, ChannelContext channelContext) throws Exception {
    HelloPacket helloPacket = (HelloPacket) packet;
    byte[] body = helloPacket.getBody();
    if (body != null) {
        String str = new String(body, HelloPacket.CHARSET);
        System.out.println("收到消息:" + str);

        HelloPacket resppacket = new HelloPacket();
        resppacket.setBody(("收到了你的消息,你的消息是:" + str).getBytes(HelloPacket.CHARSET));
        Tio.send(channelContext, resppacket);
    }
    return;
}
    1. 首先,将获取到的Packet转换成HelloPacket,这是因为已知收到的包是这个。并从HelloPacket中取出实际的消息体。如果消息体为null,那就是没有可处理的实际数据,方法结束。
    1. 其次,将byte[]消息体数据根据指定的字符集进行转换,得到可以真正处理的字符串格式。这个数据可以进行后续的任何处理,然后将这个处理的结果重新封装成HelloPacket,返回给client。这样,服务端的处理就结束了。

    2. 心跳检测

/**
 * 此方法如果返回null,框架层面则不会发心跳;如果返回非null,框架层面会定时发本方法返回的消息包
 */
@Override
public HelloPacket heartbeatPacket() {
    return heartbeatPacket;
}
    1. 首先,这个方法是客户端处理器特有的,用来检测与服务端的连接是否正常。
    1. 其次,demo里面是发送了一个没有消息体,刚实例化的HelloPacket包。非null包都会定时发送,定时的设置是在GroupContext全局上下文中。如果是null,不发送。

GroupContext

  • 描述:

    这是一个上下文抽象类,定义了t-io框架的上下文所需的信息,它继承了MapWithLockPropSupport类,所以这个类是一个可以进行安全属性读写的类。它的主要子类就是两个,服务端的ServerGroupContext和客户端的ClientGroupContext。这两个后续再学习,先分析它们的共同特性的抽象父类GroupContext。

  • 代码:

package org.tio.core;

import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.client.ReconnConf;
import org.tio.core.cluster.DefaultMessageListener;
import org.tio.core.cluster.TioClusterConfig;
import org.tio.core.intf.AioHandler;
import org.tio.core.intf.AioListener;
import org.tio.core.intf.ChannelTraceHandler;
import org.tio.core.intf.GroupListener;
import org.tio.core.intf.Packet;
import org.tio.core.intf.TioUuid;
import org.tio.core.maintain.BsIds;
import org.tio.core.maintain.ClientNodeMap;
import org.tio.core.maintain.Groups;
import org.tio.core.maintain.Ids;
import org.tio.core.maintain.IpBlacklist;
import org.tio.core.maintain.IpStats;
import org.tio.core.maintain.Ips;
import org.tio.core.maintain.Tokens;
import org.tio.core.maintain.Users;
import org.tio.core.ssl.SslConfig;
import org.tio.core.stat.DefaultIpStatListener;
import org.tio.core.stat.GroupStat;
import org.tio.core.stat.IpStatListener;
import org.tio.utils.Threads;
import org.tio.utils.lock.MapWithLock;
import org.tio.utils.lock.SetWithLock;
import org.tio.utils.prop.MapWithLockPropSupport;
import org.tio.utils.thread.pool.SynThreadPoolExecutor;

/**
 * 
 * @author tanyaowu 
 * 2016年10月10日 下午5:25:43
 */
public abstract class GroupContext extends MapWithLockPropSupport {
    static Logger log = LoggerFactory.getLogger(GroupContext.class);
    /**
     * 默认的接收数据的buffer size
     */
    public static final int READ_BUFFER_SIZE = Integer.getInteger("tio.default.read.buffer.size", 2048);

    private final static AtomicInteger ID_ATOMIC = new AtomicInteger();

    private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;

    public boolean isShortConnection = false;

    public SslConfig sslConfig = null;

    public boolean debug = false;

    /**
     * 心跳超时时间(单位: 毫秒),如果用户不希望框架层面做心跳相关工作,请把此值设为0或负数
     */
    public long heartbeatTimeout = 1000 * 120;

    public PacketHandlerMode packetHandlerMode = PacketHandlerMode.SINGLE_THREAD;//.queue;

    /**
     * 接收数据的buffer size
     */
    public int readBufferSize = READ_BUFFER_SIZE;

    protected ReconnConf reconnConf;//重连配置

    public final ChannelTraceHandler clientTraceHandler = new DefaultChannelTraceHandler();

    private GroupListener groupListener = null;

    private TioUuid tioUuid = new DefaultTioUuid();

    public SynThreadPoolExecutor tioExecutor = null;

    public SynThreadPoolExecutor tioCloseExecutor = null;

    public ThreadPoolExecutor groupExecutor = null;
    public final ClientNodeMap clientNodeMap = new ClientNodeMap();
    public final SetWithLock<ChannelContext> connections = new SetWithLock<ChannelContext>(new HashSet<ChannelContext>());
    public final Groups groups = new Groups();
    public final Users users = new Users();
    public final Tokens tokens = new Tokens();
    public final Ids ids = new Ids();
    public final BsIds bsIds = new BsIds();
    public final Ips ips = new Ips();
    public IpStats ipStats = null;

    /**
     * ip黑名单
     */
    public IpBlacklist ipBlacklist = null;//new IpBlacklist();

    public final MapWithLock<Integer, Packet> waitingResps = new MapWithLock<Integer, Packet>(new HashMap<Integer, Packet>());

    /**
     * packet编码成bytebuffer时,是否与ChannelContext相关,false: packet编码与ChannelContext无关
     */
    //  private boolean isEncodeCareWithChannelContext = true;

    protected String id;

    /**
     * 解码异常多少次就把ip拉黑
     */
    protected int maxDecodeErrorCountForIp = 10;

    protected String name = "未命名GroupContext";

    private IpStatListener ipStatListener = DefaultIpStatListener.me;

    private boolean isStopped = false;

    /**
     * 如果此值不为null,就表示要集群
     */
    private TioClusterConfig tioClusterConfig = null;

    public GroupContext() {
        this(null, null);
    }

    /**
     * 
     * @param tioExecutor
     * @param groupExecutor
     * @author: tanyaowu
     */
    public GroupContext(SynThreadPoolExecutor tioExecutor, ThreadPoolExecutor groupExecutor) {
        this(null, tioExecutor, groupExecutor);
    }

    /**
     * 
     * @param tioClusterConfig
     * @param tioExecutor
     * @param groupExecutor
     * @author: tanyaowu
     */
    public GroupContext(TioClusterConfig tioClusterConfig, SynThreadPoolExecutor tioExecutor, ThreadPoolExecutor groupExecutor) {
        this(tioClusterConfig, tioExecutor, null, groupExecutor);
    }

    /**
     * 
     * @param tioClusterConfig
     * @param tioExecutor
     * @param groupExecutor
     * @author: tanyaowu
     */
    public GroupContext(TioClusterConfig tioClusterConfig, SynThreadPoolExecutor tioExecutor, SynThreadPoolExecutor tioCloseExecutor, ThreadPoolExecutor groupExecutor) {
        super();
        this.id = ID_ATOMIC.incrementAndGet() + "";
        this.ipBlacklist = new IpBlacklist(id, this);
        this.ipStats = new IpStats(this, null);

        // 调整rtopicMessageListener设定位置
        // 原来在TioClusterConfig内,这样不太友好,
        // 因为TioClusterConfig做为GroupContext的构造形参同时GroupContext又是TioClusterConfig的初始化参数,无解…
        this.setTioClusterConfig(tioClusterConfig);
        this.tioExecutor = tioExecutor;
        if (this.tioExecutor == null) {
            this.tioExecutor = Threads.tioExecutor;
        }

        this.groupExecutor = groupExecutor;
        if (this.groupExecutor == null) {
            this.groupExecutor = Threads.groupExecutor;
        }

        this.tioCloseExecutor = tioCloseExecutor;
        if (this.tioCloseExecutor == null) {
            this.tioCloseExecutor = Threads.tioCloseExecutor;
        }
    }

    /**
     * 获取AioHandler对象
     * @return
     * @author: tanyaowu
     */
    public abstract AioHandler getAioHandler();

    /**
     * 获取AioListener对象
     * @return
     * @author: tanyaowu
     */
    public abstract AioListener getAioListener();

    /**
     * 是否是集群
     * @return true: 是集群
     * @author: tanyaowu
     */
    public boolean isCluster() {
        return tioClusterConfig != null;
    }

    /**
     *
     * @return
     * @author tanyaowu
     */
    public ByteOrder getByteOrder() {
        return byteOrder;
    }

    /**
     * @return the groupListener
     */
    public GroupListener getGroupListener() {
        return groupListener;
    }

    /**
     * 获取GroupStat对象
     * @return
     * @author: tanyaowu
     */
    public abstract GroupStat getGroupStat();

    /**
     *
     * @return
     * @author tanyaowu
     */
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    /**
     * @return the reconnConf
     */
    public ReconnConf getReconnConf() {
        return reconnConf;
    }

    /**
     * @return the tioUuid
     */
    public TioUuid getTioUuid() {
        return tioUuid;
    }

    /**
     * @return the syns
     */
    public MapWithLock<Integer, Packet> getWaitingResps() {
        return waitingResps;
    }

    /**
     * @return the isEncodeCareWithChannelContext
     */
    //  public boolean isEncodeCareWithChannelContext() {
    //      return isEncodeCareWithChannelContext;
    //  }

//  /**
//   * @return the isShortConnection
//   */
//  public boolean isShortConnection {
//      return isShortConnection;
//  }

    /**
     * @return the isStop
     */
    public boolean isStopped() {
        return isStopped;
    }

    /**
     *
     * @param byteOrder
     * @author tanyaowu
     */
    public void setByteOrder(ByteOrder byteOrder) {
        this.byteOrder = byteOrder;
    }

    /**
     * @param isEncodeCareWithChannelContext the isEncodeCareWithChannelContext to set
     */
    //  public void setEncodeCareWithChannelContext(boolean isEncodeCareWithChannelContext) {
    //      this.isEncodeCareWithChannelContext = isEncodeCareWithChannelContext;
    //  }

    /**
     * @param groupListener the groupListener to set
     */
    public void setGroupListener(GroupListener groupListener) {
        this.groupListener = groupListener;
    }

    /**
     * @param heartbeatTimeout the heartbeatTimeout to set
     */
    public void setHeartbeatTimeout(long heartbeatTimeout) {
        this.heartbeatTimeout = heartbeatTimeout;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * @param packetHandlerMode the packetHandlerMode to set
     */
    public void setPacketHandlerMode(PacketHandlerMode packetHandlerMode) {
        this.packetHandlerMode = packetHandlerMode;
    }

    /**
     * @param readBufferSize the readBufferSize to set
     */
    public void setReadBufferSize(int readBufferSize) {
        this.readBufferSize = Math.min(readBufferSize, TcpConst.MAX_DATA_LENGTH);
    }

    /**
     * @param isShortConnection the isShortConnection to set
     */
    public void setShortConnection(boolean isShortConnection) {
        this.isShortConnection = isShortConnection;
    }

    /**
     * @param isStop the isStop to set
     */
    public void setStopped(boolean isStopped) {
        this.isStopped = isStopped;
    }

    /**
     * @param tioUuid the tioUuid to set
     */
    public void setTioUuid(TioUuid tioUuid) {
        this.tioUuid = tioUuid;
    }

    public TioClusterConfig getTioClusterConfig() {
        return tioClusterConfig;
    }

    public void setTioClusterConfig(TioClusterConfig tioClusterConfig) {
        this.tioClusterConfig = tioClusterConfig;
        if (this.tioClusterConfig != null) {
            this.tioClusterConfig.addMessageListener(new DefaultMessageListener(this));
        }
    }

    public void setSslConfig(SslConfig sslConfig) {
        this.sslConfig = sslConfig;
    }

    public IpStatListener getIpStatListener() {
        return ipStatListener;
    }

    public void setIpStatListener(IpStatListener ipStatListener) {
        this.ipStatListener = ipStatListener;
        //      this.ipStats.setIpStatListener(ipStatListener);
    }

    /**
     * 是服务器端还是客户端
     * @return
     * @author tanyaowu
     */
    public abstract boolean isServer();
}
  • 解析:

    1. 定义4个抽象方法,

      public abstract AioHandler getAioHandler(),

      public abstract AioListener getAioListener(),

      public abstract GroupStat getGroupStat(),

      public abstract boolean isServer()。

      在GroupContext的两个子类ServerGroupContext和ClientGroupContext中分别返回

      ServerAioHandler/ClientAioHandler,
      ServerAioListener/ClientAioHandler,
      ServerGroupStat/ClientGroupStat,

      true/false。
    1. 设置全局的配置参数:

      接收数据的buffer的size默认设置为2048字节,

      定义写入buffer的字节顺序为BIG_ENDIAN,详情参见字节顺序

      定义心跳超时时间,默认两分钟,

      设置一个ip黑名单,当解码失败多少次后拉入黑名单,默认为10次,

      设置是否为集群,

      还有一系列的配置,头疼。。。
  • 编码和解码的总结:

完整的消息包括消息头和消息体,而消息头存的是消息体的长度,消息头的长度是编码和解码双方都达成一致的协议。解码方先个根据协议中消息头的长度读取消息头中消息体的长度,然后通过消息体的长度去判断剩下的字节数组是否有足够长的消息体,如果没有那么是半包,如果大于等于了这个长度,那么读取消息体的长度还原成原来的包进行后续的处理。


TioServer

  • 描述:

    t-io封装好的服务端,加入地址参数调用start()方法即可启动。主要代码是start()方法。
  • start()代码:
public void start(String serverIp, int serverPort) throws IOException {
    this.serverNode = new Node(serverIp, serverPort);
    channelGroup = AsynchronousChannelGroup.withThreadPool(serverGroupContext.groupExecutor);
    serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);

    serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
    serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 64 * 1024);

    InetSocketAddress listenAddress = null;

    if (StringUtils.isBlank(serverIp)) {
        listenAddress = new InetSocketAddress(serverPort);
    } else {
        listenAddress = new InetSocketAddress(serverIp, serverPort);
    }

    serverSocketChannel.bind(listenAddress, 0);

    AcceptCompletionHandler acceptCompletionHandler = serverGroupContext.getAcceptCompletionHandler();
    serverSocketChannel.accept(this, acceptCompletionHandler);

    log.warn("{} started, listen on {}", serverGroupContext.getName(), this.serverNode);
}

看了源码,就是把原生的nio中启动server的方式封装了一下。和netty的方式也蛮相似的,之前联系netty的时候也是差不多这个流程走下来,或许下次也可以把netty的设置也用这样的方式封装起来,这样启动节点就简单明了。

服务端的启动很简单,就是实例化一个TioServer,然后将服务端上下文参数设置好传入,最后启动即可。如:

//handler, 包括编码、解码、消息处理
public static ServerAioHandler aioHandler = new HelloServerAioHandler();

//事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
public static ServerAioListener aioListener = null;

//一组连接共用的上下文对象
public static ServerGroupContext serverGroupContext = new ServerGroupContext("hello-tio-server", aioHandler, aioListener);

//tioServer对象
public static TioServer tioServer = new TioServer(serverGroupContext);

//有时候需要绑定ip,不需要则null
public static String serverIp = null;

//监听的端口
public static int serverPort = Const.PORT;

/**
 * 启动程序入口
 */
public static void main(String[] args) throws IOException {
    serverGroupContext.setHeartbeatTimeout(org.tio.examples.helloworld.common.Const.TIMEOUT);

    tioServer.start(serverIp, serverPort);
}

服务端上下文实例化需要传入的是服务端的name,自定义的服务端handler和自定义的服务端listener。


DecodeRunnable

  • 描述:

    解码任务线程对象,当有一个连接向本节点发送数据时用一个解码对象去解码,这些线程对象是由t-io自己封装ThreadPoolExecutor的SynThreadPoolExecutor来调用,这样可以有效的减小线程开支和保证线程安全。作为一个线程,最重要的肯定是它需要执行的run()方法,如下:
/**
 * @see java.lang.Runnable#run()
 *
 * @author tanyaowu
 * 2017年3月21日 下午4:26:39
 *
 */
@Override
public void run() {
    ByteBuffer byteBuffer = newByteBuffer;
    if (byteBuffer != null) {
        if (lastByteBuffer != null) {
            byteBuffer = ByteBufferUtils.composite(lastByteBuffer, byteBuffer);
            lastByteBuffer = null;
        }
    } else {
        return;
    }

    label_2: while (true) {
        try {
            int initPosition = byteBuffer.position();
            int limit = byteBuffer.limit();
            int readableLength = limit - initPosition;
            Packet packet = null;
            if (channelContext.packetNeededLength != null) {
                log.info("{}, 解码所需长度:{}", channelContext, channelContext.packetNeededLength);
                if (readableLength >= channelContext.packetNeededLength) {
                    packet = groupContext.getAioHandler().decode(byteBuffer, limit, initPosition, readableLength, channelContext);
                }
            } else {
                packet = groupContext.getAioHandler().decode(byteBuffer, limit, initPosition, readableLength, channelContext);
            }

            if (packet == null)// 数据不够,解不了码
            {
                lastByteBuffer = ByteBufferUtils.copy(byteBuffer, initPosition, limit);
                ChannelStat channelStat = channelContext.stat;
                channelStat.decodeFailCount++;
                //                  int len = byteBuffer.limit() - initPosition;
                log.debug("{} 本次解码失败, 已经连续{}次解码失败,参与解码的数据长度共{}字节", channelContext, channelStat.decodeFailCount, readableLength);
                if (channelStat.decodeFailCount > 5) {
                    if (channelContext.packetNeededLength == null) {
                        log.info("{} 本次解码失败, 已经连续{}次解码失败,参与解码的数据长度共{}字节", channelContext, channelStat.decodeFailCount, readableLength);
                    }

                    //检查慢包攻击(只有自用版才有)
                    if (channelStat.decodeFailCount > 10) {
                        int capacity = lastByteBuffer.capacity();
                        int per = capacity / channelStat.decodeFailCount;
                        if (per < Math.min(groupContext.readBufferSize / 2, 256)) {
                            throw new AioDecodeException("连续解码" + channelStat.decodeFailCount + "次都不成功,并且平均每次接收到的数据为" + per + "字节,有慢攻击的嫌疑");
                        }
                    }
                }
                return;
            } else //解码成功
            {
                channelContext.setPacketNeededLength(null);
                channelContext.stat.latestTimeOfReceivedPacket = SystemTimer.currentTimeMillis();

                ChannelStat channelStat = channelContext.stat;
                channelStat.decodeFailCount = 0;

                int afterDecodePosition = byteBuffer.position();
                int len = afterDecodePosition - initPosition;
                packet.setByteCount(len);

                groupContext.getGroupStat().receivedPackets.incrementAndGet();
                channelContext.stat.receivedPackets.incrementAndGet();

                if (groupContext.ipStats.durationList != null && groupContext.ipStats.durationList.size() > 0) {
                    try {
                        for (Long v : groupContext.ipStats.durationList) {
                            IpStat ipStat = groupContext.ipStats.get(v, channelContext.getClientNode().getIp());
                            ipStat.getReceivedPackets().incrementAndGet();
                            groupContext.getIpStatListener().onAfterDecoded(channelContext, packet, len, ipStat);
                        }
                    } catch (Exception e1) {
                        log.error(packet.logstr(), e1);
                    }
                }
                channelContext.traceClient(ChannelAction.RECEIVED, packet, null);

                AioListener aioListener = groupContext.getAioListener();
                try {
                    if (log.isDebugEnabled()) {
                        log.debug("{} 收到消息 {}", channelContext, packet.logstr());
                    }
                    aioListener.onAfterDecoded(channelContext, packet, len);
                } catch (Throwable e) {
                    log.error(e.toString(), e);
                }

                if (log.isDebugEnabled()) {
                    log.debug("{}, 解包获得一个packet:{}", channelContext, packet.logstr());
                }
                handler(packet, len);

                int remainingLength = byteBuffer.limit() - byteBuffer.position();
                if (remainingLength > 0)//组包后,还剩有数据
                {
                    if (log.isDebugEnabled()) {
                        log.debug("{},组包后,还剩有数据:{}", channelContext, remainingLength);
                    }
                    continue label_2;
                } else//组包后,数据刚好用完
                {
                    lastByteBuffer = null;
                    log.debug("{},组包后,数据刚好用完", channelContext);
                    return;
                }
            }
        } catch (Throwable e) {
            channelContext.setPacketNeededLength(null);
            log.error(channelContext + ", " + byteBuffer + ", 解码异常:" + e.toString(), e);

            if (e instanceof AioDecodeException) {
                List<Long> list = groupContext.ipStats.durationList;
                if (list != null && list.size() > 0) {
                    try {
                        for (Long v : list) {
                            IpStat ipStat = groupContext.ipStats.get(v, channelContext.getClientNode().getIp());
                            ipStat.getDecodeErrorCount().incrementAndGet();
                            groupContext.getIpStatListener().onDecodeError(channelContext, ipStat);
                        }
                    } catch (Exception e1) {
                        log.error(e1.toString(), e1);
                    }
                }
            }

            Tio.close(channelContext, e, "解码异常:" + e.getMessage());
            return;
        }
    }
}
  • 过程:
    1. 判断线程持有的ByteBuffer是否为null,为null表示没有数据接收到,方法结束。这里的ByteBuffer有一个set的方法,所以我觉得应该是框架在接收到数据的时候调用这个方法来设置一下。目前没看到在哪里设置,后面再看。不为null的时候需要和之前处理有可能剩下的ByteBuffer重新组合一下,在之前解码剩下的ByteBuffer后接上新的ByteBuffer组成一个新的ByteBuffer。
    1. 读取ByteBuffer中的可读长度是否大于上下文中定的包的需要长度,如果小于那么长度不够组成一个包,解码结束。
    1. 后续就是对数据进行解码,设置解码后的指针,判断解码后是否有数据多余,有多余的再次循环判断。没有多余。解码结束。

PS 未完待续。。。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值