網路程式設計框架t-io的程式設計基本知識介紹

manong發表於2021-12-06

t-io作為目前國內最流行的開源網路程式設計框架軟體,以簡單易懂,上手容易而著稱,相同的功能比起netty實現起來,要簡單的多,程式碼量也大大減少,如果要使用好t-io,還是要先學習t-io的一些基本知識,這篇文章主要從8個方面介紹了t-io的基礎知識。 具體請參考:

t-io收發訊息過程

t-io收發訊息及處理過程,可以用一張圖清晰地表達出來

應用層包:Packet

Packet是用於表述業務資料結構的,我們透過 繼承 Packet來實現自己的業務資料結構,對於各位而言,把Packet看作是一個普通的VO物件即可。

注意:不建議直接使用Packet物件,而是要 繼承Packet

一個簡單的Packet可能長這樣

  1. package org . tio . study . helloworld . common ;
  2. import org . tio . core . intf . Packet ;
  3. /**
  4. * @author tanyaowu
  5. */
  6. public class HelloPacket extends Packet {
  7.     private static final long serialVersionUID = - 172060606924066412L ;
  8.     public static final int HEADER_LENGTH = 4 ; //訊息頭的長度
  9.     public static final String CHARSET = "utf-8" ;
  10.     private byte [] body ;
  11.     /**
  12.     * @return the body
  13.     */
  14.     public byte [] getBody () {
  15.         return body ;
  16.     }
  17.     /**
  18.     * @param body the body to set
  19.     */
  20.     public void setBody ( byte [] body ) {
  21.         this . body = body ;
  22.     }
  23. }


可以結合AioHandler.java理解Packet

  1. package org . tio . core . intf ;
  2. import java . nio . ByteBuffer ;
  3. import org . tio . core . ChannelContext ;
  4. import org . tio . core . TioConfig ;
  5. import org . tio . core . exception . AioDecodeException ;
  6. /**
  7. *
  8. * @author tanyaowu
  9. * 2017年10月19日 上午9:40:15
  10. */
  11. public interface AioHandler {
  12.     /**
  13.     * 根據ByteBuffer解碼成業務需要的Packet物件.
  14.     * 如果收到的資料不全,導致解碼失敗,請返回null,在下次訊息來時框架層會自動續上前面的收到的資料
  15.     * @param buffer 參與本次希望解碼的ByteBuffer
  16.     * @param limit ByteBuffer的limit
  17.     * @param position ByteBuffer的position,不一定是0哦
  18.     * @param readableLength ByteBuffer參與本次解碼的有效資料(= limit - position)
  19.     * @param channelContext
  20.     * @return
  21.     * @throws AioDecodeException
  22.     */
  23.     Packet decode ( ByteBuffer buffer , int limit , int position , int readableLength , ChannelContext channelContext ) throws AioDecodeException ;
  24.     /**
  25.     * 編碼
  26.     * @param packet
  27.     * @param tioConfig
  28.     * @param channelContext
  29.     * @return
  30.     * @author: tanyaowu
  31.     */
  32.     ByteBuffer encode ( Packet packet , TioConfig tioConfig , ChannelContext channelContext );
  33.     /**
  34.     * 處理訊息包
  35.     * @param packet
  36.     * @param channelContext
  37.     * @throws Exception
  38.     * @author: tanyaowu
  39.     */
  40.     void handler ( Packet packet , ChannelContext channelContext ) throws Exception ;
  41. }

單條TCP連線上下文:ChannelContext

每一個tcp連線的建立都會產生一個ChannelContext物件,這是個抽象類,如果你是用t-io作tcp客戶端,那麼就是ClientChannelContext,如果你是用tio作tcp伺服器,那麼就是ServerChannelContext

網路程式設計框架t-io的程式設計基本知識介紹

使用者可以把業務資料透過ChannelContext物件和TCP連線關聯起來,像下面這樣設定屬性

ChannelContext.set(String key, Object value)

然後用下面的方式獲取屬性

ChannelContext.get(String key)

當然最最常用的還是用t-io提供的強到沒對手的bind功能,譬如用下面的程式碼繫結userid

Tio.bindUser(ChannelContext channelContext, String userid)

然後可以透過userid進行操作,示範程式碼如下

//獲取某使用者的ChannelContext集合SetWithLock<ChannelContext> set = Tio.getChannelContextsByUserid(tioConfig, userid);//給某使用者發訊息Tio.sendToUser(TioConfig, userid, Packet)

除了可以繫結userid,t-io還內建瞭如下繫結API

  • 繫結業務id

    Tio.bindBsId(ChannelContext channelContext, String bsId)
  • 繫結token

    Tio.bindToken(ChannelContext channelContext, String token)
  • 繫結群組
    Tio.bindGroup(ChannelContext channelContext, String group)

ChannelContext物件包含的資訊非常多,主要物件見下圖

網路程式設計框架t-io的程式設計基本知識介紹

說明

ChannelContext是t-io中非常重要的類,他是業務和連線的溝通橋樑!


