serverList = mIMSOptions.getServerList();
73 | if (serverList == null || serverList.isEmpty()) {
74 | return IMSConnectStatus.ConnectFailed_ServerListEmpty;
75 | }
76 |
77 | ims.initBootstrap();
78 | for (int i = 0; i < serverList.size(); i++) {
79 | String server = serverList.get(i);
80 | if (StringUtil.isNullOrEmpty(server)) {
81 | return IMSConnectStatus.ConnectFailed_ServerEmpty;
82 | }
83 |
84 | URI uri;
85 | try {
86 | uri = URI.create(server);
87 | }catch (IllegalArgumentException e) {
88 | e.printStackTrace();
89 | if(i == serverList.size() - 1) {
90 | Log.w(TAG, String.format("【%1$s】连接失败,地址不合法", server));
91 | return IMSConnectStatus.ConnectFailed_ServerIllegitimate;
92 | }else {
93 | Log.w(TAG, String.format("【%1$s】连接失败,地址不合法,正在等待重连,当前重连延时时长:%2$d", server, mIMSOptions.getReconnectInterval()));
94 | Log.w(TAG, "=========================================================================================");
95 | try {
96 | Thread.sleep(mIMSOptions.getReconnectInterval());
97 | } catch (InterruptedException ex) {
98 | ex.printStackTrace();
99 | }
100 | continue;
101 | }
102 | }
103 |
104 | if(!"ws".equals(uri.getScheme())) {
105 | if(i == serverList.size() - 1) {
106 | Log.w(TAG, String.format("【%1$s】连接失败,地址不合法", server));
107 | return IMSConnectStatus.ConnectFailed_ServerIllegitimate;
108 | }else {
109 | Log.w(TAG, String.format("【%1$s】连接失败,地址不合法,正在等待重连,当前重连延时时长:%2$d", server, mIMSOptions.getReconnectInterval()));
110 | Log.w(TAG, "=========================================================================================");
111 | try {
112 | Thread.sleep(mIMSOptions.getReconnectInterval());
113 | } catch (InterruptedException ex) {
114 | ex.printStackTrace();
115 | }
116 | continue;
117 | }
118 | }
119 |
120 | if(i == 0) {
121 | ims.callbackIMSConnectStatus(IMSConnectStatus.Connecting);
122 | }
123 |
124 | // +1是因为首次连接也认为是重连,所以如果重连次数设置为3,则最大连接次数为3+1次
125 | for (int j = 0; j < mIMSOptions.getReconnectCount() + 1; j++) {
126 | if (ims.isClosed()) {
127 | return IMSConnectStatus.ConnectFailed_IMSClosed;
128 | }
129 | if (!ims.isNetworkAvailable()) {
130 | return IMSConnectStatus.ConnectFailed_NetworkUnavailable;
131 | }
132 |
133 | Log.d(TAG, String.format("正在进行【%1$s】的第%2$d次连接", server, j + 1));
134 | try {
135 | String host = uri.getHost();
136 | int port = uri.getPort();
137 | Channel channel = toServer(host, port);
138 | if (channel != null && channel.isOpen() && channel.isActive() && channel.isRegistered() && channel.isWritable()) {
139 | ims.setChannel(channel);
140 | return IMSConnectStatus.Connected;
141 | } else {
142 | if (j == mIMSOptions.getReconnectCount()) {
143 | // 如果当前已达到最大重连次数,并且是最后一个服务器地址,则回调连接失败
144 | if(i == serverList.size() - 1) {
145 | Log.w(TAG, String.format("【%1$s】连接失败", server));
146 | return IMSConnectStatus.ConnectFailed;
147 | }
148 | // 否则,无需回调连接失败,等待一段时间再去进行下一个服务器地址连接即可
149 | // 也就是说,当服务器地址列表里的地址都连接失败,才认为是连接失败
150 | else {
151 | // 一个服务器地址连接失败后,延时指定时间再去进行下一个服务器地址的连接
152 | Log.w(TAG, String.format("【%1$s】连接失败,正在等待进行下一个服务器地址的重连,当前重连延时时长:%2$dms", server, mIMSOptions.getReconnectInterval()));
153 | Log.w(TAG, "=========================================================================================");
154 | Thread.sleep(mIMSOptions.getReconnectInterval());
155 | }
156 | } else {
157 | // 连接失败,则线程休眠(重连间隔时长 / 2 * n) ms
158 | int delayTime = mIMSOptions.getReconnectInterval() + mIMSOptions.getReconnectInterval() / 2 * j;
159 | Log.w(TAG, String.format("【%1$s】连接失败,正在等待重连,当前重连延时时长:%2$dms", server, delayTime));
160 | Thread.sleep(delayTime);
161 | }
162 | }
163 | } catch (InterruptedException e) {
164 | break;// 线程被中断,则强制关闭
165 | }
166 | }
167 | }
168 |
169 | return IMSConnectStatus.ConnectFailed;
170 | }
171 |
172 | /**
173 | * 真正连接服务器的地方
174 | * @param host
175 | * @param port
176 | * @return
177 | */
178 | private Channel toServer(String host, int port) {
179 | Channel channel;
180 | try {
181 | channel = ims.getBootstrap().connect(host, port).sync().channel();
182 | } catch (Exception e) {
183 | e.printStackTrace();
184 | channel = null;
185 | }
186 |
187 | return channel;
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/com/freddy/kulaims/nio/tcp/NioTCPIMS.java:
--------------------------------------------------------------------------------
1 | package com.freddy.kulaims.nio.tcp;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 |
6 | import com.freddy.kulaims.bean.IMSMsg;
7 | import com.freddy.kulaims.config.IMSOptions;
8 | import com.freddy.kulaims.interf.IMSInterface;
9 | import com.freddy.kulaims.listener.IMSConnectStatusListener;
10 | import com.freddy.kulaims.listener.IMSMsgReceivedListener;
11 | import com.freddy.kulaims.listener.IMSMsgSentStatusListener;
12 |
13 | public class NioTCPIMS implements IMSInterface {
14 |
15 | private NioTCPIMS() {
16 | }
17 |
18 | public static NioTCPIMS getInstance() {
19 | return SingletonHolder.INSTANCE;
20 | }
21 |
22 | private static final class SingletonHolder {
23 | @SuppressLint("StaticFieldLeak")
24 | private static final NioTCPIMS INSTANCE = new NioTCPIMS();
25 | }
26 |
27 | @Override
28 | public boolean init(Context context, IMSOptions options, IMSConnectStatusListener connectStatusListener, IMSMsgReceivedListener msgReceivedListener) {
29 | return false;
30 | }
31 |
32 | @Override
33 | public void connect() {
34 |
35 | }
36 |
37 | @Override
38 | public void reconnect(boolean isFirstConnect) {
39 |
40 | }
41 |
42 | @Override
43 | public void sendMsg(IMSMsg msg) {
44 |
45 | }
46 |
47 | @Override
48 | public void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener) {
49 |
50 | }
51 |
52 | @Override
53 | public void sendMsg(IMSMsg msg, boolean isJoinResendManager) {
54 |
55 | }
56 |
57 | @Override
58 | public void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener, boolean isJoinResendManager) {
59 |
60 | }
61 |
62 | @Override
63 | public void release() {
64 |
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/freddy/kulaims/nio/websocket/NioWebSocketIMS.java:
--------------------------------------------------------------------------------
1 | package com.freddy.kulaims.nio.websocket;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 |
6 | import com.freddy.kulaims.bean.IMSMsg;
7 | import com.freddy.kulaims.config.IMSOptions;
8 | import com.freddy.kulaims.interf.IMSInterface;
9 | import com.freddy.kulaims.listener.IMSConnectStatusListener;
10 | import com.freddy.kulaims.listener.IMSMsgReceivedListener;
11 | import com.freddy.kulaims.listener.IMSMsgSentStatusListener;
12 |
13 | public class NioWebSocketIMS implements IMSInterface {
14 |
15 | private NioWebSocketIMS() {
16 | }
17 |
18 | public static NioWebSocketIMS getInstance() {
19 | return SingletonHolder.INSTANCE;
20 | }
21 |
22 | private static final class SingletonHolder {
23 | @SuppressLint("StaticFieldLeak")
24 | private static final NioWebSocketIMS INSTANCE = new NioWebSocketIMS();
25 | }
26 |
27 | @Override
28 | public boolean init(Context context, IMSOptions options, IMSConnectStatusListener connectStatusListener, IMSMsgReceivedListener msgReceivedListener) {
29 | return false;
30 | }
31 |
32 | @Override
33 | public void connect() {
34 |
35 | }
36 |
37 | @Override
38 | public void reconnect(boolean isFirstConnect) {
39 |
40 | }
41 |
42 | @Override
43 | public void sendMsg(IMSMsg msg) {
44 |
45 | }
46 |
47 | @Override
48 | public void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener) {
49 |
50 | }
51 |
52 | @Override
53 | public void sendMsg(IMSMsg msg, boolean isJoinResendManager) {
54 |
55 | }
56 |
57 | @Override
58 | public void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener, boolean isJoinResendManager) {
59 |
60 | }
61 |
62 | @Override
63 | public void release() {
64 |
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/freddy/kulaims/utils/ExecutorServiceFactory.java:
--------------------------------------------------------------------------------
1 | package com.freddy.kulaims.utils;
2 |
3 | import java.util.concurrent.ExecutorService;
4 | import java.util.concurrent.Executors;
5 |
6 | /**
7 | * @ProjectName: NettyChat
8 | * @ClassName: ExecutorServiceFactory.java
9 | * @PackageName: com.freddy.kulaims
10 | *
11 | * @Description: 线程池工厂,负责重连和心跳线程调度
12 | *
13 | * @author: FreddyChen
14 | * @date: 2019/04/05 05:12
15 | * @email: chenshichao@outlook.com
16 | */
17 | public class ExecutorServiceFactory {
18 |
19 | private ExecutorService bossPool;// 管理线程组,负责重连
20 | private ExecutorService workPool;// 工作线程组,负责心跳
21 |
22 | /**
23 | * 初始化boss线程池
24 | */
25 | public synchronized void initBossLoopGroup() {
26 | destroyBossLoopGroup();
27 | bossPool = Executors.newSingleThreadExecutor();
28 | }
29 |
30 | /**
31 | * 初始化work线程池
32 | */
33 | public synchronized void initWorkLoopGroup() {
34 | destroyWorkLoopGroup();
35 | workPool = Executors.newSingleThreadExecutor();
36 | }
37 |
38 | /**
39 | * 执行boss任务
40 | *
41 | * @param r
42 | */
43 | public void execBossTask(Runnable r) {
44 | if (bossPool == null) {
45 | initBossLoopGroup();
46 | }
47 | bossPool.execute(r);
48 | }
49 |
50 | /**
51 | * 执行work任务
52 | *
53 | * @param r
54 | */
55 | public void execWorkTask(Runnable r) {
56 | if (workPool == null) {
57 | initWorkLoopGroup();
58 | }
59 | workPool.execute(r);
60 | }
61 |
62 | /**
63 | * 释放boss线程池
64 | */
65 | public synchronized void destroyBossLoopGroup() {
66 | if (bossPool != null) {
67 | try {
68 | bossPool.shutdownNow();
69 | } catch (Throwable t) {
70 | t.printStackTrace();
71 | } finally {
72 | bossPool = null;
73 | }
74 | }
75 | }
76 |
77 | /**
78 | * 释放work线程池
79 | */
80 | public synchronized void destroyWorkLoopGroup() {
81 | if (workPool != null) {
82 | try {
83 | workPool.shutdownNow();
84 | } catch (Throwable t) {
85 | t.printStackTrace();
86 | } finally {
87 | workPool = null;
88 | }
89 | }
90 | }
91 |
92 | /**
93 | * 释放所有线程池
94 | */
95 | public synchronized void destroy() {
96 | destroyBossLoopGroup();
97 | destroyWorkLoopGroup();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/freddy/kulaims/utils/UUID.java:
--------------------------------------------------------------------------------
1 | package com.freddy.kulaims.utils;
2 |
3 | public class UUID {
4 |
5 | private static final String[] CHARS = {
6 | "a",
7 | "b",
8 | "c",
9 | "d",
10 | "e",
11 | "f",
12 | "g",
13 | "h",
14 | "i",
15 | "j",
16 | "k",
17 | "l",
18 | "m",
19 | "n",
20 | "o",
21 | "p",
22 | "q",
23 | "r",
24 | "s",
25 | "t",
26 | "u",
27 | "v",
28 | "w",
29 | "x",
30 | "y",
31 | "z",
32 | "0",
33 | "1",
34 | "2",
35 | "3",
36 | "4",
37 | "5",
38 | "6",
39 | "7",
40 | "8",
41 | "9",
42 | "A",
43 | "B",
44 | "C",
45 | "D",
46 | "E",
47 | "F",
48 | "G",
49 | "H",
50 | "I",
51 | "J",
52 | "K",
53 | "L",
54 | "M",
55 | "N",
56 | "O",
57 | "P",
58 | "Q",
59 | "R",
60 | "S",
61 | "T",
62 | "U",
63 | "V",
64 | "W",
65 | "X",
66 | "Y",
67 | "Z"
68 | };
69 |
70 | /**
71 | * 生成短8位UUID
72 | * @return
73 | */
74 | public static String generateShortUuid() {
75 | StringBuilder shortBuffer = new StringBuilder();
76 | String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
77 | for (int i = 0; i < 8; i++) {
78 | String str = uuid.substring(i * 4, i * 4 + 4);
79 | int x = Integer.parseInt(str, 16);
80 | shortBuffer.append(CHARS[x % 0x3E]);
81 | }
82 | return shortBuffer.toString();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/proto/msg.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";// 指定protobuf版本
2 | option java_package = "com.freddy.kulaims.protobuf";// 指定包名
3 | option java_outer_classname = "MessageProtobuf";// 指定类名
4 |
5 | message Msg {
6 | Head head = 1;// 消息头
7 | Body body = 2;// 消息体
8 | }
9 |
10 | message Head {
11 | string msgId = 1;// 消息id
12 | int32 msgType = 2;// 消息类型
13 | string sender = 3;// 发送者
14 | string receiver = 4;// 接收者
15 | int64 timestamp = 5;// 发送时间戳,单位:毫秒
16 | int32 report = 6;// 消息发送状态报告
17 | }
18 |
19 | message Body {
20 | string content = 1;// 消息内容
21 | int32 contentType = 2;// 消息内容类型
22 | string data = 3;// 扩展字段,以key/value形式存储的json字符串
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/test/java/com/freddy/kulaims/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.freddy.kulaims;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------