t-io作為目前國內最流行的開源網路程式設計框架軟體,以簡單易懂,上手容易而著稱,相同的功能比起netty實現起來,要簡單的多,程式碼量也大大減少,如果要使用好t-io,還是要先學習t-io的一些基本知識,這篇文章主要從8個方面介紹了t-io的基礎知識。 具體請參考:
https://www.wanetech.com/doc/tio/88
t-io收發訊息過程
t-io收發訊息及處理過程,可以用一張圖清晰地表達出來
應用層包:Packet
Packet是用於表述業務資料結構的,我們通過繼承Packet來實現自己的業務資料結構,對於各位而言,把Packet看作是一個普通的VO物件即可。
注意:不建議直接使用Packet物件,而是要繼承Packet
一個簡單的Packet可能長這樣
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;//訊息頭的長度
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;
}
}
可以結合AioHandler.java理解Packet
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年10月19日 上午9:40:15
*/
public interface AioHandler {
/**
* 根據ByteBuffer解碼成業務需要的Packet物件.
* 如果收到的資料不全,導致解碼失敗,請返回null,在下次訊息來時框架層會自動續上前面的收到的資料
* @param buffer 參與本次希望解碼的ByteBuffer
* @param limit ByteBuffer的limit
* @param position ByteBuffer的position,不一定是0哦
* @param readableLength ByteBuffer參與本次解碼的有效資料(= limit - position)
* @param channelContext
* @return
* @throws AioDecodeException
*/
Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException;
/**
* 編碼
* @param packet
* @param tioConfig
* @param channelContext
* @return
* @author: tanyaowu
*/
ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext);
/**
* 處理訊息包
* @param packet
* @param channelContext
* @throws Exception
* @author: tanyaowu
*/
void handler(Packet packet, ChannelContext channelContext) throws Exception;
}
單條TCP連線上下文:ChannelContext
每一個tcp連線的建立都會產生一個ChannelContext物件,這是個抽象類,如果你是用t-io作tcp客戶端,那麼就是ClientChannelContext,如果你是用tio作tcp伺服器,那麼就是ServerChannelContext
使用者可以把業務資料通過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物件包含的資訊非常多,主要物件見下圖
說明
ChannelContext是t-io中非常重要的類,他是業務和連線的溝通橋樑!
服務配置與維護:TioConfig
場景:我們在寫TCP Server時,都會先選好一個埠以監聽客戶端連線,再建立N組執行緒池來執行相關的任務,譬如傳送訊息、解碼資料包、處理資料包等任務,還要維護客戶端連線的各種資料,為了和業務互動,還要把這些客戶端連線和各種業務資料繫結起來,譬如把某個客戶端繫結到一個群組,繫結到一個userid,繫結到一個token等。
TioConfig就是解決以上場景的:配置執行緒池、監聽埠,維護客戶端各種資料等的。
TioConfig是個抽象類
如果你是用tio作tcp客戶端,那麼你需要建立ClientTioConfig物件
伺服器端對應一個ClientTioConfig物件
如果你是用tio作tcp伺服器,那麼你需要建立ServerTioConfig
一個監聽埠對應一個ServerTioConfig ,一個jvm可以監聽多個埠,所以一個jvm可以有多個ServerTioConfig物件
TioConfig物件包含的資訊非常多,主要物件見下圖
如何獲取TioConfig物件
見:https://www.wanetech.com/doc/...
編碼、解碼、處理:AioHandler
AioHandler是處理訊息的核心介面,它有兩個子介面,ClientAioHandler和ServerAioHandler,當用tio作tcp客戶端時需要實現ClientAioHandler,當用tio作tcp伺服器時需要實現ServerAioHandler,它主要定義了3個方法,見下
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年10月19日 上午9:40:15
*/
public interface AioHandler {
/**
* 根據ByteBuffer解碼成業務需要的Packet物件.
* 如果收到的資料不全,導致解碼失敗,請返回null,在下次訊息來時框架層會自動續上前面的收到的資料
* @param buffer 參與本次希望解碼的ByteBuffer
* @param limit ByteBuffer的limit
* @param position ByteBuffer的position,不一定是0哦
* @param readableLength ByteBuffer參與本次解碼的有效資料(= limit - position)
* @param channelContext
* @return
* @throws AioDecodeException
*/
Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException;
/**
* 編碼
* @param packet
* @param tioConfig
* @param channelContext
* @return
* @author: tanyaowu
*/
ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext);
/**
* 處理訊息包
* @param packet
* @param channelContext
* @throws Exception
* @author: tanyaowu
*/
void handler(Packet packet, ChannelContext channelContext) throws Exception;
}
訊息來往監聽:AioListener
AioListener是處理訊息的核心介面,它有兩個子介面:ClientAioListener和ServerAioListener
當用tio作tcp客戶端時需要實現ClientAioListener
當用tio作tcp伺服器時需要實現ServerAioListener
它主要定義瞭如下方法
package org.tio.core.intf;
import org.tio.core.ChannelContext;
/**
*
* @author tanyaowu
* 2017年4月1日 上午9:34:08
*/
public interface AioListener {
/**
* 建鏈後觸發本方法,注:建鏈不一定成功,需要關注引數isConnected
* @param channelContext
* @param isConnected 是否連線成功,true:表示連線成功,false:表示連線失敗
* @param isReconnect 是否是重連, true: 表示這是重新連線,false: 表示這是第一次連線
* @throws Exception
* @author: tanyaowu
*/
public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception;
/**
* 原方法名:onAfterDecoded
* 解碼成功後觸發本方法
* @param channelContext
* @param packet
* @param packetSize
* @throws Exception
* @author: tanyaowu
*/
public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception;
/**
* 接收到TCP層傳過來的資料後
* @param channelContext
* @param receivedBytes 本次接收了多少位元組
* @throws Exception
*/
public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception;
/**
* 訊息包傳送之後觸發本方法
* @param channelContext
* @param packet
* @param isSentSuccess true:傳送成功,false:傳送失敗
* @throws Exception
* @author tanyaowu
*/
public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception;
/**
* 處理一個訊息包後
* @param channelContext
* @param packet
* @param cost 本次處理訊息耗時,單位:毫秒
* @throws Exception
*/
public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception;
/**
* 連線關閉前觸發本方法
* @param channelContext the channelcontext
* @param throwable the throwable 有可能為空
* @param remark the remark 有可能為空
* @param isRemove
* @author tanyaowu
* @throws Exception
*/
public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
/**
* 連線關閉前後觸發本方法
* 警告:走到這個裡面時,很多繫結的業務都已經解綁了,所以這個方法一般是空著不實現的
* @param channelContext the channelcontext
* @param throwable the throwable 有可能為空
* @param remark the remark 有可能為空
* @param isRemove 是否是刪除
* @throws Exception
* @author: tanyaowu
*/
// public void onAfterClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
}
伺服器端入口:TioServer
這個物件大家稍微瞭解一下即可,伺服器啟動時會用到這個物件,簡單貼一下它的原始碼吧,大家只需要關注它有一個start()方法是用來啟動網路服務的即可
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年1月2日 下午5:53:06
*
*/
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();
//下面這段程式碼有點無聊,寫得隨意,純粹是為了列印好看些
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()時報錯", e);
}
try {
serverSocketChannel.close();
} catch (Exception e1) {
log.error("serverSocketChannel.close()時報錯", 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;
}
}
客戶端入口:TioClient
只有當你在用t-io作為TCP客戶端時,才用得到TioClient,此處簡單貼一下它的原始碼,它的用法,見後面的showcase示範工程
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年4月1日 上午9:29:58
*/
public class TioClient {
/**
* 自動重連任務
* @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年2月2日 下午8:24:40
*
*/
@Override
public void run() {
ReentrantReadWriteLock closeLock = channelContext.closeLock;
WriteLock writeLock = closeLock.writeLock();
writeLock.lock();
try {
if (!channelContext.isClosed) //已經連上了,不需要再重連了
{
return;
}
long start = SystemTimer.currTime;
tioClient.reconnect(channelContext, 2);
long end = SystemTimer.currTime;
long iv = end - start;
if (iv >= 100) {
log.error("{},重連耗時:{} ms", channelContext, iv);
} else {
log.info("{},重連耗時:{} 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 可以為空
* @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 超時時間,單位秒
* @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 超時時間,單位秒
* @param isSyn true: 同步, false: 非同步
* @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 耗時:{} 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) 返回false ");
return attachment.getChannelContext();
}
} else {
asynchronousSocketChannel.connect(inetSocketAddress, attachment, clientTioConfig.getConnectionCompletionHandler());
return null;
}
}
/**
*
* @param serverNode
* @param bindIp
* @param bindPort
* @param timeout 超時時間,單位秒
* @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;
}
/**
* 定時任務:發心跳
* @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("使用者取消了框架層面的心跳定時傳送功能,請使用者自己去完成心跳機制");
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("{}傳送心跳包", 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();
}
/**
* 啟動重連任務
*
*
* @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("準備重連");
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) //已經刪除的,不需要重新再連
{
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) //已經刪除的和已經連上的,不需要重新再連
{
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;
}
}