服務配置與維護:TioConfig


  • 場景:我們在寫TCP Server時,都會先選好一個埠以監聽客戶端連線,再建立N組執行緒池來執行相關的任務,譬如傳送訊息、解碼資料包、處理資料包等任務,還要維護客戶端連線的各種資料,為了和業務互動,還要把這些客戶端連線和各種業務資料繫結起來,譬如把某個客戶端繫結到一個群組,繫結到一個userid,繫結到一個token等。
  • TioConfig就是解決以上場景的:配置執行緒池、監聽埠,維護客戶端各種資料等的。

  • TioConfig是個抽象類

    • 如果你是用tio作tcp客戶端,那麼你需要建立ClientTioConfig物件
      • 伺服器端對應一個ClientTioConfig物件
    • 如果你是用tio作tcp伺服器,那麼你需要建立ServerTioConfig
      • 一個監聽埠對應一個ServerTioConfig ,一個jvm可以監聽多個埠,所以一個jvm可以有多個ServerTioConfig物件
  • TioConfig物件包含的資訊非常多,主要物件見下圖

網路程式設計框架t-io的程式設計基本知識介紹

如何獲取TioConfig物件

見:


編碼、解碼、處理:AioHandler


AioHandler是處理訊息的核心介面,它有兩個子介面,ClientAioHandler和ServerAioHandler,當用tio作tcp客戶端時需要實現ClientAioHandler,當用tio作tcp伺服器時需要實現ServerAioHandler,它主要定義了3個方法,見下

  1. package org . tio . core . intf ;
  2. import java . nio . ByteBuffer ;
  3. import org . tio . core . ChannelContext ;
  4. import org . tio . core . TioConfig ;
  5. import org . tio . core . exception . AioDecodeException ;
  6. /**
  7. *
  8. * @author tanyaowu
  9. * 2017年10月19日 上午9:40:15
  10. */
  11. public interface AioHandler {
  12.     /**
  13.     * 根據ByteBuffer解碼成業務需要的Packet物件.
  14.     * 如果收到的資料不全,導致解碼失敗,請返回null,在下次訊息來時框架層會自動續上前面的收到的資料
  15.     * @param buffer 參與本次希望解碼的ByteBuffer
  16.     * @param limit ByteBuffer的limit
  17.     * @param position ByteBuffer的position,不一定是0哦
  18.     * @param readableLength ByteBuffer參與本次解碼的有效資料(= limit - position)
  19.     * @param channelContext
  20.     * @return
  21.     * @throws AioDecodeException
  22.     */
  23.     Packet decode ( ByteBuffer buffer , int limit , int position , int readableLength , ChannelContext channelContext ) throws AioDecodeException ;
  24.     /**
  25.     * 編碼
  26.     * @param packet
  27.     * @param tioConfig
  28.     * @param channelContext
  29.     * @return
  30.     * @author: tanyaowu
  31.     */
  32.     ByteBuffer encode ( Packet packet , TioConfig tioConfig , ChannelContext channelContext );
  33.     /**
  34.     * 處理訊息包
  35.     * @param packet
  36.     * @param channelContext
  37.     * @throws Exception
  38.     * @author: tanyaowu
  39.     */
  40.     void handler ( Packet packet , ChannelContext channelContext ) throws Exception ;
  41. }


訊息來往監聽:AioListener

AioListener是處理訊息的核心介面,它有兩個子介面:ClientAioListener和ServerAioListener

  • 當用tio作tcp客戶端時需要實現ClientAioListener
  • 當用tio作tcp伺服器時需要實現ServerAioListener

