【前言】
这篇文字不全是讲t-io代码框架,而是博主怎么根据代码系统学习梳理的过程
t-io和之前的ukefu不太一样,ukefu是产品,所有有控制层/业务支撑层/数据层,t-io是网络框架
tio源码链接:https://gitee.com/tywo45/t-io
代码分析过程用的代码地图工具参考链接:
https://blog.csdn.net/weixin_39020940/article/details/80633835
【正文】
首先,我们知道,t-io对外呈现的是接口,依赖的是java的aio能力(不了解aio的自己补一课)
对于产品,我的建议是从控制层入手,对于框架,我建议从接口Demo入手,先看看Demo和框架之间的串联
package org.tio.examples.helloworld.server;
import java.io.IOException;
import org.tio.examples.helloworld.common.Const;
import org.tio.server.TioServer;
import org.tio.server.ServerGroupContext;
import org.tio.server.intf.ServerAioHandler;
import org.tio.server.intf.ServerAioListener;
/**
*
* @author tanyaowu
* 2017年4月4日 下午12:22:58
*/
public class HelloServerStarter {
//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);
}
}
Demo代码结构大致如下:
从这个结构和Demo代码可以看出
1、使用t-io做网络开发确实是异常简单,只用几行代码就可以实现服务端
2、ServerAioListener\ServerAioHandler是框架给应用来实现的接口,用于框架对应用的回调
3、TioServer、ServerGroupContext是框架的提供的(在这里我们可能还不知道它具体的作用,但可以确认的是,这两个负责起来了应用和框架的串联)
一、首先自上而下的看,找到框架是怎么把应用进行了串联
如果框架要对应用进行回调控制,就需要持有ServerAioHandler实例,这里使用starTool导出t-io的代码地图,在Member Variable列过滤ServerAioHandler,结果如下图
后面几个Starter结尾的肯定和上面的demo一样是应用,而ServerGroupContext就是刚才demo使用的上下文这样,这里我们看一下ServerGroupContext后面的方法,确认一下提供了ServerAioHandler的set/get方法
而ServerAioHandler的get方法的调用点是ServerGroupContext的AioHandler的get方法(调用点需要看代码得出,毕竟这里以及锁定代码了),而ServerGroupContext的AioHandler的get方法的调用点是DecodeRunnable
Integer packetNeededLength = channelContext.getPacketNeededLength();
if (packetNeededLength != null) {
log.info("{}, 解码所需长度:{}", channelContext, packetNeededLength);
if (readableLength >= packetNeededLength) {
packet = groupContext.getAioHandler().decode(byteBuffer, limit, initPosition, readableLength, channelContext);
}
} else {
packet = groupContext.getAioHandler().decode(byteBuffer, limit, initPosition, readableLength, channelContext);
}
这里,我们找到了ServerAioHandler的一个方法的调用点(框架->应用),但是ServerAioHandler方法不止这一个,为了避免过于深入代码,我们发散一下,继续使用代码地图,过滤后缀是Runnable,结果如下:
这样,对应用handler/Listener的几个回调点我们找到了(CloseRunable会回调AioListener,当然不一定是全部回调点),而且我们也找到了几个框架的自有线程
(这里注意下,使用代码地图时,同时也要观察类的成员类,这样才能有系统的理解)
到此,我们基于当前的结论做一下总结
1、框架的xxRunable会回调应用的handler和listener
2、GroupContext是连接应用和框架的上下文,框架会从GroupContext获取应用的handler和listener
3、框架底层很多是面向事务的,上层再衍生出Server和Client,如ServerAioHandler/ClientAioHandler,ServerGroupContext/ClientGroupContext
以及三个新任务:
1、ChannelContext是做什么的
2、xxRunable的运作机制和生命周期
3、TioServer的start做了哪些事情
二、ChannelContext\Runable\TioServer解读
首先从代码地图中搜索包含几个Runnable的
范围缩小后,我们可以撸代码了,稍作排查基本可以确认(只看有没有new这些xxRunnable的点就行了)
1、Tio控制CloseRunnable的生命周期,确切的来说是在Close的时候
2、ChannelContext控制DecodeRunnable,HandlerRunnable,SendRunnable,直接贴代码了
public void setGroupContext(GroupContext groupContext) {
this.groupContext = groupContext;
if (groupContext != null) {
decodeRunnable = new DecodeRunnable(this);
// closeRunnable = new CloseRunnable(this, null, null, groupContext.getCloseExecutor());
// handlerRunnableHighPrior = new HandlerRunnable(this, groupContext.getHandlerExecutorHighPrior());
handlerRunnable = new HandlerRunnable(this, groupContext.getTioExecutor());
// sendRunnableHighPrior = new SendRunnable(this, groupContext.getSendExecutorHighPrior());
sendRunnable = new SendRunnable(this, groupContext.getTioExecutor());
groupContext.connections.add(this);
}
}
3、ReadCompletionHandler只有调用
4、SslListener,作者懒不想看了
Tio和ChannelContext都不是Demo里面显示创建的对象,而且从代码地图上,这时候我们看一眼Tio代码,很容易就可以看到原因,这是一个API类,所有的方法都是静态的,正如作者注释所说,The Class Tio. t-io用户关心的API几乎全在这
ChannelContext的生命周期管理者其实很好找了,之前如果看过一下GroupContext代码,用代码地图过滤也行,就可以找到,是由GroupContext创建管理的,如下:
public final SetWithLock connections = new SetWithLock(new HashSet());
public final SetWithLock connecteds = new SetWithLock(new HashSet());
public final SetWithLock closeds = new SetWithLock(new HashSet());
不难看出,GroupContext管理了三组ChannelContext,分别是各种状态下的连接,结构如下
接下来我们看一下,TioServer在调用start中间,做了什么,基本应该就可以把框架相关流程串联起来了
public void start(String serverIp, int serverPort) throws IOException {
this.serverNode = new Node(serverIp, serverPort);
// ExecutorService groupExecutor = serverGroupContext.getGroupExecutor();
channelGroup = AsynchronousChannelGroup.withThreadPool(serverGroupContext.getGroupExecutor());
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);
}
这里TioServer使用的Asynchronous开头的相关能力就是jdk.aio提供的了,代码核心流程如下
1、TioServer使用ServerGroupContext创建的线程池提供给aio的channel用于建连
2、绑定端口监听
3、设置报文接受完应用的回调函数,AcceptCompletionHandler::completed
4、AcceptCompletionHandler::completed里创建ServerChannelContext用于保存本次连接相关信息并刷新到ServerGroupContext下,并回调预留给APP的回调接口
至此,我们就完成一轮代码摸底,相信认真看完的已经对代码框架有了初步的理解
三、回过头梳理一下服务端侧APP和Tio共同完成的服务端业务主要流程
作者在梳理这个流程的时候,看到了很多在代码地图里面上功能类被上下文context使用,类似于黑名单、鉴权、统计、跟踪,服务于这个呼叫流程。
四、横向纵向扩展理解
现在我们已经看了一个关键业务流程,并且知道了一些代码风格,我们可以根据这个风格做横向扩展
1、xxxHandler在框架内外的职责基本一致,就是对xx的处理
2、xxListener
3、xxRunable
4、xxContext
【总结】
1、代码地图需要增加对静态API类的识别(已经加入作者工期)
2、代码都是关联的,我们总能通过关联找到代码之间的联系
3、通过一个流程的横向纵向扩展学习,可以帮助我们了解整个系统