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配置等等很是麻煩,後來也是有大神改了一版本,所有接口使用了靜態方法去調用,代碼簡化了不少不少,不是亂糟糟的樣子,讓代碼可讀性加強。

相關文章
相關標籤/搜索