网络编程框架t-io的编程基本知识介绍

1,947 阅读14分钟

t-io作为目前国内最流行的开源网络编程框架软件,以简单易懂,上手容易而著称,相同的功能比起netty实现起来,要简单的多,代码量也大大减少,如果要使用好t-io,还是要先学习t-io的一些基本知识,这篇文章主要从8个方面介绍了t-io的基础知识。 具体请参考www.tiocloud.com/doc/tio/88

t-io收发消息过程

t-io收发消息及处理过程,可以用一张图清晰地表达出来 image.png ## 应用层包:Packet

Packet是用于表述业务数据结构的,我们通过继承Packet来实现自己的业务数据结构,对于各位而言,把Packet看作是一个普通的VO对象即可。

注意: 不建议直接使用Packet对象,而是要继承Packet

一个简单的Packet可能长这样 1. package org.tio.study.helloworld.common;`

  1. ``
  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

image.png 用户可以把业务数据通过ChannelContext对象和TCP连接关联起来,像下面这样设置属性

image.png 然后用下面的方式获取属性

image.png 当然最最常用的还是用t-io提供的强到没对手的bind功能,譬如用下面的代码绑定userid

image.png 然后可以通过userid进行操作,示范代码如下

image.png 除了可以绑定userid,t-io还内置了如下绑定API

  • 绑定业务id

image.png

  • 绑定token

image.png

  • 绑定群组

image.png ChannelContext对象包含的信息非常多,主要对象见下图

image.png

说明

ChannelContext是t-io中非常重要的类,他是业务和连接的沟通桥梁!

服务配置与维护:TioConfig

  • 场景:我们在写TCP Server时,都会先选好一个端口以监听客户端连接,再创建N组线程池来执行相关的任务,譬如发送消息、解码数据包、处理数据包等任务,还要维护客户端连接的各种数据,为了和业务互动,还要把这些客户端连接和各种业务数据绑定起来,譬如把某个客户端绑定到一个群组,绑定到一个userid,绑定到一个token等。

  • TioConfig就是解决以上场景的:配置线程池、监听端口,维护客户端各种数据等的。

  • TioConfig是个抽象类

    • 如果你是用tio作tcp客户端,那么你需要创建ClientTioConfig对象

      • 服务器端对应一个ClientTioConfig对象
    • 如果你是用tio作tcp服务器,那么你需要创建ServerTioConfig

      • 一个监听端口对应一个ServerTioConfig ,一个jvm可以监听多个端口,所以一个jvm可以有多个ServerTioConfig对象
  • TioConfig对象包含的信息非常多,主要对象见下图

image.png

如何获取TioConfig对象

见:如何获取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

它主要定义了如下方法

.    package org.tio.core.intf;
2. 
3.    import org.tio.core.ChannelContext;
5.    /**
6.    *
7.    * @author tanyaowu
8.    * 2017年4月1日 上午9:34:08
9.    */
10.   public interface AioListener {
13.   /**
14.   * 建链后触发本方法,注:建链不一定成功,需要关注参数isConnected
15.   * @param channelContext
16.   * @param isConnected 是否连接成功,true:表示连接成功,false:表示连接失败
17.   * @param isReconnect 是否是重连, true: 表示这是重新连接,false: 表示这是第一次连接
18.   * @throws Exception
19.   * @author: tanyaowu
20.   */
21.   public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception;
23.   /**
24.   * 原方法名:onAfterDecoded
25.   * 解码成功后触发本方法
26.   * @param channelContext
27.   * @param packet
28.   * @param packetSize
29.   * @throws Exception
30.   * @author: tanyaowu
31.   */
32.   public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception;
34.   /**
35.   * 接收到TCP层传过来的数据后
36.   * @param channelContext
37.   * @param receivedBytes 本次接收了多少字节
38.   * @throws Exception
39.   */
40.   public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception;
42.   /**
43.   * 消息包发送之后触发本方法
44.   * @param channelContext
45.   * @param packet
46.   * @param isSentSuccess true:发送成功,false:发送失败
47.   * @throws Exception
48.   * @author tanyaowu
49.   */
50.   public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception;
52.   /**
53.   * 处理一个消息包后
54.   * @param channelContext
55.   * @param packet
56.   * @param cost 本次处理消息耗时,单位:毫秒
57.   * @throws Exception
58.   */
59.   public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception;
61.   /**
62.   * 连接关闭前触发本方法
63.   * @param channelContext the channelcontext
64.   * @param throwable the throwable 有可能为空
65.   * @param remark the remark 有可能为空
66.   * @param isRemove
67.   * @author tanyaowu
68.   * @throws Exception 
69.   */
70.   public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
72.   /**
73.   * 连接关闭前后触发本方法
74.   * 警告:走到这个里面时,很多绑定的业务都已经解绑了,所以这个方法一般是空着不实现的
75.   * @param channelContext the channelcontext
76.   * @param throwable the throwable 有可能为空
77.   * @param remark the remark 有可能为空
78.   * @param isRemove 是否是删除
79.   * @throws Exception
80.   * @author: tanyaowu
81.   */
82.   // public void onAfterClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception;
83.   }

服务器端入口:TioServer

这个对象大家稍微了解一下即可,服务器启动时会用到这个对象,简单贴一下它的源代码吧,大家只需要关注它有一个start()方法是用来启动网络服务的即可

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

客户端入口:TioClient

只有当你在用t-io作为TCP客户端时,才用得到TioClient,此处简单贴一下它的源代码,它的用法,见后面的showcase示范工程

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