t-io入门篇(二)

1、项目下载导入

  1. 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文件成功:java

mvn eclipse:eclipse -DdownloadSources=true -X

   3. 使用eclipse导入maven项目便可。ios

2、helloworld server 

  • HelloServerStarter.java demo

      hello world的代码很是简介,其功能主要就是:git

      启动一个服务端,服务端能够接收客户端发送的消息,而且向客户端回发一条消息。咱们先来看看使用这个框架启动一个服务端有多么简单。spring

..
//建立消息handler,解/编码消息体
public static ServerAioHandler<Object, HelloPacket, Object> aioHandler = new HelloServerAioHandler();
//建立链接公用上下文
public static ServerGroupContext<Object, HelloPacket, Object> serverGroupContext = new ServerGroupContext<>(aioHandler, aioListener);
//建立aioserver对象
public static AioServer<Object, HelloPacket, Object> aioServer = new AioServer<>(serverGroupContext);

..

// 启动server
aioServer.start(serverIp, serverPort);

      没有错,就只须要初始化几个这样的参数便可启动socket server服务了 。多线程

如下是server启动框架内部代码分析(本身不用管,我是学习一下做者的源代码):

AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(serverGroupContext.getGroupExecutor());
		serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);

..

AcceptCompletionHandler<SessionContext, P, R> acceptCompletionHandler = serverGroupContext.getAcceptCompletionHandler();
//接收到消息以后把消息交给acceptCompletionHandler来处理
serverSocketChannel.accept(this, acceptCompletionHandler);

     AcceptCompletionHandler.java中接收到消息以后,会把消息传递给ReadCompletionHandler<SessionContext, P, R> readCompletionHandler,readCompletionHandler会对消息进行解析:框架

public void completed(Integer result, ByteBuffer byteBuffer) {
		if (result > 0) {
			if (channelContext.isTraceClient()) {
				Map<String, Object> 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<SessionContext, P, R> 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方法eclipse

public Object handler(HelloPacket packet, ChannelContext<Object, HelloPacket, Object> 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这种链式工做流程。socket

可是看代码有些细节值得我注意:async

1.ObjWithLock 对象附带读写锁封装,其实能够不封装,可是为了代码整洁方便作出了这个小的改进。maven

2.SystemTimer.currentTimeMillis()。哇,不得不说做者对性能要求极高。连jdk自带的System.currentTimeMills()本身也作了一点小优化,可能他的代码里面获取当前时间比较多了,为了提升性能,他本身写了一个单线程task,保证每10ms只会有一个线程去调用native方法,定时更新这个时间,各个线程每次不会调用native方法去区系统时间,而是直接从内存获取这个时间便可。若是是我本身写框架的话想一想本身也不会考虑这么细,通常就直接System.currentTimeMills()了。

3.我有一点不太明白的是,做者常常出现即便这个类没有手动定义的父类时,在该类定义构造方法的时候都会写一个super()方法,也就是object的构造方法,可是实际上没什么做用吧,仍是说编辑器自带生成,或者说是一个编码好习惯,防止将来出现什么疏漏?

 

==========================分割线=======================================

3、helloworld client

  • HelloClientStarter.java

     客户端代码看起来至关简洁 

..
// 客户端处理handler,发送消息以前会走super.encode 
public static ClientAioHandler<Object, HelloPacket, Object> aioClientHandler = new HelloClientAioHandler();
..
//断链后自动链接的,不想自动链接请设为null
private static ReconnConf<Object, HelloPacket, Object> reconnConf = new ReconnConf<Object, HelloPacket, Object>(5000L);

//一组链接共用的上下文对象
public static ClientGroupContext<Object, HelloPacket, Object> 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配置等等很是麻烦,后来也是有大神改了一版本,所有接口使用了静态方法去调用,代码简化了不少不少,不是乱糟糟的样子,让代码可读性加强。

相关文章
相关标签/搜索