Introduction to basic programming knowledge of network programming framework t-io

Keywords: Java Android Back-end

As the most popular open source network programming framework software in China, t-io is famous for its simplicity and ease of use. Compared with netty, the same function is much simpler and the amount of code is greatly reduced. If you want to use t-io well, you should first learn some basic knowledge of t-io. This article mainly introduces the basic knowledge of t-io from eight aspects. Please refer to:
https://www.wanetech.com/doc/tio/88

t-io messaging process

t-io sending and receiving messages and processing process can be clearly expressed in a picture

Application layer package: Packet

Packet is used to express the business data structure. We implement our own business data structure by inheriting packet. For you, packet can be regarded as an ordinary VO object.

Note: it is not recommended to use the Packet object directly, but to inherit the Packet

A simple Packet may look like this

package org.tio.study.helloworld.common;
import org.tio.core.intf.Packet;
/**
 * @author tanyaowu
 */
public class HelloPacket extends Packet {
    private static final long serialVersionUID = -172060606924066412L;
    public static final int HEADER_LENGTH = 4;//Length of message header
    public static final String CHARSET = "utf-8";
    private byte[] body;
    /**
     * @return the body
     */
    public byte[] getBody() {
        return body;
    }
    /**
     * @param body the body to set
     */
    public void setBody(byte[] body) {
        this.body = body;
    }
}

Packet s can be understood in combination with AioHandler.java

package org.tio.core.intf;
import java.nio.ByteBuffer;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
/**
 * 
 * @author tanyaowu 
 * 2017 9:40:15 a.m. on October 19
 */
public interface AioHandler {
    /**
     * Decode into the Packet object required by the service according to ByteBuffer
     * If the received data is incomplete, resulting in decoding failure, please return null. The framework layer will automatically continue the previous received data when the next message comes
     * @param buffer Participate in the ByteBuffer that you want to decode this time
     * @param limit ByteBuffer limit of
     * @param position ByteBuffer The position of is not necessarily 0
     * @param readableLength ByteBuffer Valid data participating in this decoding (= limit - position)
     * @param channelContext
     * @return
     * @throws AioDecodeException
     */
    Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException;
    /**
     * code
     * @param packet
     * @param tioConfig
     * @param channelContext
     * @return
     * @author: tanyaowu
     */
    ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext);
    /**
     * Processing message packets
     * @param packet
     * @param channelContext
     * @throws Exception
     * @author: tanyaowu
     */
    void handler(Packet packet, ChannelContext channelContext) throws Exception;
}

Single TCP connection context: ChannelContext

Each tcp connection will generate a ChannelContext object, which is an abstract class. If you use t-io as the tcp client, it is ClientChannelContext. If you use tio as the tcp server, it is ServerChannelContext

The user can associate the business data with the TCP connection through the ChannelContext object, and set the properties as follows

ChannelContext.set(String key, Object value)

Then get the properties in the following way

ChannelContext.get(String key)

Of course, the most common is to use the powerful bind function provided by t-io, such as binding userid with the following code

Tio.bindUser(ChannelContext channelContext, String userid)

Then you can operate through userid. The example code is as follows

//Get the ChannelContext collection of a user
SetWithLock<ChannelContext> set = Tio.getChannelContextsByUserid(tioConfig, userid);
//Send a message to a user
Tio.sendToUser(TioConfig, userid, Packet)

In addition to binding userid s, t-io has built-in binding API s as follows

Binding service id

Tio.bindBsId(ChannelContext channelContext, String bsId)

Binding token

Tio.bindToken(ChannelContext channelContext, String token)

Bind group

Tio.bindGroup(ChannelContext channelContext, String group)

The ChannelContext object contains a lot of information. The main objects are shown in the figure below

explain
ChannelContext is a very important class in t-io. It is a communication bridge between business and connection!

Service configuration and maintenance: TioConfig

Scenario: when writing a TCP Server, we will first select a port to listen to client connections, and then create N groups of thread pools to perform related tasks, such as sending messages, decoding data packets, processing data packets, etc. we also need to maintain various data connected by the client. In order to interact with the business, we also need to bind these client connections with various business data, For example, bind a client to a group, a userid, a token, etc.
TioConfig solves the above scenarios: configuring thread pools, listening ports, and maintaining various client data.

