一、项目下载导入
- t-io的git地址是 https://git.oschina.net/tywo45/t-io 直接克隆一个到本地。
git clone https://git.oschina.net/tywo45/t-io.git
2. 由于使用eclipse直接导入maven项目非常慢,我先使用本地maven命令生成t-io的eclipse项目,进入到项目目录中的 t-io\src\parent 目录执行直到所有项目都生成eclipse文件成功:
mvn eclipse:eclipse -DdownloadSources=true -X
3. 使用eclipse导入maven项目即可。
二、helloworld server
- HelloServerStarter.java demo
hello world的代码非常简介,其功能主要就是:
启动一个服务端,服务端可以接收客户端发送的消息,并且向客户端回发一条消息。我们先来看看使用这个框架启动一个服务端有多么简单。
..
//创建消息handler,解/编码消息体
public static ServerAioHandler
没有错,就只需要初始化几个这样的参数即可启动socket server服务了 。
以下是server启动框架内部代码分析(自己不用管,我是学习一下作者的源代码):
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(serverGroupContext.getGroupExecutor());
serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
..
AcceptCompletionHandler acceptCompletionHandler = serverGroupContext.getAcceptCompletionHandler();
//接收到消息之后把消息交给acceptCompletionHandler来处理
serverSocketChannel.accept(this, acceptCompletionHandler);
AcceptCompletionHandler.java中接收到消息之后,会把消息传递给ReadCompletionHandler
public void completed(Integer result, ByteBuffer byteBuffer) {
if (result > 0) {
if (channelContext.isTraceClient()) {
Map map = new HashMap<>();
map.put("p_r_buf_len", result);
channelContext.traceClient(ClientAction.RECEIVED_BUF, null, map);
}
//注意这里,DecodeRunnable会调用 aioHandler的decode方法
// run方法中 channelContext.getGroupContext().getAioHandler().decode(byteBuffer, channelContext);
DecodeRunnable decodeRunnable = channelContext.getDecodeRunnable();
readByteBuffer.flip();
decodeRunnable.setNewByteBuffer(readByteBuffer);
decodeRunnable.run();
} else if (result == 0) {
log.error("{}读到的数据长度为0", channelContext);
} else if (result < 0) {
Aio.close(channelContext, null, "读数据时返回" + result);
}
if (AioUtils.checkBeforeIO(channelContext)) {
AsynchronousSocketChannel asynchronousSocketChannel = channelContext.getAsynchronousSocketChannel();
readByteBuffer.position(0);
readByteBuffer.limit(readByteBuffer.capacity());
asynchronousSocketChannel.read(readByteBuffer, readByteBuffer, this);
}
}
DecodeRunnable 中会先执行自己的decode方法,然后调用handler的 handler 方法,也就是调用了HelloServerAioHandler的handler方法
public Object handler(HelloPacket packet, ChannelContext channelContext) throws Exception
{
byte[] body = packet.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));
Aio.send(channelContext, resppacket);
}
return null;
}
- 代码细节
因为之前没接触过这方面开发,看起来很吃力,有些吃力比如asynchronousSocketChannel.read 这种语句,不是很清楚工作原理,看似是类似filter里面的fillter.doChain这种链式工作流程。
但是看代码有些细节值得我注意:
1.ObjWithLock 对象附带读写锁封装,其实可以不封装,但是为了代码整洁方便做出了这个小的改进。
2.SystemTimer.currentTimeMillis()。哇,不得不说作者对性能要求极高。连jdk自带的System.currentTimeMills()自己也做了一点小优化,可能他的代码里面获取当前时间比较多了,为了提高性能,他自己写了一个单线程task,保证每10ms只会有一个线程去调用native方法,定时更新这个时间,各个线程每次不会调用native方法去区系统时间,而是直接从内存获取这个时间即可。如果是我自己写框架的话想想自己也不会考虑这么细,一般就直接System.currentTimeMills()了。
3.我有一点不太明白的是,作者经常出现即使这个类没有手动定义的父类时,在该类定义构造方法的时候都会写一个super()方法,也就是object的构造方法,但是实际上没什么作用吧,还是说编辑器自带生成,或者说是一个编码好习惯,防止未来出现什么疏漏?
==========================分割线=======================================
三、helloworld client
- HelloClientStarter.java
客户端代码看起来相当简洁
..
// 客户端处理handler,发送消息之前会走super.encode
public static ClientAioHandler aioClientHandler = new HelloClientAioHandler();
..
//断链后自动连接的,不想自动连接请设为null
private static ReconnConf reconnConf = new ReconnConf(5000L);
//一组连接共用的上下文对象
public static ClientGroupContext clientGroupContext = new ClientGroupContext<>(aioClientHandler, aioListener, reconnConf);
..
//创建client对象
aioClient = new AioClient<>(clientGroupContext);
//尝试连接服务端
clientChannelContext = aioClient.connect(serverNode);
..
//最后发送
Aio.send(clientChannelContext, packet);
OK,上面就是客户端连接我上面的服务端的代码,看起来是不是非常的简洁?就连向服务端发送的方法都已经写成了静态方法了。
以下是客户端框架内部代码分析:
- aioClient.connect方法解析
helloworld采用的是默认同步连接服务端方式,其中采用了 CountDownLatch(闭锁)
//闭锁创建
CountDownLatch countDownLatch = new CountDownLatch(1);
attachment.setCountDownLatch(countDownLatch);
//连接服务端
asynchronousSocketChannel.connect(inetSocketAddress, attachment, clientGroupContext.getConnectionCompletionHandler());
countDownLatch.await(_timeout, TimeUnit.SECONDS);
在ConnectionCompletionHandler中逻辑处理完毕在finally中调用的
attachment.getCountDownLatch().countDown()
同样在Aio.send方法里面也同样使用了闭锁,同样SendRunnable中会调用handler的encode方法将packet进行编码后发送。
- 心得:
1.闭锁和栅栏之前也有阅读过一些资料,但是一直没有应用到一些应用中来,譬如之前自己做的一个爬虫项目,同时使用多线程抓取多个网站多网页数据,我是通过设置信号量来判断线程是否已经抓取完毕然后执行数据清洗去重工作。其实使用CountDownLatch和CyclicBarrier来实现更简单
2.接口使用静态公用方法代码更加简洁。老版本的diamond在使用的时候要各种初始化,创建对象、spring ioc配置等等非常麻烦,后来也是有大神改了一版本,全部接口使用了静态方法去调用,代码简化了很多很多,不是乱糟糟的样子,让代码可读性增强。