它主要定義瞭如下方法

  1. package org . tio . core . intf ;
  2. import org . tio . core . ChannelContext ;
  3. /**
  4. *
  5. * @author tanyaowu
  6. * 2017年4月1日 上午9:34:08
  7. */
  8. public interface AioListener {
  9.     /**
  10.     * 建鏈後觸發本方法,注:建鏈不一定成功,需要關注引數isConnected
  11.     * @param channelContext
  12.     * @param isConnected 是否連線成功,true:表示連線成功,false:表示連線失敗
  13.     * @param isReconnect 是否是重連, true: 表示這是重新連線,false: 表示這是第一次連線
  14.     * @throws Exception
  15.     * @author: tanyaowu
  16.     */
  17.     public void onAfterConnected ( ChannelContext channelContext , boolean isConnected , boolean isReconnect ) throws Exception ;
  18.     /**
  19.     * 原方法名:onAfterDecoded
  20.     * 解碼成功後觸發本方法
  21.     * @param channelContext
  22.     * @param packet
  23.     * @param packetSize
  24.     * @throws Exception
  25.     * @author: tanyaowu
  26.     */
  27.     public void onAfterDecoded ( ChannelContext channelContext , Packet packet , int packetSize ) throws Exception ;
  28.     /**
  29.     * 接收到TCP層傳過來的資料後
  30.     * @param channelContext
  31.     * @param receivedBytes 本次接收了多少位元組
  32.     * @throws Exception
  33.     */
  34.     public void onAfterReceivedBytes ( ChannelContext channelContext , int receivedBytes ) throws Exception ;
  35.     /**
  36.     * 訊息包傳送之後觸發本方法
  37.     * @param channelContext
  38.     * @param packet
  39.     * @param isSentSuccess true:傳送成功,false:傳送失敗
  40.     * @throws Exception
  41.     * @author tanyaowu
  42.     */
  43.     public void onAfterSent ( ChannelContext channelContext , Packet packet , boolean isSentSuccess ) throws Exception ;
  44.     /**
  45.     * 處理一個訊息包後
  46.     * @param channelContext
  47.     * @param packet
  48.     * @param cost 本次處理訊息耗時,單位:毫秒
  49.     * @throws Exception
  50.     */
  51.     public void onAfterHandled ( ChannelContext channelContext , Packet packet , long cost ) throws Exception ;
  52.     /**
  53.     * 連線關閉前觸發本方法
  54.     * @param channelContext the channelcontext
  55.     * @param throwable the throwable 有可能為空
  56.     * @param remark the remark 有可能為空
  57.     * @param isRemove
  58.     * @author tanyaowu
  59.     * @throws Exception
  60.     */
  61.     public void onBeforeClose ( ChannelContext channelContext , Throwable throwable , String remark , boolean isRemove ) throws Exception ;
  62.     /**
  63.     * 連線關閉前後觸發本方法
  64.     * 警告:走到這個裡面時,很多繫結的業務都已經解綁了,所以這個方法一般是空著不實現的
  65.     * @param channelContext the channelcontext
  66.     * @param throwable the throwable 有可能為空
  67.     * @param remark the remark 有可能為空
  68.     * @param isRemove 是否是刪除
  69.     * @throws Exception
  70.     * @author: tanyaowu
  71.     */
  72. //    public void onAfterClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
  73. }


伺服器端入口:TioServer