TioConfig is an abstract class

If you use tio as a tcp client, you need to create a ClientTioConfig object
The server side corresponds to a ClientTioConfig object
If you are using tio as a tcp server, you need to create ServerTioConfig
A listening port corresponds to a ServerTioConfig. A jvm can listen to multiple ports, so a jvm can have multiple ServerTioConfig objects
The TioConfig object contains a lot of information. The main objects are shown in the figure below

How to get the TioConfig object
See: https://www.wanetech.com/doc/...

Encoding, decoding and processing: AioHandler

AioHandler is the core interface for processing messages. It has two sub interfaces, ClientAioHandler and ServerAioHandler. When tio is used as tcp client, ClientAioHandler needs to be implemented, and when tio is used as tcp server, ServerAioHandler needs to be implemented. It mainly defines three methods, as shown below

package org.tio.core.intf;
import java.nio.ByteBuffer;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
/**
 * 
 * @author tanyaowu 
 * 2017 9:40:15 a.m. on October 19
 */
public interface AioHandler {
    /**
     * Decode into the Packet object required by the service according to ByteBuffer
     * If the received data is incomplete, resulting in decoding failure, please return null. The framework layer will automatically continue the previous received data when the next message comes
     * @param buffer Participate in the ByteBuffer that you want to decode this time
     * @param limit ByteBuffer limit of
     * @param position ByteBuffer The position of is not necessarily 0
     * @param readableLength ByteBuffer Valid data participating in this decoding (= limit - position)
     * @param channelContext
     * @return
     * @throws AioDecodeException
     */
    Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException;
    /**
     * code
     * @param packet
     * @param tioConfig
     * @param channelContext
     * @return
     * @author: tanyaowu
     */
    ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext);
    /**
     * Processing message packets
     * @param packet
     * @param channelContext
     * @throws Exception
     * @author: tanyaowu
     */
    void handler(Packet packet, ChannelContext channelContext) throws Exception;
}

Message traffic monitoring: AioListener

AioListener is the core interface for processing messages. It has two sub interfaces: clientaiistener and ServerAioListener

When tio is used as the tcp client, the ClientAioListener needs to be implemented
When using tio as a tcp server, you need to implement ServerAioListener
It mainly defines the following methods

package org.tio.core.intf;
import org.tio.core.ChannelContext;
/**
 *
 * @author tanyaowu
 * 2017 9:34:08 am, April 1
 */
public interface AioListener {
    /**
     * This method is triggered after chain building. Note: chain building may not be successful, and the parameter isConnected needs to be paid attention to
     * @param channelContext
     * @param isConnected Whether the connection is successful. true: indicates that the connection is successful, false: indicates that the connection failed
     * @param isReconnect Whether it is reconnection. true: indicates that this is reconnection, false: indicates that this is the first connection
     * @throws Exception
     * @author: tanyaowu
     */
    public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception;
    /**
     * Original method name: onAfterDecoded
     * This method is triggered after successful decoding
     * @param channelContext
     * @param packet
     * @param packetSize
     * @throws Exception
     * @author: tanyaowu
     */
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception;
    /**
     * After receiving the data from the TCP layer
     * @param channelContext
     * @param receivedBytes How many bytes are received this time
     * @throws Exception
     */
    public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception;
    /**
     * This method is triggered after the message packet is sent
     * @param channelContext
     * @param packet
     * @param isSentSuccess true:Sending succeeded, false: sending failed
     * @throws Exception
     * @author tanyaowu
     */
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception;
    /**
     * After processing a message packet
     * @param channelContext
     * @param packet
     * @param cost The time taken to process the message this time, unit: ms
     * @throws Exception
     */
    public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception;
    /**
     * This method is triggered before the connection is closed
     * @param channelContext the channelcontext
     * @param throwable the throwable May be empty
     * @param remark the remark May be empty
     * @param isRemove
     * @author tanyaowu
     * @throws Exception 
     */
    public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
    /**
     * This method is triggered before and after the connection is closed
     * Warning: many bound businesses have been unbound when you enter this, so this method is generally empty and not implemented
     * @param channelContext the channelcontext
     * @param throwable the throwable May be empty
     * @param remark the remark May be empty
     * @param isRemove Delete
     * @throws Exception
     * @author: tanyaowu
     */
//    public void onAfterClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
}