這個物件大家稍微瞭解一下即可,伺服器啟動時會用到這個物件,簡單貼一下它的原始碼吧,大家只需要關注它有一個start()方法是用來啟動網路服務的即可

  1. package org . tio . server ;
  2. import java . io . IOException ;
  3. import java . lang . management . ManagementFactory ;
  4. import java . lang . management . RuntimeMXBean ;
  5. import java . net . InetSocketAddress ;
  6. import java . net . StandardSocketOptions ;
  7. import java . nio . channels . AsynchronousChannelGroup ;
  8. import java . nio . channels . AsynchronousServerSocketChannel ;
  9. import java . util . ArrayList ;
  10. import java . util . Date ;
  11. import java . util . List ;
  12. import java . util . concurrent . TimeUnit ;
  13. import org . slf4j . Logger ;
  14. import org . slf4j . LoggerFactory ;
  15. import org . tio . core . Node ;
  16. import org . tio . utils . SysConst ;
  17. import org . tio . utils . date . DateUtils ;
  18. import org . tio . utils . hutool . StrUtil ;
  19. /**
  20. * @author tanyaowu
  21. *
  22. */
  23. public class TioServer {
  24.     private static Logger log = LoggerFactory . getLogger ( TioServer . class );
  25.     private ServerTioConfig serverTioConfig ;
  26.     private AsynchronousServerSocketChannel serverSocketChannel ;
  27.     private AsynchronousChannelGroup channelGroup = null ;
  28.     private Node serverNode ;
  29.     private boolean isWaitingStop = false ;
  30.     /**
  31.     *
  32.     * @param serverTioConfig
  33.     *
  34.     * @author tanyaowu
  35.     * 2017年1月2日 下午5:53:06
  36.     *
  37.     */
  38.     public TioServer ( ServerTioConfig serverTioConfig ) {
  39.         super ();
  40.         this . serverTioConfig = serverTioConfig ;
  41.     }
  42.     /**
  43.     * @return the serverTioConfig
  44.     */
  45.     public ServerTioConfig getServerTioConfig () {
  46.         return serverTioConfig ;
  47.     }
  48.     /**
  49.     * @return the serverNode
  50.     */
  51.     public Node getServerNode () {
  52.         return serverNode ;
  53.     }
  54.     /**
  55.     * @return the serverSocketChannel
  56.     */
  57.     public AsynchronousServerSocketChannel getServerSocketChannel () {
  58.         return serverSocketChannel ;
  59.     }
  60.     /**
  61.     * @return the isWaitingStop
  62.     */
  63.     public boolean isWaitingStop () {
  64.         return isWaitingStop ;
  65.     }
  66.     /**
  67.     * @param serverTioConfig the serverTioConfig to set
  68.     */
  69.     public void setServerTioConfig ( ServerTioConfig serverTioConfig ) {
  70.         this . serverTioConfig = serverTioConfig ;
  71.     }
  72.     /**
  73.     * @param isWaitingStop the isWaitingStop to set
  74.     */
  75.     public void setWaitingStop ( boolean isWaitingStop ) {
  76.         this . isWaitingStop = isWaitingStop ;
  77.     }
  78.     public void start ( String serverIp , int serverPort ) throws IOException {
  79.         long start = System . currentTimeMillis ();
  80.         this . serverNode = new Node ( serverIp , serverPort );
  81.        channelGroup = AsynchronousChannelGroup . withThreadPool ( serverTioConfig . groupExecutor );
  82.        serverSocketChannel = AsynchronousServerSocketChannel . open ( channelGroup );
  83.        serverSocketChannel . setOption ( StandardSocketOptions . SO_REUSEADDR , true );
  84.        serverSocketChannel . setOption ( StandardSocketOptions . SO_RCVBUF , 64 * 1024 );
  85.         InetSocketAddress listenAddress = null ;
  86.         if ( StrUtil . isBlank ( serverIp )) {
  87.            listenAddress = new InetSocketAddress ( serverPort );
  88.         } else {
  89.            listenAddress = new InetSocketAddress ( serverIp , serverPort );
  90.         }
  91.        serverSocketChannel . bind ( listenAddress , 0 );
  92.         AcceptCompletionHandler acceptCompletionHandler = serverTioConfig . getAcceptCompletionHandler ();
  93.        serverSocketChannel . accept ( this , acceptCompletionHandler );
  94.        serverTioConfig . startTime = System . currentTimeMillis ();
  95.         //下面這段程式碼有點無聊,寫得隨意,純粹是為了列印好看些
  96.         String baseStr = "|----------------------------------------------------------------------------------------|" ;
  97.         int baseLen = baseStr . length ();
  98.         StackTraceElement [] ses = Thread . currentThread (). getStackTrace ();
  99.         StackTraceElement se = ses [ ses . length - 1 ];
  100.         int xxLen = 18 ;
  101.         int aaLen = baseLen - 3 ;
  102.         List < String > infoList = new ArrayList <>();
  103.        infoList . add ( StrUtil . fillAfter ( "Tio gitee address" , ' ' , xxLen ) + "| " + SysConst . TIO_URL_GITEE );
  104.        infoList . add ( StrUtil . fillAfter ( "Tio site address" , ' ' , xxLen ) + "| " + SysConst . TIO_URL_SITE );
  105.        infoList . add ( StrUtil . fillAfter ( "Tio version" , ' ' , xxLen ) + "| " + SysConst . TIO_CORE_VERSION );
  106.        infoList . add ( StrUtil . fillAfter ( "-" , '-' , aaLen ));
  107.        infoList . add ( StrUtil . fillAfter ( "TioConfig name" , ' ' , xxLen ) + "| " + serverTioConfig . getName ());
  108.        infoList . add ( StrUtil . fillAfter ( "Started at" , ' ' , xxLen ) + "| " + DateUtils . formatDateTime ( new Date ()));
  109.        infoList . add ( StrUtil . fillAfter ( "Listen on" , ' ' , xxLen ) + "| " + this . serverNode );
  110.        infoList . add ( StrUtil . fillAfter ( "Main Class" , ' ' , xxLen ) + "| " + se . getClassName ());
  111.         try {
  112.             RuntimeMXBean runtimeMxBean = ManagementFactory . getRuntimeMXBean ();
  113.             String runtimeName = runtimeMxBean . getName ();
  114.             String pid = runtimeName . split ( "@" )[ 0 ];
  115.             long startTime = runtimeMxBean . getStartTime ();
  116.             long startCost = System . currentTimeMillis () - startTime ;
  117.            infoList . add ( StrUtil . fillAfter ( "Jvm start time" , ' ' , xxLen ) + "| " + startCost + " ms" );
  118.            infoList . add ( StrUtil . fillAfter ( "Tio start time" , ' ' , xxLen ) + "| " + ( System . currentTimeMillis () - start ) + " ms" );
  119.            infoList . add ( StrUtil . fillAfter ( "Pid" , ' ' , xxLen ) + "| " + pid );
  120.         } catch ( Exception e ) {
  121.         }
  122.         //100
  123.         String printStr = "\r\n" + baseStr + "\r\n" ;
  124.         //        printStr += "|--" + leftStr + " " + info + " " + rightStr + "--|\r\n";
  125.         for ( String string : infoList ) {
  126.            printStr += "| " + StrUtil . fillAfter ( string , ' ' , aaLen ) + "|\r\n" ;
  127.         }
  128.        printStr += baseStr + "\r\n" ;
  129.         if ( log . isInfoEnabled ()) {
  130.            log . info ( printStr );
  131.         } else {
  132.             System . out . println ( printStr );
  133.         }
  134.     }
  135.     /**
  136.     *
  137.     * @return
  138.     * @author tanyaowu
  139.     */
  140.     public boolean stop () {
  141.        isWaitingStop = true ;
  142.         boolean ret = true ;
  143.         try {
  144.            channelGroup . shutdownNow ();
  145.         } catch ( Exception e ) {
  146.            log . error ( "channelGroup.shutdownNow()時報錯" , e );
  147.         }
  148.         try {
  149.            serverSocketChannel . close ();
  150.         } catch ( Exception e1 ) {
  151.            log . error ( "serverSocketChannel.close()時報錯" , e1 );
  152.         }
  153.         try {
  154.            serverTioConfig . groupExecutor . shutdown ();
  155.         } catch ( Exception e1 ) {
  156.            log . error ( e1 . toString (), e1 );
  157.         }
  158.         try {
  159.            serverTioConfig . tioExecutor . shutdown ();
  160.         } catch ( Exception e1 ) {
  161.            log . error ( e1 . toString (), e1 );
  162.         }
  163.        serverTioConfig . setStopped ( true );
  164.         try {
  165.            ret = ret && serverTioConfig . groupExecutor . awaitTermination ( 6000 , TimeUnit . SECONDS );
  166.            ret = ret && serverTioConfig . tioExecutor . awaitTermination ( 6000 , TimeUnit . SECONDS );
  167.         } catch ( InterruptedException e ) {
  168.            log . error ( e . getLocalizedMessage (), e );
  169.         }
  170.        log . info ( this . serverNode + " stopped" );
  171.         return ret ;
  172.     }
  173. }


客戶端入口:TioClient


只有當你在用t-io作為TCP客戶端時,才用得到TioClient,此處簡單貼一下它的原始碼,它的用法,見後面的showcase示範工程

  1. package org . tio . client ;
  2. import java . io . IOException ;
  3. import java . net . InetSocketAddress ;
  4. import java . net . StandardSocketOptions ;
  5. import java . nio . channels . AsynchronousChannelGroup ;
  6. import java . nio . channels . AsynchronousSocketChannel ;
  7. import java . util . Set ;
  8. import java . util . concurrent . CountDownLatch ;
  9. import java . util . concurrent . LinkedBlockingQueue ;
  10. import java . util . concurrent . TimeUnit ;
  11. import java . util . concurrent . locks . ReentrantReadWriteLock ;
  12. import java . util . concurrent . locks . ReentrantReadWriteLock . ReadLock ;
  13. import java . util . concurrent . locks . ReentrantReadWriteLock . WriteLock ;
  14. import org . slf4j . Logger ;
  15. import org . slf4j . LoggerFactory ;
  16. import org . tio . client . intf . ClientAioHandler ;
  17. import org . tio . core . ChannelContext ;
  18. import org . tio . core . Node ;
  19. import org . tio . core . Tio ;
  20. import org . tio . core . intf . Packet ;
  21. import org . tio . core . ssl . SslFacadeContext ;
  22. import org . tio . core . stat . ChannelStat ;
  23. import org . tio . utils . SystemTimer ;
  24. import org . tio . utils . hutool . StrUtil ;
  25. import org . tio . utils . lock . SetWithLock ;
  26. /**
  27. *
  28. * @author tanyaowu
  29. * 2017年4月1日 上午9:29:58
  30. */
  31. public class TioClient {
  32.     /**
  33.     * 自動重連任務
  34.     * @author tanyaowu
  35.     *
  36.     */
  37.     private static class ReconnRunnable implements Runnable {
  38.         ClientChannelContext channelContext = null ;
  39.         TioClient tioClient = null ;
  40.         //        private static Map<Node, Long> cacheMap = new HashMap<>();
  41.         public ReconnRunnable ( ClientChannelContext channelContext , TioClient tioClient ) {
  42.             this . channelContext = channelContext ;
  43.             this . tioClient = tioClient ;
  44.         }
  45.         /**
  46.         * @see java.lang.Runnable#run()
  47.         *
  48.         * @author tanyaowu
  49.         * 2017年2月2日 下午8:24:40
  50.         *
  51.         */
  52.         @Override
  53.         public void run () {
  54.             ReentrantReadWriteLock closeLock = channelContext . closeLock ;
  55.             WriteLock writeLock = closeLock . writeLock ();
  56.            writeLock . lock ();
  57.             try {
  58.                 if (! channelContext . isClosed ) //已經連上了,不需要再重連了
  59.                 {
  60.                     return ;
  61.                 }
  62.                 long start = SystemTimer . currTime ;
  63.                tioClient . reconnect ( channelContext , 2 );
  64.                 long end = SystemTimer . currTime ;
  65.                 long iv = end - start ;
  66.                 if ( iv >= 100 ) {
  67.                    log . error ( "{},重連耗時:{} ms" , channelContext , iv );
  68.                 } else {
  69.                    log . info ( "{},重連耗時:{} ms" , channelContext , iv );
  70.                 }
  71.                 if ( channelContext . isClosed ) {
  72.                    channelContext . setReconnCount ( channelContext . getReconnCount () + 1 );
  73.                     //                    cacheMap.put(channelContext.getServerNode(), SystemTimer.currTime);
  74.                     return ;
  75.                 }
  76.             } catch ( java . lang . Throwable e ) {
  77.                log . error ( e . toString (), e );
  78.             } finally {
  79.                writeLock . unlock ();
  80.             }
  81.         }
  82.     }
  83.     private static Logger log = LoggerFactory . getLogger ( TioClient . class );
  84.     private AsynchronousChannelGroup channelGroup ;
  85.     private ClientTioConfig clientTioConfig ;
  86.     /**
  87.     * @param serverIp 可以為空
  88.     * @param serverPort
  89.     * @param aioDecoder
  90.     * @param aioEncoder
  91.     * @param aioHandler
  92.     *
  93.     * @author tanyaowu
  94.     * @throws IOException
  95.     *
  96.     */
  97.     public TioClient ( final ClientTioConfig clientTioConfig ) throws IOException {
  98.         super ();
  99.         this . clientTioConfig = clientTioConfig ;
  100.         this . channelGroup = AsynchronousChannelGroup . withThreadPool ( clientTioConfig . groupExecutor );
  101.        startHeartbeatTask ();
  102.        startReconnTask ();
  103.     }
  104.     /**
  105.     *
  106.     * @param serverNode
  107.     * @throws Exception
  108.     *
  109.     * @author tanyaowu
  110.     *
  111.     */
  112.     public void asynConnect ( Node serverNode ) throws Exception {
  113.        asynConnect ( serverNode , null );
  114.     }
  115.     /**
  116.     *
  117.     * @param serverNode
  118.     * @param timeout
  119.     * @throws Exception
  120.     *
  121.     * @author tanyaowu
  122.     *
  123.     */
  124.     public void asynConnect ( Node serverNode , Integer timeout ) throws Exception {
  125.        asynConnect ( serverNode , null , null , timeout );
  126.     }
  127.     /**
  128.     *
  129.     * @param serverNode
  130.     * @param bindIp
  131.     * @param bindPort
  132.     * @param timeout
  133.     * @throws Exception
  134.     *
  135.     * @author tanyaowu
  136.     *
  137.     */
  138.     public void asynConnect ( Node serverNode , String bindIp , Integer bindPort , Integer timeout ) throws Exception {
  139.        connect ( serverNode , bindIp , bindPort , null , timeout , false );
  140.     }
  141.     /**
  142.     *
  143.     * @param serverNode
  144.     * @return
  145.     * @throws Exception
  146.     *
  147.     * @author tanyaowu
  148.     *
  149.     */
  150.     public ClientChannelContext connect ( Node serverNode ) throws Exception {
  151.         return connect ( serverNode , null );
  152.     }
  153.     /**
  154.     *
  155.     * @param serverNode
  156.     * @param timeout
  157.     * @return
  158.     * @throws Exception
  159.     * @author tanyaowu
  160.     */
  161.     public ClientChannelContext connect ( Node serverNode , Integer timeout ) throws Exception {
  162.         return connect ( serverNode , null , 0 , timeout );
  163.     }
  164.     /**
  165.     *
  166.     * @param serverNode
  167.     * @param bindIp
  168.     * @param bindPort
  169.     * @param initClientChannelContext
  170.     * @param timeout 超時時間,單位秒
  171.     * @return
  172.     * @throws Exception
  173.     * @author tanyaowu
  174.     */
  175.     public ClientChannelContext connect ( Node serverNode , String bindIp , Integer bindPort , ClientChannelContext initClientChannelContext , Integer timeout ) throws Exception {
  176.         return connect ( serverNode , bindIp , bindPort , initClientChannelContext , timeout , true );
  177.     }
  178.     /**
  179.     *
  180.     * @param serverNode
  181.     * @param bindIp
  182.     * @param bindPort
  183.     * @param initClientChannelContext
  184.     * @param timeout 超時時間,單位秒
  185.     * @param isSyn true: 同步, false: 非同步
  186.     * @return
  187.     * @throws Exception
  188.     * @author tanyaowu
  189.     */
  190.     private ClientChannelContext connect ( Node serverNode , String bindIp , Integer bindPort , ClientChannelContext initClientChannelContext , Integer timeout , boolean isSyn )
  191.             throws Exception {
  192.         AsynchronousSocketChannel asynchronousSocketChannel = null ;
  193.         ClientChannelContext channelContext = null ;
  194.         boolean isReconnect = initClientChannelContext != null ;
  195.         //        ClientAioListener clientAioListener = clientTioConfig.getClientAioListener();
  196.         long start = SystemTimer . currTime ;
  197.        asynchronousSocketChannel = AsynchronousSocketChannel . open ( channelGroup );
  198.         long end = SystemTimer . currTime ;
  199.         long iv = end - start ;
  200.         if ( iv >= 100 ) {
  201.            log . error ( "{}, open 耗時:{} ms" , channelContext , iv );
  202.         }
  203.        asynchronousSocketChannel . setOption ( StandardSocketOptions . TCP_NODELAY , true );
  204.        asynchronousSocketChannel . setOption ( StandardSocketOptions . SO_REUSEADDR , true );
  205.        asynchronousSocketChannel . setOption ( StandardSocketOptions . SO_KEEPALIVE , true );
  206.         InetSocketAddress bind = null ;
  207.         if ( bindPort != null && bindPort > 0 ) {
  208.             if ( false == StrUtil . isBlank ( bindIp )) {
  209.                bind = new InetSocketAddress ( bindIp , bindPort );
  210.             } else {
  211.                bind = new InetSocketAddress ( bindPort );
  212.             }
  213.         }
  214.         if ( bind != null ) {
  215.            asynchronousSocketChannel . bind ( bind );
  216.         }
  217.        channelContext = initClientChannelContext ;
  218.        start = SystemTimer . currTime ;
  219.         InetSocketAddress inetSocketAddress = new InetSocketAddress ( serverNode . getIp (), serverNode . getPort ());
  220.         ConnectionCompletionVo attachment = new ConnectionCompletionVo ( channelContext , this , isReconnect , asynchronousSocketChannel , serverNode , bindIp , bindPort );
  221.         if ( isSyn ) {
  222.             Integer realTimeout = timeout ;
  223.             if ( realTimeout == null ) {
  224.                realTimeout = 5 ;
  225.             }
  226.             CountDownLatch countDownLatch = new CountDownLatch ( 1 );
  227.            attachment . setCountDownLatch ( countDownLatch );
  228.            asynchronousSocketChannel . connect ( inetSocketAddress , attachment , clientTioConfig . getConnectionCompletionHandler ());
  229.             boolean f = countDownLatch . await ( realTimeout , TimeUnit . SECONDS );
  230.             if ( f ) {
  231.                 return attachment . getChannelContext ();
  232.             } else {
  233.                log . error ( "countDownLatch.await(realTimeout, TimeUnit.SECONDS) 返回false " );
  234.                 return attachment . getChannelContext ();
  235.             }
  236.         } else {
  237.            asynchronousSocketChannel . connect ( inetSocketAddress , attachment , clientTioConfig . getConnectionCompletionHandler ());
  238.             return null ;
  239.         }
  240.     }
  241.     /**
  242.     *
  243.     * @param serverNode
  244.     * @param bindIp
  245.     * @param bindPort
  246.     * @param timeout 超時時間,單位秒
  247.     * @return
  248.     * @throws Exception
  249.     *
  250.     * @author tanyaowu
  251.     *
  252.     */
  253.     public ClientChannelContext connect ( Node serverNode , String bindIp , Integer bindPort , Integer timeout ) throws Exception {
  254.         return connect ( serverNode , bindIp , bindPort , null , timeout );
  255.     }
  256.     /**
  257.     * @return the channelGroup
  258.     */
  259.     public AsynchronousChannelGroup getChannelGroup () {
  260.         return channelGroup ;
  261.     }
  262.     /**
  263.     * @return the clientTioConfig
  264.     */
  265.     public ClientTioConfig getClientTioConfig () {
  266.         return clientTioConfig ;
  267.     }
  268.     /**
  269.     *
  270.     * @param channelContext
  271.     * @param timeout
  272.     * @return
  273.     * @throws Exception
  274.     *
  275.     * @author tanyaowu
  276.     *
  277.     */
  278.     public void reconnect ( ClientChannelContext channelContext , Integer timeout ) throws Exception {
  279.        connect ( channelContext . getServerNode (), channelContext . getBindIp (), channelContext . getBindPort (), channelContext , timeout );
  280.     }
  281.     /**
  282.     * @param clientTioConfig the clientTioConfig to set
  283.     */
  284.     public void setClientTioConfig ( ClientTioConfig clientTioConfig ) {
  285.         this . clientTioConfig = clientTioConfig ;
  286.     }
  287.     /**
  288.     * 定時任務:發心跳
  289.     * @author tanyaowu
  290.     *
  291.     */
  292.     private void startHeartbeatTask () {
  293.         final ClientGroupStat clientGroupStat = ( ClientGroupStat ) clientTioConfig . groupStat ;
  294.         final ClientAioHandler aioHandler = clientTioConfig . getClientAioHandler ();
  295.         final String id = clientTioConfig . getId ();
  296.         new Thread ( new Runnable () {
  297.             @Override
  298.             public void run () {
  299.                 while (! clientTioConfig . isStopped ()) {
  300. //                    final long heartbeatTimeout = clientTioConfig.heartbeatTimeout;
  301.                     if ( clientTioConfig . heartbeatTimeout <= 0 ) {
  302.                        log . warn ( "使用者取消了框架層面的心跳定時傳送功能,請使用者自己去完成心跳機制" );
  303.                         break ;
  304.                     }
  305.                     SetWithLock < ChannelContext > setWithLock = clientTioConfig . connecteds ;
  306.                     ReadLock readLock = setWithLock . readLock ();
  307.                    readLock . lock ();
  308.                     try {
  309.                         Set < ChannelContext > set = setWithLock . getObj ();
  310.                         long currtime = SystemTimer . currTime ;
  311.                         for ( ChannelContext entry : set ) {
  312.                             ClientChannelContext channelContext = ( ClientChannelContext ) entry ;
  313.                             if ( channelContext . isClosed || channelContext . isRemoved ) {
  314.                                 continue ;
  315.                             }
  316.                             ChannelStat stat = channelContext . stat ;
  317.                             long compareTime = Math . max ( stat . latestTimeOfReceivedByte , stat . latestTimeOfSentPacket );
  318.                             long interval = currtime - compareTime ;
  319.                             if ( interval >= clientTioConfig . heartbeatTimeout / 2 ) {
  320.                                 Packet packet = aioHandler . heartbeatPacket ( channelContext );
  321.                                 if ( packet != null ) {
  322.                                     if ( log . isInfoEnabled ()) {
  323.                                        log . info ( "{}傳送心跳包" , channelContext . toString ());
  324.                                     }
  325.                                     Tio . send ( channelContext , packet );
  326.                                 }
  327.                             }
  328.                         }
  329.                         if ( log . isInfoEnabled ()) {
  330.                            log . info ( "[{}]: curr:{}, closed:{}, received:({}p)({}b), handled:{}, sent:({}p)({}b)" , id , set . size (), clientGroupStat . closed . get (),
  331.                                    clientGroupStat . receivedPackets . get (), clientGroupStat . receivedBytes . get (), clientGroupStat . handledPackets . get (),
  332.                                    clientGroupStat . sentPackets . get (), clientGroupStat . sentBytes . get ());
  333.                         }
  334.                     } catch ( Throwable e ) {
  335.                        log . error ( "" , e );
  336.                     } finally {
  337.                         try {
  338.                            readLock . unlock ();
  339.                             Thread . sleep ( clientTioConfig . heartbeatTimeout / 4 );
  340.                         } catch ( Throwable e ) {
  341.                            log . error ( e . toString (), e );
  342.                         } finally {
  343.                         }
  344.                     }
  345.                 }
  346.             }
  347.         }, "tio-timer-heartbeat" + id ). start ();
  348.     }
  349.     /**
  350.     * 啟動重連任務
  351.     *
  352.     *
  353.     * @author tanyaowu
  354.     *
  355.     */
  356.     private void startReconnTask () {
  357.         final ReconnConf reconnConf = clientTioConfig . getReconnConf ();
  358.         if ( reconnConf == null || reconnConf . getInterval () <= 0 ) {
  359.             return ;
  360.         }
  361.         final String id = clientTioConfig . getId ();
  362.         Thread thread = new Thread ( new Runnable () {
  363.             @Override
  364.             public void run () {
  365.                 while (! clientTioConfig . isStopped ()) {
  366.                     //log.info("準備重連");
  367.                     LinkedBlockingQueue < ChannelContext > queue = reconnConf . getQueue ();
  368.                     ClientChannelContext channelContext = null ;
  369.                     try {
  370.                        channelContext = ( ClientChannelContext ) queue . take ();
  371.                     } catch ( InterruptedException e1 ) {
  372.                        log . error ( e1 . toString (), e1 );
  373.                     }
  374.                     if ( channelContext == null ) {
  375.                         continue ;
  376.                         //                        return;
  377.                     }
  378.                     if ( channelContext . isRemoved ) //已經刪除的,不需要重新再連
  379.                     {
  380.                         continue ;
  381.                     }
  382.                     SslFacadeContext sslFacadeContext = channelContext . sslFacadeContext ;
  383.                     if ( sslFacadeContext != null ) {
  384.                        sslFacadeContext . setHandshakeCompleted ( false );
  385.                     }
  386.                     long sleeptime = reconnConf . getInterval () - ( SystemTimer . currTime - channelContext . stat . timeInReconnQueue );
  387.                     //log.info("sleeptime:{}, closetime:{}", sleeptime, timeInReconnQueue);
  388.                     if ( sleeptime > 0 ) {
  389.                         try {
  390.                             Thread . sleep ( sleeptime );
  391.                         } catch ( InterruptedException e ) {
  392.                            log . error ( e . toString (), e );
  393.                         }
  394.                     }
  395.                     if ( channelContext . isRemoved || ! channelContext . isClosed ) //已經刪除的和已經連上的,不需要重新再連
  396.                     {
  397.                         continue ;
  398.                     }
  399.                     ReconnRunnable runnable = new ReconnRunnable ( channelContext , TioClient . this );
  400.                    reconnConf . getThreadPoolExecutor (). execute ( runnable );
  401.                 }
  402.             }
  403.         });
  404.        thread . setName ( "tio-timer-reconnect-" + id );
  405.        thread . setDaemon ( true );
  406.        thread . start ();
  407.     }
  408.     /**
  409.     *
  410.     * @return
  411.     * @author tanyaowu
  412.     */
  413.     public boolean stop () {
  414.         boolean ret = true ;
  415.         try {
  416.            clientTioConfig . groupExecutor . shutdown ();
  417.         } catch ( Exception e1 ) {
  418.            log . error ( e1 . toString (), e1 );
  419.         }
  420.         try {
  421.            clientTioConfig . tioExecutor . shutdown ();
  422.         } catch ( Exception e1 ) {
  423.            log . error ( e1 . toString (), e1 );
  424.         }
  425.        clientTioConfig . setStopped ( true );
  426.         try {
  427.            ret = ret && clientTioConfig . groupExecutor . awaitTermination ( 6000 , TimeUnit . SECONDS );
  428.            ret = ret && clientTioConfig . tioExecutor . awaitTermination ( 6000 , TimeUnit . SECONDS );
  429.         } catch ( InterruptedException e ) {
  430.            log . error ( e . getLocalizedMessage (), e );
  431.         }
  432.        log . info ( "client resource has released" );
  433.         return ret ;
  434.     }
  435. }







來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70006579/viewspace-2846027/,如需轉載,請註明出處,否則將追究法律責任。

相關文章