Server side portal: TioServer

You can understand this object a little. This object will be used when the server starts. Simply post its source code. You only need to pay attention to it. There is a start() method to start the network service

package org.tio.server;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.Node;
import org.tio.utils.SysConst;
import org.tio.utils.date.DateUtils;
import org.tio.utils.hutool.StrUtil;
/**
 * @author tanyaowu
 *
 */
public class TioServer {
    private static Logger log = LoggerFactory.getLogger(TioServer.class);
    private ServerTioConfig serverTioConfig;
    private AsynchronousServerSocketChannel serverSocketChannel;
    private AsynchronousChannelGroup channelGroup = null;
    private Node serverNode;
    private boolean isWaitingStop = false;
    /**
     *
     * @param serverTioConfig
     *
     * @author tanyaowu
     * 2017 5:53:06 PM, January 2
     *
     */
    public TioServer(ServerTioConfig serverTioConfig) {
        super();
        this.serverTioConfig = serverTioConfig;
    }
    /**
     * @return the serverTioConfig
     */
    public ServerTioConfig getServerTioConfig() {
        return serverTioConfig;
    }
    /**
     * @return the serverNode
     */
    public Node getServerNode() {
        return serverNode;
    }
    /**
     * @return the serverSocketChannel
     */
    public AsynchronousServerSocketChannel getServerSocketChannel() {
        return serverSocketChannel;
    }
    /**
     * @return the isWaitingStop
     */
    public boolean isWaitingStop() {
        return isWaitingStop;
    }
    /**
     * @param serverTioConfig the serverTioConfig to set
     */
    public void setServerTioConfig(ServerTioConfig serverTioConfig) {
        this.serverTioConfig = serverTioConfig;
    }
    /**
     * @param isWaitingStop the isWaitingStop to set
     */
    public void setWaitingStop(boolean isWaitingStop) {
        this.isWaitingStop = isWaitingStop;
    }
    public void start(String serverIp, int serverPort) throws IOException {
        long start = System.currentTimeMillis();
        this.serverNode = new Node(serverIp, serverPort);
        channelGroup = AsynchronousChannelGroup.withThreadPool(serverTioConfig.groupExecutor);
        serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
        serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 64 * 1024);
        InetSocketAddress listenAddress = null;
        if (StrUtil.isBlank(serverIp)) {
            listenAddress = new InetSocketAddress(serverPort);
        } else {
            listenAddress = new InetSocketAddress(serverIp, serverPort);
        }
        serverSocketChannel.bind(listenAddress, 0);
        AcceptCompletionHandler acceptCompletionHandler = serverTioConfig.getAcceptCompletionHandler();
        serverSocketChannel.accept(this, acceptCompletionHandler);
        serverTioConfig.startTime = System.currentTimeMillis();
        //The following code is a little boring and written casually, just to print better
        String baseStr = "|----------------------------------------------------------------------------------------|";
        int baseLen = baseStr.length();
        StackTraceElement[] ses = Thread.currentThread().getStackTrace();
        StackTraceElement se = ses[ses.length - 1];
        int xxLen = 18;
        int aaLen = baseLen - 3;
        List<String> infoList = new ArrayList<>();
        infoList.add(StrUtil.fillAfter("Tio gitee address", ' ', xxLen) + "| " + SysConst.TIO_URL_GITEE);
        infoList.add(StrUtil.fillAfter("Tio site address", ' ', xxLen) + "| " + SysConst.TIO_URL_SITE);
        infoList.add(StrUtil.fillAfter("Tio version", ' ', xxLen) + "| " + SysConst.TIO_CORE_VERSION);
        infoList.add(StrUtil.fillAfter("-", '-', aaLen));
        infoList.add(StrUtil.fillAfter("TioConfig name", ' ', xxLen) + "| " + serverTioConfig.getName());
        infoList.add(StrUtil.fillAfter("Started at", ' ', xxLen) + "| " + DateUtils.formatDateTime(new Date()));
        infoList.add(StrUtil.fillAfter("Listen on", ' ', xxLen) + "| " + this.serverNode);
        infoList.add(StrUtil.fillAfter("Main Class", ' ', xxLen) + "| " + se.getClassName());
        try {
            RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
            String runtimeName = runtimeMxBean.getName();
            String pid = runtimeName.split("@")[0];
            long startTime = runtimeMxBean.getStartTime();
            long startCost = System.currentTimeMillis() - startTime;
            infoList.add(StrUtil.fillAfter("Jvm start time", ' ', xxLen) + "| " + startCost + " ms");
            infoList.add(StrUtil.fillAfter("Tio start time", ' ', xxLen) + "| " + (System.currentTimeMillis() - start) + " ms");
            infoList.add(StrUtil.fillAfter("Pid", ' ', xxLen) + "| " + pid);
        } catch (Exception e) {
        }
        //100
        String printStr = "\r\n"+baseStr+"\r\n";
        //        printStr += "|--" + leftStr + " " + info + " " + rightStr + "--|\r\n";
        for (String string : infoList) {
            printStr += "| " + StrUtil.fillAfter(string, ' ', aaLen) + "|\r\n";
        }
        printStr += baseStr + "\r\n";
        if (log.isInfoEnabled()) {
            log.info(printStr);
        } else {
            System.out.println(printStr);
        }
    }
    /**
     * 
     * @return
     * @author tanyaowu
     */
    public boolean stop() {
        isWaitingStop = true;
        boolean ret = true;
        try {
            channelGroup.shutdownNow();
        } catch (Exception e) {
            log.error("channelGroup.shutdownNow()Times error", e);
        }
        try {
            serverSocketChannel.close();
        } catch (Exception e1) {
            log.error("serverSocketChannel.close()Times error", e1);
        }
        try {
            serverTioConfig.groupExecutor.shutdown();
        } catch (Exception e1) {
            log.error(e1.toString(), e1);
        }
        try {
            serverTioConfig.tioExecutor.shutdown();
        } catch (Exception e1) {
            log.error(e1.toString(), e1);
        }
        serverTioConfig.setStopped(true);
        try {
            ret = ret && serverTioConfig.groupExecutor.awaitTermination(6000, TimeUnit.SECONDS);
            ret = ret && serverTioConfig.tioExecutor.awaitTermination(6000, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        log.info(this.serverNode + " stopped");
        return ret;
    }
}

Client entry: TioClient

Only when you use t-io as the TCP client can you get the TioClient. Here you can simply post its source code and its usage. See the following showcase demonstration project

package org.tio.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.client.intf.ClientAioHandler;
import org.tio.core.ChannelContext;
import org.tio.core.Node;
import org.tio.core.Tio;
import org.tio.core.intf.Packet;
import org.tio.core.ssl.SslFacadeContext;
import org.tio.core.stat.ChannelStat;
import org.tio.utils.SystemTimer;
import org.tio.utils.hutool.StrUtil;
import org.tio.utils.lock.SetWithLock;
/**
 *
 * @author tanyaowu
 * 2017 9:29:58 am, April 1
 */
public class TioClient {
    /**
     * Automatic reconnection task
     * @author tanyaowu
     *
     */
    private static class ReconnRunnable implements Runnable {
        ClientChannelContext channelContext = null;
        TioClient tioClient = null;
        //        private static Map<Node, Long> cacheMap = new HashMap<>();
        public ReconnRunnable(ClientChannelContext channelContext, TioClient tioClient) {
            this.channelContext = channelContext;
            this.tioClient = tioClient;
        }
        /**
         * @see java.lang.Runnable#run()
         *
         * @author tanyaowu
         * 2017 February 2, 2008 8:24:40 PM
         *
         */
        @Override
        public void run() {
            ReentrantReadWriteLock closeLock = channelContext.closeLock;
            WriteLock writeLock = closeLock.writeLock();
            writeLock.lock();
            try {
                if (!channelContext.isClosed) //It's already connected. There's no need to reconnect
                {
                    return;
                }
                long start = SystemTimer.currTime;
                tioClient.reconnect(channelContext, 2);
                long end = SystemTimer.currTime;
                long iv = end - start;
                if (iv >= 100) {
                    log.error("{},Reconnection time:{} ms", channelContext, iv);
                } else {
                    log.info("{},Reconnection time:{} ms", channelContext, iv);
                }
                if (channelContext.isClosed) {
                    channelContext.setReconnCount(channelContext.getReconnCount() + 1);
                    //                    cacheMap.put(channelContext.getServerNode(), SystemTimer.currTime);
                    return;
                }
            } catch (java.lang.Throwable e) {
                log.error(e.toString(), e);
            } finally {
                writeLock.unlock();
            }
        }
    }
    private static Logger log = LoggerFactory.getLogger(TioClient.class);
    private AsynchronousChannelGroup channelGroup;
    private ClientTioConfig clientTioConfig;
    /**
     * @param serverIp Can be empty
     * @param serverPort
     * @param aioDecoder
     * @param aioEncoder
     * @param aioHandler
     *
     * @author tanyaowu
     * @throws IOException
     *
     */
    public TioClient(final ClientTioConfig clientTioConfig) throws IOException {
        super();
        this.clientTioConfig = clientTioConfig;
        this.channelGroup = AsynchronousChannelGroup.withThreadPool(clientTioConfig.groupExecutor);
        startHeartbeatTask();
        startReconnTask();
    }
    /**
     *
     * @param serverNode
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public void asynConnect(Node serverNode) throws Exception {
        asynConnect(serverNode, null);
    }
    /**
     *
     * @param serverNode
     * @param timeout
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public void asynConnect(Node serverNode, Integer timeout) throws Exception {
        asynConnect(serverNode, null, null, timeout);
    }
    /**
     *
     * @param serverNode
     * @param bindIp
     * @param bindPort
     * @param timeout
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public void asynConnect(Node serverNode, String bindIp, Integer bindPort, Integer timeout) throws Exception {
        connect(serverNode, bindIp, bindPort, null, timeout, false);
    }
    /**
     *
     * @param serverNode
     * @return
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public ClientChannelContext connect(Node serverNode) throws Exception {
        return connect(serverNode, null);
    }
    /**
     *
     * @param serverNode
     * @param timeout
     * @return
     * @throws Exception
     * @author tanyaowu
     */
    public ClientChannelContext connect(Node serverNode, Integer timeout) throws Exception {
        return connect(serverNode, null, 0, timeout);
    }
    /**
     *
     * @param serverNode
     * @param bindIp
     * @param bindPort
     * @param initClientChannelContext
     * @param timeout Timeout in seconds
     * @return
     * @throws Exception
     * @author tanyaowu
     */
    public ClientChannelContext connect(Node serverNode, String bindIp, Integer bindPort, ClientChannelContext initClientChannelContext, Integer timeout) throws Exception {
        return connect(serverNode, bindIp, bindPort, initClientChannelContext, timeout, true);
    }
    /**
     *
     * @param serverNode
     * @param bindIp
     * @param bindPort
     * @param initClientChannelContext
     * @param timeout Timeout in seconds
     * @param isSyn true: Synchronous, false: asynchronous
     * @return
     * @throws Exception
     * @author tanyaowu
     */
    private ClientChannelContext connect(Node serverNode, String bindIp, Integer bindPort, ClientChannelContext initClientChannelContext, Integer timeout, boolean isSyn)
            throws Exception {
        AsynchronousSocketChannel asynchronousSocketChannel = null;
        ClientChannelContext channelContext = null;
        boolean isReconnect = initClientChannelContext != null;
        //        ClientAioListener clientAioListener = clientTioConfig.getClientAioListener();
        long start = SystemTimer.currTime;
        asynchronousSocketChannel = AsynchronousSocketChannel.open(channelGroup);
        long end = SystemTimer.currTime;
        long iv = end - start;
        if (iv >= 100) {
            log.error("{}, open time consuming:{} ms", channelContext, iv);
        }
        asynchronousSocketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
        asynchronousSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        asynchronousSocketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
        InetSocketAddress bind = null;
        if (bindPort != null && bindPort > 0) {
            if (false == StrUtil.isBlank(bindIp)) {
                bind = new InetSocketAddress(bindIp, bindPort);
            } else {
                bind = new InetSocketAddress(bindPort);
            }
        }
        if (bind != null) {
            asynchronousSocketChannel.bind(bind);
        }
        channelContext = initClientChannelContext;
        start = SystemTimer.currTime;
        InetSocketAddress inetSocketAddress = new InetSocketAddress(serverNode.getIp(), serverNode.getPort());
        ConnectionCompletionVo attachment = new ConnectionCompletionVo(channelContext, this, isReconnect, asynchronousSocketChannel, serverNode, bindIp, bindPort);
        if (isSyn) {
            Integer realTimeout = timeout;
            if (realTimeout == null) {
                realTimeout = 5;
            }
            CountDownLatch countDownLatch = new CountDownLatch(1);
            attachment.setCountDownLatch(countDownLatch);
            asynchronousSocketChannel.connect(inetSocketAddress, attachment, clientTioConfig.getConnectionCompletionHandler());
            boolean f = countDownLatch.await(realTimeout, TimeUnit.SECONDS);
            if (f) {
                return attachment.getChannelContext();
            } else {
                log.error("countDownLatch.await(realTimeout, TimeUnit.SECONDS) return false ");
                return attachment.getChannelContext();
            }
        } else {
            asynchronousSocketChannel.connect(inetSocketAddress, attachment, clientTioConfig.getConnectionCompletionHandler());
            return null;
        }
    }
    /**
     *
     * @param serverNode
     * @param bindIp
     * @param bindPort
     * @param timeout Timeout in seconds
     * @return
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public ClientChannelContext connect(Node serverNode, String bindIp, Integer bindPort, Integer timeout) throws Exception {
        return connect(serverNode, bindIp, bindPort, null, timeout);
    }
    /**
     * @return the channelGroup
     */
    public AsynchronousChannelGroup getChannelGroup() {
        return channelGroup;
    }
    /**
     * @return the clientTioConfig
     */
    public ClientTioConfig getClientTioConfig() {
        return clientTioConfig;
    }
    /**
     *
     * @param channelContext
     * @param timeout
     * @return
     * @throws Exception
     *
     * @author tanyaowu
     *
     */
    public void reconnect(ClientChannelContext channelContext, Integer timeout) throws Exception {
        connect(channelContext.getServerNode(), channelContext.getBindIp(), channelContext.getBindPort(), channelContext, timeout);
    }
    /**
     * @param clientTioConfig the clientTioConfig to set
     */
    public void setClientTioConfig(ClientTioConfig clientTioConfig) {
        this.clientTioConfig = clientTioConfig;
    }
    /**
     * Scheduled task: send heartbeat
     * @author tanyaowu
     *
     */
    private void startHeartbeatTask() {
        final ClientGroupStat clientGroupStat = (ClientGroupStat)clientTioConfig.groupStat;
        final ClientAioHandler aioHandler = clientTioConfig.getClientAioHandler();
        final String id = clientTioConfig.getId();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!clientTioConfig.isStopped()) {
//                    final long heartbeatTimeout = clientTioConfig.heartbeatTimeout;
                    if (clientTioConfig.heartbeatTimeout <= 0) {
                        log.warn("The user cancelled the heartbeat timing sending function at the framework level, and asked the user to complete the heartbeat mechanism by himself");
                        break;
                    }
                    SetWithLock<ChannelContext> setWithLock = clientTioConfig.connecteds;
                    ReadLock readLock = setWithLock.readLock();
                    readLock.lock();
                    try {
                        Set<ChannelContext> set = setWithLock.getObj();
                        long currtime = SystemTimer.currTime;
                        for (ChannelContext entry : set) {
                            ClientChannelContext channelContext = (ClientChannelContext) entry;
                            if (channelContext.isClosed || channelContext.isRemoved) {
                                continue;
                            }
                            ChannelStat stat = channelContext.stat;
                            long compareTime = Math.max(stat.latestTimeOfReceivedByte, stat.latestTimeOfSentPacket);
                            long interval = currtime - compareTime;
                            if (interval >= clientTioConfig.heartbeatTimeout / 2) {
                                Packet packet = aioHandler.heartbeatPacket(channelContext);
                                if (packet != null) {
                                    if (log.isInfoEnabled()) {
                                        log.info("{}Send heartbeat packet", channelContext.toString());
                                    }
                                    Tio.send(channelContext, packet);
                                }
                            }
                        }
                        if (log.isInfoEnabled()) {
                            log.info("[{}]: curr:{}, closed:{}, received:({}p)({}b), handled:{}, sent:({}p)({}b)", id, set.size(), clientGroupStat.closed.get(),
                                    clientGroupStat.receivedPackets.get(), clientGroupStat.receivedBytes.get(), clientGroupStat.handledPackets.get(),
                                    clientGroupStat.sentPackets.get(), clientGroupStat.sentBytes.get());
                        }
                    } catch (Throwable e) {
                        log.error("", e);
                    } finally {
                        try {
                            readLock.unlock();
                            Thread.sleep(clientTioConfig.heartbeatTimeout / 4);
                        } catch (Throwable e) {
                            log.error(e.toString(), e);
                        } finally {
                        }
                    }
                }
            }
        }, "tio-timer-heartbeat" + id).start();
    }
    /**
     * Start reconnection task
     *
     *
     * @author tanyaowu
     *
     */
    private void startReconnTask() {
        final ReconnConf reconnConf = clientTioConfig.getReconnConf();
        if (reconnConf == null || reconnConf.getInterval() <= 0) {
            return;
        }
        final String id = clientTioConfig.getId();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!clientTioConfig.isStopped()) {
                    //log.info("prepare for reconnection");
                    LinkedBlockingQueue<ChannelContext> queue = reconnConf.getQueue();
                    ClientChannelContext channelContext = null;
                    try {
                        channelContext = (ClientChannelContext) queue.take();
                    } catch (InterruptedException e1) {
                        log.error(e1.toString(), e1);
                    }
                    if (channelContext == null) {
                        continue;
                        //                        return;
                    }
                    if (channelContext.isRemoved) //Deleted items do not need to be reconnected
                    {
                        continue;
                    }
                    SslFacadeContext sslFacadeContext = channelContext.sslFacadeContext;
                    if (sslFacadeContext != null) {
                        sslFacadeContext.setHandshakeCompleted(false);
                    }
                    long sleeptime = reconnConf.getInterval() - (SystemTimer.currTime - channelContext.stat.timeInReconnQueue);
                    //log.info("sleeptime:{}, closetime:{}", sleeptime, timeInReconnQueue);
                    if (sleeptime > 0) {
                        try {
                            Thread.sleep(sleeptime);
                        } catch (InterruptedException e) {
                            log.error(e.toString(), e);
                        }
                    }
                    if (channelContext.isRemoved || !channelContext.isClosed) //Deleted and connected items do not need to be connected again
                    {
                        continue;
                    }
                    ReconnRunnable runnable = new ReconnRunnable(channelContext, TioClient.this);
                    reconnConf.getThreadPoolExecutor().execute(runnable);
                }
            }
        });
        thread.setName("tio-timer-reconnect-" + id);
        thread.setDaemon(true);
        thread.start();
    }
    /**
     * 
     * @return
     * @author tanyaowu
     */
    public boolean stop() {
        boolean ret = true;
        try {
            clientTioConfig.groupExecutor.shutdown();
        } catch (Exception e1) {
            log.error(e1.toString(), e1);
        }
        try {
            clientTioConfig.tioExecutor.shutdown();
        } catch (Exception e1) {
            log.error(e1.toString(), e1);
        }
        clientTioConfig.setStopped(true);
        try {
            ret = ret && clientTioConfig.groupExecutor.awaitTermination(6000, TimeUnit.SECONDS);
            ret = ret && clientTioConfig.tioExecutor.awaitTermination(6000, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        log.info("client resource has released");
        return ret;
    }
}

Posted by Hipster on Mon, 06 Dec 2021 19:07:19 -0800