@ProjectName: NettyChat
7 | *@ClassName: NettyChatApp.java
8 | *@PackageName: com.freddy.chat
9 | * 10 | *@Description: 类描述
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/07 23:58
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public class NettyChatApp extends MultiDexApplication { 17 | 18 | private static NettyChatApp instance; 19 | 20 | public static NettyChatApp sharedInstance() { 21 | if (instance == null) { 22 | throw new IllegalStateException("app not init..."); 23 | } 24 | return instance; 25 | } 26 | 27 | @Override 28 | public void onCreate() { 29 | super.onCreate(); 30 | instance = this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/AppMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | /** 4 | *@ProjectName: NettyChat
5 | *@ClassName: AppMessage.java
6 | *@PackageName: com.freddy.chat.bean
7 | * 8 | *@Description: App消息,用于把protobuf消息转换成app可用的消息类型
9 | * 10 | *@author: FreddyChen
11 | *@date: 2019/04/10 00:01
12 | *@email: chenshichao@outlook.com
13 | */ 14 | public class AppMessage { 15 | 16 | private Head head; // 消息头 17 | private String body;// 消息体 18 | 19 | public Head getHead() { 20 | return head; 21 | } 22 | 23 | public void setHead(Head head) { 24 | this.head = head; 25 | } 26 | 27 | public String getBody() { 28 | return body; 29 | } 30 | 31 | public void setBody(String body) { 32 | this.body = body; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "AppMessage{" + 38 | "head=" + head + 39 | ", body='" + body + '\'' + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: BaseMessage.java
8 | *@PackageName: com.freddy.chat.bean
9 | * 10 | *@Description: 消息基类
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/10 00:02
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public class BaseMessage { 17 | 18 | protected String msgId; // 消息id 19 | protected int msgType; // 消息类型 20 | protected int msgContentType; // 消息内容乐行 21 | protected String fromId; // 发送者id 22 | protected String toId; // 接收者id 23 | protected long timestamp; // 消息时间戳 24 | protected int statusReport; // 消息状态报告 25 | protected String extend; // 扩展字段,以key/value形式存放json 26 | protected String content; // 消息内容 27 | 28 | public String getMsgId() { 29 | return msgId; 30 | } 31 | 32 | public void setMsgId(String msgId) { 33 | this.msgId = msgId; 34 | } 35 | 36 | public int getMsgType() { 37 | return msgType; 38 | } 39 | 40 | public void setMsgType(int msgType) { 41 | this.msgType = msgType; 42 | } 43 | 44 | public int getMsgContentType() { 45 | return msgContentType; 46 | } 47 | 48 | public void setMsgContentType(int msgContentType) { 49 | this.msgContentType = msgContentType; 50 | } 51 | 52 | public String getFromId() { 53 | return fromId; 54 | } 55 | 56 | public void setFromId(String fromId) { 57 | this.fromId = fromId; 58 | } 59 | 60 | public String getToId() { 61 | return toId; 62 | } 63 | 64 | public void setToId(String toId) { 65 | this.toId = toId; 66 | } 67 | 68 | public long getTimestamp() { 69 | return timestamp; 70 | } 71 | 72 | public void setTimestamp(long timestamp) { 73 | this.timestamp = timestamp; 74 | } 75 | 76 | public int getStatusReport() { 77 | return statusReport; 78 | } 79 | 80 | public void setStatusReport(int statusReport) { 81 | this.statusReport = statusReport; 82 | } 83 | 84 | public String getExtend() { 85 | return extend; 86 | } 87 | 88 | public void setExtend(String extend) { 89 | this.extend = extend; 90 | } 91 | 92 | public String getContent() { 93 | return content; 94 | } 95 | 96 | public void setContent(String content) { 97 | this.content = content; 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | try { 103 | return this.msgId.hashCode(); 104 | }catch (NullPointerException e) { 105 | e.printStackTrace(); 106 | } 107 | 108 | return 1; 109 | } 110 | 111 | @Override 112 | public boolean equals(Object obj) { 113 | if(obj == null || !(obj instanceof BaseMessage)) { 114 | return false; 115 | } 116 | 117 | return StringUtil.equals(this.msgId, ((BaseMessage) obj).getMsgId()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/ContentMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: ContentMessage.java
8 | *@PackageName: com.freddy.chat.bean
9 | * 10 | *@Description: 内容消息,包含单聊消息及群聊消息
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/10 00:06
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public class ContentMessage extends BaseMessage { 17 | 18 | protected boolean isRead; 19 | protected boolean isPlaying; 20 | protected boolean isLoading; 21 | 22 | public ContentMessage() { 23 | } 24 | 25 | public ContentMessage(String msgId, int msgType, int msgContentType, String fromId, String toId, 26 | long timestamp, int statusReport, String extend, String content) { 27 | this.msgId = msgId; 28 | this.msgType = msgType; 29 | this.msgContentType = msgContentType; 30 | this.fromId = fromId; 31 | this.toId = toId; 32 | this.timestamp = timestamp; 33 | this.statusReport = statusReport; 34 | this.extend = extend; 35 | this.content = content; 36 | } 37 | 38 | public boolean isRead() { 39 | return isRead; 40 | } 41 | 42 | public void setRead(boolean read) { 43 | isRead = read; 44 | } 45 | 46 | public boolean isPlaying() { 47 | return isPlaying; 48 | } 49 | 50 | public void setPlaying(boolean playing) { 51 | isPlaying = playing; 52 | } 53 | 54 | public boolean isLoading() { 55 | return isLoading; 56 | } 57 | 58 | public void setLoading(boolean loading) { 59 | isLoading = loading; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object obj) { 64 | if (obj == null || !(obj instanceof ContentMessage)) { 65 | return false; 66 | } 67 | 68 | return StringUtil.equals(this.msgId, ((ContentMessage) obj).getMsgId()); 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | try { 74 | return this.msgId.hashCode(); 75 | }catch (NullPointerException e) { 76 | e.printStackTrace(); 77 | } 78 | 79 | return 1; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/Head.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | /** 4 | *@ProjectName: NettyChat
5 | *@ClassName: Head.java
6 | *@PackageName: com.freddy.chat.bean
7 | * 8 | *@Description: 消息头
9 | * 10 | *@author: FreddyChen
11 | *@date: 2019/04/10 00:00
12 | *@email: chenshichao@outlook.com
13 | */ 14 | public class Head { 15 | 16 | private String msgId; 17 | private int msgType; 18 | private int msgContentType; 19 | private String fromId; 20 | private String toId; 21 | private long timestamp; 22 | private int statusReport; 23 | private String extend; 24 | 25 | public String getMsgId() { 26 | return msgId; 27 | } 28 | 29 | public void setMsgId(String msgId) { 30 | this.msgId = msgId; 31 | } 32 | 33 | public int getMsgType() { 34 | return msgType; 35 | } 36 | 37 | public void setMsgType(int msgType) { 38 | this.msgType = msgType; 39 | } 40 | 41 | public int getMsgContentType() { 42 | return msgContentType; 43 | } 44 | 45 | public void setMsgContentType(int msgContentType) { 46 | this.msgContentType = msgContentType; 47 | } 48 | 49 | public String getFromId() { 50 | return fromId; 51 | } 52 | 53 | public void setFromId(String fromId) { 54 | this.fromId = fromId; 55 | } 56 | 57 | public String getToId() { 58 | return toId; 59 | } 60 | 61 | public void setToId(String toId) { 62 | this.toId = toId; 63 | } 64 | 65 | public long getTimestamp() { 66 | return timestamp; 67 | } 68 | 69 | public void setTimestamp(long timestamp) { 70 | this.timestamp = timestamp; 71 | } 72 | 73 | public int getStatusReport() { 74 | return statusReport; 75 | } 76 | 77 | public void setStatusReport(int statusReport) { 78 | this.statusReport = statusReport; 79 | } 80 | 81 | public String getExtend() { 82 | return extend; 83 | } 84 | 85 | public void setExtend(String extend) { 86 | this.extend = extend; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "Head{" + 92 | "msgId='" + msgId + '\'' + 93 | ", msgType=" + msgType + 94 | ", msgContentType=" + msgContentType + 95 | ", fromId='" + fromId + '\'' + 96 | ", toId='" + toId + '\'' + 97 | ", timestamp=" + timestamp + 98 | ", statusReport=" + statusReport + 99 | ", extend='" + extend + '\'' + 100 | '}'; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/bean/SingleMessage.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.bean; 2 | 3 | import com.freddy.chat.utils.StringUtil; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: SingleMessage.java
8 | *@PackageName: com.freddy.chat.bean
9 | * 10 | *@Description: 单聊消息
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/10 03:24
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public class SingleMessage extends ContentMessage implements Cloneable { 17 | 18 | @Override 19 | public int hashCode() { 20 | try { 21 | return this.msgId.hashCode(); 22 | } catch (Exception ex) { 23 | ex.printStackTrace(); 24 | } 25 | 26 | return 1; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object obj) { 31 | if (obj == null) { 32 | return false; 33 | } 34 | 35 | if (!(obj instanceof SingleMessage)) { 36 | return false; 37 | } 38 | 39 | return StringUtil.equals(this.msgId, ((SingleMessage) obj).getMsgId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/event/CEvenObjPool.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.event; 2 | 3 | /** 4 | * 事件对象池 5 | * 6 | * Created by Freddy on 2015/11/3. 7 | * chenshichao@outlook.com 8 | */ 9 | public class CEvenObjPool extends ObjectPool@ProjectName: NettyChat
14 | *@ClassName: IMSClientBootstrap.java
15 | *@PackageName: com.freddy.chat.im
16 | * 17 | *@Description: 应用层的imsClient启动器
18 | * 19 | *@author: FreddyChen
20 | *@date: 2019/04/08 00:25
21 | *@email: chenshichao@outlook.com
22 | */ 23 | public class IMSClientBootstrap { 24 | 25 | private static final IMSClientBootstrap INSTANCE = new IMSClientBootstrap(); 26 | private IMSClientInterface imsClient; 27 | private boolean isActive; 28 | 29 | private IMSClientBootstrap() { 30 | 31 | } 32 | 33 | public static IMSClientBootstrap getInstance() { 34 | return INSTANCE; 35 | } 36 | 37 | public static void main(String[] args) { 38 | String userId = "100001"; 39 | String token = "token_" + userId; 40 | IMSClientBootstrap bootstrap = IMSClientBootstrap.getInstance(); 41 | String hosts = "[{\"host\":\"127.0.0.1\", \"port\":8866}]"; 42 | bootstrap.init(userId, token, hosts, 0); 43 | } 44 | 45 | public synchronized void init(String userId, String token, String hosts, int appStatus) { 46 | if (!isActive()) { 47 | Vector@ProjectName: NettyChat
7 | *@ClassName: IMSConnectStatusListener.java
8 | *@PackageName: com.freddy.chat.im
9 | * 10 | *@Description: 类描述
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/08 00:31
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public class IMSConnectStatusListener implements IMSConnectStatusCallback { 17 | 18 | @Override 19 | public void onConnecting() { 20 | } 21 | 22 | @Override 23 | public void onConnected() { 24 | } 25 | 26 | @Override 27 | public void onConnectFailed() { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMSEventListener.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import com.alibaba.fastjson.JSONObject; 8 | import com.freddy.chat.NettyChatApp; 9 | import com.freddy.im.listener.OnEventListener; 10 | import com.freddy.im.protobuf.MessageProtobuf; 11 | 12 | import java.util.UUID; 13 | 14 | /** 15 | *@ProjectName: NettyChat
16 | *@ClassName: IMSEventListener.java
17 | *@PackageName: com.freddy.chat.im
18 | * 19 | *@Description: 与ims交互的listener
20 | * 21 | *@author: FreddyChen
22 | *@date: 2019/04/07 23:55
23 | *@email: chenshichao@outlook.com
24 | */ 25 | public class IMSEventListener implements OnEventListener { 26 | 27 | private String userId; 28 | private String token; 29 | 30 | public IMSEventListener(String userId, String token) { 31 | this.userId = userId; 32 | this.token = token; 33 | } 34 | 35 | /** 36 | * 接收ims转发过来的消息 37 | * 38 | * @param msg 39 | */ 40 | @Override 41 | public void dispatchMsg(MessageProtobuf.Msg msg) { 42 | MessageProcessor.getInstance().receiveMsg(MessageBuilder.getMessageByProtobuf(msg)); 43 | } 44 | 45 | /** 46 | * 网络是否可用 47 | * 48 | * @return 49 | */ 50 | @Override 51 | public boolean isNetworkAvailable() { 52 | ConnectivityManager cm = (ConnectivityManager) NettyChatApp.sharedInstance().getSystemService(Context.CONNECTIVITY_SERVICE); 53 | NetworkInfo info = cm.getActiveNetworkInfo(); 54 | return info != null && info.isConnected(); 55 | } 56 | 57 | /** 58 | * 设置ims重连间隔时长,0表示默认使用ims的值 59 | * 60 | * @return 61 | */ 62 | @Override 63 | public int getReconnectInterval() { 64 | return 0; 65 | } 66 | 67 | /** 68 | * 设置ims连接超时时长,0表示默认使用ims的值 69 | * 70 | * @return 71 | */ 72 | @Override 73 | public int getConnectTimeout() { 74 | return 0; 75 | } 76 | 77 | /** 78 | * 设置应用在前台时ims心跳间隔时长,0表示默认使用ims的值 79 | * 80 | * @return 81 | */ 82 | @Override 83 | public int getForegroundHeartbeatInterval() { 84 | return 0; 85 | } 86 | 87 | /** 88 | * 设置应用在后台时ims心跳间隔时长,0表示默认使用ims的值 89 | * 90 | * @return 91 | */ 92 | @Override 93 | public int getBackgroundHeartbeatInterval() { 94 | return 0; 95 | } 96 | 97 | /** 98 | * 构建握手消息 99 | * 100 | * @return 101 | */ 102 | @Override 103 | public MessageProtobuf.Msg getHandshakeMsg() { 104 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 105 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 106 | headBuilder.setMsgId(UUID.randomUUID().toString()); 107 | headBuilder.setMsgType(MessageType.HANDSHAKE.getMsgType()); 108 | headBuilder.setFromId(userId); 109 | headBuilder.setTimestamp(System.currentTimeMillis()); 110 | 111 | JSONObject jsonObj = new JSONObject(); 112 | jsonObj.put("token", token); 113 | headBuilder.setExtend(jsonObj.toString()); 114 | builder.setHead(headBuilder.build()); 115 | 116 | return builder.build(); 117 | } 118 | 119 | /** 120 | * 构建心跳消息 121 | * 122 | * @return 123 | */ 124 | @Override 125 | public MessageProtobuf.Msg getHeartbeatMsg() { 126 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 127 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 128 | headBuilder.setMsgId(UUID.randomUUID().toString()); 129 | headBuilder.setMsgType(MessageType.HEARTBEAT.getMsgType()); 130 | headBuilder.setFromId(userId); 131 | headBuilder.setTimestamp(System.currentTimeMillis()); 132 | builder.setHead(headBuilder.build()); 133 | 134 | return builder.build(); 135 | } 136 | 137 | /** 138 | * 服务端返回的消息发送状态报告消息类型 139 | * 140 | * @return 141 | */ 142 | @Override 143 | public int getServerSentReportMsgType() { 144 | return MessageType.SERVER_MSG_SENT_STATUS_REPORT.getMsgType(); 145 | } 146 | 147 | /** 148 | * 客户端提交的消息接收状态报告消息类型 149 | * 150 | * @return 151 | */ 152 | @Override 153 | public int getClientReceivedReportMsgType() { 154 | return MessageType.CLIENT_MSG_RECEIVED_STATUS_REPORT.getMsgType(); 155 | } 156 | 157 | /** 158 | * 设置ims消息发送超时重发次数,0表示默认使用ims的值 159 | * 160 | * @return 161 | */ 162 | @Override 163 | public int getResendCount() { 164 | return 0; 165 | } 166 | 167 | /** 168 | * 设置ims消息发送超时重发间隔时长,0表示默认使用ims的值 169 | * 170 | * @return 171 | */ 172 | @Override 173 | public int getResendInterval() { 174 | return 0; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/IMessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | import com.freddy.chat.bean.BaseMessage; 5 | import com.freddy.chat.bean.ContentMessage; 6 | 7 | /** 8 | *@ProjectName: NettyChat
9 | *@ClassName: IMessageProcessor.java
10 | *@PackageName: com.freddy.chat.im
11 | * 12 | *@Description: 消息处理器接口
13 | * 14 | *@author: FreddyChen
15 | *@date: 2019/04/10 00:11
16 | *@email: chenshichao@outlook.com
17 | */ 18 | public interface IMessageProcessor { 19 | 20 | void receiveMsg(AppMessage message); 21 | void sendMsg(AppMessage message); 22 | void sendMsg(ContentMessage message); 23 | void sendMsg(BaseMessage message); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | import com.freddy.chat.bean.BaseMessage; 5 | import com.freddy.chat.bean.ContentMessage; 6 | import com.freddy.chat.bean.Head; 7 | import com.freddy.chat.utils.StringUtil; 8 | import com.freddy.im.protobuf.MessageProtobuf; 9 | 10 | /** 11 | *@ProjectName: BoChat
12 | *@ClassName: MessageBuilder.java
13 | *@PackageName: com.bochat.app.message
14 | * 15 | *@Description: 消息转换
16 | * 17 | *@author: FreddyChen
18 | *@date: 2019/02/07 17:26
19 | *@email: chenshichao@outlook.com
20 | */ 21 | public class MessageBuilder { 22 | 23 | /** 24 | * 根据聊天消息,生成一条可以能够传输通讯的消息 25 | * 26 | * @param msgId 27 | * @param type 28 | * @param subType 29 | * @param fromId 30 | * @param toId 31 | * @param extend 32 | * @param content 33 | * @return 34 | */ 35 | public static AppMessage buildAppMessage(String msgId, int type, int subType, String fromId, 36 | String toId, String extend, String content) { 37 | AppMessage message = new AppMessage(); 38 | Head head = new Head(); 39 | head.setMsgId(msgId); 40 | head.setMsgType(type); 41 | head.setMsgContentType(subType); 42 | head.setFromId(fromId); 43 | head.setToId(toId); 44 | head.setExtend(extend); 45 | message.setHead(head); 46 | message.setBody(content); 47 | 48 | return message; 49 | } 50 | 51 | /** 52 | * 根据聊天消息,生成一条可以能够传输通讯的消息 53 | * 54 | * @param msg 55 | * @return 56 | */ 57 | public static AppMessage buildAppMessage(ContentMessage msg) { 58 | AppMessage message = new AppMessage(); 59 | Head head = new Head(); 60 | head.setMsgId(msg.getMsgId()); 61 | head.setMsgType(msg.getMsgType()); 62 | head.setMsgContentType(msg.getMsgContentType()); 63 | head.setFromId(msg.getFromId()); 64 | head.setToId(msg.getToId()); 65 | head.setTimestamp(msg.getTimestamp()); 66 | head.setExtend(msg.getExtend()); 67 | message.setHead(head); 68 | message.setBody(msg.getContent()); 69 | 70 | return message; 71 | } 72 | 73 | /** 74 | * 根据聊天消息,生成一条可以能够传输通讯的消息 75 | * 76 | * @param msg 77 | * @return 78 | */ 79 | public static AppMessage buildAppMessage(BaseMessage msg) { 80 | AppMessage message = new AppMessage(); 81 | Head head = new Head(); 82 | head.setMsgId(msg.getMsgId()); 83 | head.setMsgType(msg.getMsgType()); 84 | head.setMsgContentType(msg.getMsgContentType()); 85 | head.setFromId(msg.getFromId()); 86 | head.setToId(msg.getToId()); 87 | head.setExtend(msg.getExtend()); 88 | head.setTimestamp(msg.getTimestamp()); 89 | message.setHead(head); 90 | message.setBody(msg.getContent()); 91 | 92 | return message; 93 | } 94 | 95 | /** 96 | * 根据业务消息对象获取protoBuf消息对应的builder 97 | * 98 | * @param message 99 | * @return 100 | */ 101 | public static MessageProtobuf.Msg.Builder getProtoBufMessageBuilderByAppMessage(AppMessage message) { 102 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 103 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 104 | headBuilder.setMsgType(message.getHead().getMsgType()); 105 | headBuilder.setStatusReport(message.getHead().getStatusReport()); 106 | headBuilder.setMsgContentType(message.getHead().getMsgContentType()); 107 | if (!StringUtil.isEmpty(message.getHead().getMsgId())) 108 | headBuilder.setMsgId(message.getHead().getMsgId()); 109 | if (!StringUtil.isEmpty(message.getHead().getFromId())) 110 | headBuilder.setFromId(message.getHead().getFromId()); 111 | if (!StringUtil.isEmpty(message.getHead().getToId())) 112 | headBuilder.setToId(message.getHead().getToId()); 113 | if (message.getHead().getTimestamp() != 0) 114 | headBuilder.setTimestamp(message.getHead().getTimestamp()); 115 | if (!StringUtil.isEmpty(message.getHead().getExtend())) 116 | headBuilder.setExtend(message.getHead().getExtend()); 117 | if (!StringUtil.isEmpty(message.getBody())) 118 | builder.setBody(message.getBody()); 119 | builder.setHead(headBuilder); 120 | return builder; 121 | } 122 | 123 | /** 124 | * 通过protobuf消息对象获取业务消息对象 125 | * 126 | * @param protobufMessage 127 | * @return 128 | */ 129 | public static AppMessage getMessageByProtobuf( 130 | MessageProtobuf.Msg protobufMessage) { 131 | AppMessage message = new AppMessage(); 132 | Head head = new Head(); 133 | MessageProtobuf.Head protoHead = protobufMessage.getHead(); 134 | head.setMsgType(protoHead.getMsgType()); 135 | head.setStatusReport(protoHead.getStatusReport()); 136 | head.setMsgContentType(protoHead.getMsgContentType()); 137 | head.setMsgId(protoHead.getMsgId()); 138 | head.setFromId(protoHead.getFromId()); 139 | head.setToId(protoHead.getToId()); 140 | head.setTimestamp(protoHead.getTimestamp()); 141 | head.setExtend(protoHead.getExtend()); 142 | message.setHead(head); 143 | message.setBody(protobufMessage.getBody()); 144 | return message; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageProcessor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | import com.freddy.chat.bean.BaseMessage; 7 | import com.freddy.chat.bean.ContentMessage; 8 | import com.freddy.chat.im.handler.IMessageHandler; 9 | import com.freddy.chat.im.handler.MessageHandlerFactory; 10 | import com.freddy.chat.utils.CThreadPoolExecutor; 11 | 12 | /** 13 | *@ProjectName: NettyChat
14 | *@ClassName: MessageProcessor.java
15 | *@PackageName: com.freddy.chat.im
16 | * 17 | *@Description: 消息处理器
18 | * 19 | *@author: FreddyChen
20 | *@date: 2019/04/10 03:27
21 | *@email: chenshichao@outlook.com
22 | */ 23 | public class MessageProcessor implements IMessageProcessor { 24 | 25 | private static final String TAG = MessageProcessor.class.getSimpleName(); 26 | 27 | private MessageProcessor() { 28 | 29 | } 30 | 31 | private static class MessageProcessorInstance { 32 | private static final IMessageProcessor INSTANCE = new MessageProcessor(); 33 | } 34 | 35 | public static IMessageProcessor getInstance() { 36 | return MessageProcessorInstance.INSTANCE; 37 | } 38 | 39 | /** 40 | * 接收消息 41 | * @param message 42 | */ 43 | @Override 44 | public void receiveMsg(final AppMessage message) { 45 | CThreadPoolExecutor.runInBackground(new Runnable() { 46 | 47 | @Override 48 | public void run() { 49 | try { 50 | IMessageHandler messageHandler = MessageHandlerFactory.getHandlerByMsgType(message.getHead().getMsgType()); 51 | if (messageHandler != null) { 52 | messageHandler.execute(message); 53 | } else { 54 | Log.e(TAG, "未找到消息处理handler,msgType=" + message.getHead().getMsgType()); 55 | } 56 | } catch (Exception e) { 57 | Log.e(TAG, "消息处理出错,reason=" + e.getMessage()); 58 | } 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * 发送消息 65 | * 66 | * @param message 67 | */ 68 | @Override 69 | public void sendMsg(final AppMessage message) { 70 | CThreadPoolExecutor.runInBackground(new Runnable() { 71 | 72 | @Override 73 | public void run() { 74 | boolean isActive = IMSClientBootstrap.getInstance().isActive(); 75 | if (isActive) { 76 | IMSClientBootstrap.getInstance().sendMessage(MessageBuilder.getProtoBufMessageBuilderByAppMessage(message).build()); 77 | } else { 78 | Log.e(TAG, "发送消息失败"); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | /** 85 | * 发送消息 86 | * 87 | * @param message 88 | */ 89 | @Override 90 | public void sendMsg(ContentMessage message) { 91 | this.sendMsg(MessageBuilder.buildAppMessage(message)); 92 | } 93 | 94 | /** 95 | * 发送消息 96 | * 97 | * @param message 98 | */ 99 | @Override 100 | public void sendMsg(BaseMessage message) { 101 | this.sendMsg(MessageBuilder.buildAppMessage(message)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im; 2 | 3 | /** 4 | *@ProjectName: NettyChat
5 | *@ClassName: MessageType.java
6 | *@PackageName: com.freddy.chat.im
7 | * 8 | *@Description: 消息类型
9 | * 10 | *@author: FreddyChen
11 | *@date: 2019/04/08 00:04
12 | *@email: chenshichao@outlook.com
13 | */ 14 | public enum MessageType { 15 | 16 | /* 17 | * 握手消息 18 | */ 19 | HANDSHAKE(1001), 20 | 21 | /* 22 | * 心跳消息 23 | */ 24 | HEARTBEAT(1002), 25 | 26 | /* 27 | * 客户端提交的消息接收状态报告 28 | */ 29 | CLIENT_MSG_RECEIVED_STATUS_REPORT(1009), 30 | 31 | /* 32 | * 服务端返回的消息发送状态报告 33 | */ 34 | SERVER_MSG_SENT_STATUS_REPORT(1010), 35 | 36 | /** 37 | * 单聊消息 38 | */ 39 | SINGLE_CHAT(2001), 40 | 41 | /** 42 | * 群聊消息 43 | */ 44 | GROUP_CHAT(3001); 45 | 46 | private int msgType; 47 | 48 | MessageType(int msgType) { 49 | this.msgType = msgType; 50 | } 51 | 52 | public int getMsgType() { 53 | return this.msgType; 54 | } 55 | 56 | public enum MessageContentType { 57 | 58 | /** 59 | * 文本消息 60 | */ 61 | TEXT(101), 62 | 63 | /** 64 | * 图片消息 65 | */ 66 | IMAGE(102), 67 | 68 | /** 69 | * 语音消息 70 | */ 71 | VOICE(103); 72 | 73 | private int msgContentType; 74 | 75 | MessageContentType(int msgContentType) { 76 | this.msgContentType = msgContentType; 77 | } 78 | 79 | public int getMsgContentType() { 80 | return this.msgContentType; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/AbstractMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: AbstractMessageHandler.java
8 | *@PackageName: com.freddy.chat.im.handler
9 | * 10 | *@Description: 抽象的MessageHandler
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/10 03:41
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public abstract class AbstractMessageHandler implements IMessageHandler { 17 | 18 | @Override 19 | public void execute(AppMessage message) { 20 | action(message); 21 | } 22 | 23 | protected abstract void action(AppMessage message); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/GroupChatMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | 7 | /** 8 | *@ProjectName: NettyChat
9 | *@ClassName: GroupChatMessageHandler.java
10 | *@PackageName: com.freddy.chat.im.handler
11 | * 12 | *@Description: 类描述
13 | * 14 | *@author: FreddyChen
15 | *@date: 2019/04/10 03:43
16 | *@email: chenshichao@outlook.com
17 | */ 18 | public class GroupChatMessageHandler extends AbstractMessageHandler { 19 | 20 | private static final String TAG = GroupChatMessageHandler.class.getSimpleName(); 21 | 22 | @Override 23 | protected void action(AppMessage message) { 24 | Log.d(TAG, "收到群聊消息,message=" + message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/IMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import com.freddy.chat.bean.AppMessage; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: IMessageHandler.java
8 | *@PackageName: com.freddy.chat.im.handler
9 | * 10 | *@Description: 类描述
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/04/10 03:41
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public interface IMessageHandler { 17 | 18 | void execute(AppMessage message); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/MessageHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.freddy.chat.im.MessageType; 6 | 7 | /** 8 | *@ProjectName: NettyChat
9 | *@ClassName: MessageHandlerFactory.java
10 | *@PackageName: com.freddy.chat.im.handler
11 | * 12 | *@Description: 消息处理handler工厂
13 | * 14 | *@author: FreddyChen
15 | *@date: 2019/04/10 03:44
16 | *@email: chenshichao@outlook.com
17 | */ 18 | public class MessageHandlerFactory { 19 | 20 | private MessageHandlerFactory() { 21 | 22 | } 23 | 24 | private static final SparseArray@ProjectName: NettyChat
9 | *@ClassName: ServerReportMessageHandler.java
10 | *@PackageName: com.freddy.chat.im.handler
11 | * 12 | *@Description: 服务端返回的消息发送状态报告
13 | * 14 | *@author: FreddyChen
15 | *@date: 2019/04/22 19:16
16 | *@email: chenshichao@outlook.com
17 | */ 18 | public class ServerReportMessageHandler extends AbstractMessageHandler { 19 | 20 | private static final String TAG = ServerReportMessageHandler.class.getSimpleName(); 21 | 22 | @Override 23 | protected void action(AppMessage message) { 24 | Log.d(TAG, "收到消息状态报告,message=" + message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/im/handler/SingleChatMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.im.handler; 2 | 3 | import android.util.Log; 4 | 5 | import com.freddy.chat.bean.AppMessage; 6 | import com.freddy.chat.bean.SingleMessage; 7 | import com.freddy.chat.event.CEventCenter; 8 | import com.freddy.chat.event.Events; 9 | 10 | /** 11 | *@ProjectName: NettyChat
12 | *@ClassName: SingleChatMessageHandler.java
13 | *@PackageName: com.freddy.chat.im.handler
14 | * 15 | *@Description: 类描述
16 | * 17 | *@author: FreddyChen
18 | *@date: 2019/04/10 03:43
19 | *@email: chenshichao@outlook.com
20 | */ 21 | public class SingleChatMessageHandler extends AbstractMessageHandler { 22 | 23 | private static final String TAG = SingleChatMessageHandler.class.getSimpleName(); 24 | 25 | @Override 26 | protected void action(AppMessage message) { 27 | Log.d(TAG, "收到单聊消息,message=" + message); 28 | 29 | SingleMessage msg = new SingleMessage(); 30 | msg.setMsgId(message.getHead().getMsgId()); 31 | msg.setMsgType(message.getHead().getMsgType()); 32 | msg.setMsgContentType(message.getHead().getMsgContentType()); 33 | msg.setFromId(message.getHead().getFromId()); 34 | msg.setToId(message.getHead().getToId()); 35 | msg.setTimestamp(message.getHead().getTimestamp()); 36 | msg.setExtend(message.getHead().getExtend()); 37 | msg.setContent(message.getBody()); 38 | 39 | 40 | CEventCenter.dispatchEvent(Events.CHAT_SINGLE_MESSAGE, 0, 0, msg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/freddy/chat/utils/CThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package com.freddy.chat.utils; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.Map; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.Future; 14 | import java.util.concurrent.LinkedBlockingQueue; 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.ThreadPoolExecutor; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | /** 21 | *@ProjectName: BoChat
22 | *@ClassName: CThreadPoolExecutor.java
23 | *@PackageName: com.bochat.app.utils
24 | * 25 | *@Description: 自定义固定大小的线程池 26 | * 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 27 | * 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 28 | *
29 | * 合理利用线程池能够带来三个好处: 30 | * 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 31 | * 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 32 | * 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 33 | * 我们可以通过ThreadPoolExecutor来创建一个线程池: 34 | * new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler); 35 | *
36 | * corePoolSize(线程池的基本大小): 37 | * 当提交一个任务到线程池时,线程池会创建一个线程来执行任务, 38 | * 即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。 39 | * 如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。 40 | *
41 | * runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。 42 | * ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 43 | *
44 | * LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。 45 | * 静态工厂方法Executors.newFixedThreadPool()使用了这个队列。 46 | *
47 | * SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 48 | * 吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 49 | *
50 | * PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 51 | *
52 | * maximumPoolSize(线程池最大大小): 53 | * 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。 54 | *
55 | * ThreadFactory: 56 | * 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。 57 | *
58 | * RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。 59 | * 这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 60 | * AbortPolicy:直接抛出异常。 61 | * CallerRunsPolicy:只用调用者所在线程来运行任务。 62 | * DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 63 | * DiscardPolicy:不处理,丢弃掉。 64 | * 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。 65 | *
66 | * keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。 67 | * 所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 68 | *
69 | * TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES), 70 | * 毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
71 | * 72 | *@author: FreddyChen
73 | *@date: 2019/2/3 15:35
74 | *@email: chenshichao@outlook.com
75 | * 76 | * @see http://www.infoq.com/cn/articles/java-threadPool 77 | */ 78 | public class CThreadPoolExecutor { 79 | 80 | private static final String TAG = CThreadPoolExecutor.class.getSimpleName(); 81 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();// CPU个数 82 | // private static final int CORE_POOL_SIZE = CPU_COUNT + 1;// 线程池中核心线程的数量 83 | // private static final int MAXIMUM_POOL_SIZE = 2 * CPU_COUNT + 1;// 线程池中最大线程数量 84 | private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));// 线程池中核心线程的数量 85 | private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;// 线程池中最大线程数量 86 | private static final long KEEP_ALIVE_TIME = 30L;// 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长 87 | private static final int WAIT_COUNT = 128; // 最多排队个数,这里控制线程创建的频率 88 | 89 | private static ThreadPoolExecutor pool = createThreadPoolExecutor(); 90 | 91 | private static ThreadPoolExecutor createThreadPoolExecutor() { 92 | if (pool == null) { 93 | pool = new ThreadPoolExecutor( 94 | CORE_POOL_SIZE, 95 | MAXIMUM_POOL_SIZE, 96 | KEEP_ALIVE_TIME, 97 | TimeUnit.SECONDS, 98 | new LinkedBlockingQueue145 | * 146 | * StringUtil.equals(null, null) = true 147 | * StringUtil.equals(null, "abc") = false 148 | * StringUtil.equals("abc", null) = false 149 | * StringUtil.equals("abc", "abc") = true 150 | * StringUtil.equals("abc", "ABC") = false 151 | * 152 | *153 | * 154 | * @param str1 155 | * 要比较的字符串1 156 | * @param str2 157 | * 要比较的字符串2 158 | * 159 | * @return 如果两个字符串相同,或者都是
null
,则返回 true
160 | */
161 | public static boolean equals(String str1, String str2)
162 | {
163 | if ( str1 == null )
164 | {
165 | return str2 == null;
166 | }
167 |
168 | return str1.equals(str2);
169 | }
170 |
171 | /**
172 | * 利用正则表达式判断字符串是否是数字
173 | * @param str
174 | * @return
175 | */
176 | public static boolean isNumeric(String str){
177 | Pattern pattern = Pattern.compile("[0-9]*");
178 | Matcher isNum = pattern.matcher(str);
179 | if( !isNum.matches() ){
180 | return false;
181 | }
182 | return true;
183 | }
184 |
185 | /**
186 | * 获取较纯净的手机号码@ProjectName: NettyChat
8 | *@ClassName: ExecutorServiceFactory.java
9 | *@PackageName: com.freddy.im
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 | initBossLoopGroup(1); 27 | } 28 | 29 | /** 30 | * 初始化boss线程池 31 | * 重载 32 | * 33 | * @param size 线程池大小 34 | */ 35 | public synchronized void initBossLoopGroup(int size) { 36 | destroyBossLoopGroup(); 37 | bossPool = Executors.newFixedThreadPool(size); 38 | } 39 | 40 | /** 41 | * 初始化work线程池 42 | */ 43 | public synchronized void initWorkLoopGroup() { 44 | initWorkLoopGroup(1); 45 | } 46 | 47 | /** 48 | * 初始化work线程池 49 | * 重载 50 | * 51 | * @param size 线程池大小 52 | */ 53 | public synchronized void initWorkLoopGroup(int size) { 54 | destroyWorkLoopGroup(); 55 | workPool = Executors.newFixedThreadPool(size); 56 | } 57 | 58 | /** 59 | * 执行boss任务 60 | * 61 | * @param r 62 | */ 63 | public void execBossTask(Runnable r) { 64 | if (bossPool == null) { 65 | initBossLoopGroup(); 66 | } 67 | bossPool.execute(r); 68 | } 69 | 70 | /** 71 | * 执行work任务 72 | * 73 | * @param r 74 | */ 75 | public void execWorkTask(Runnable r) { 76 | if (workPool == null) { 77 | initWorkLoopGroup(); 78 | } 79 | workPool.execute(r); 80 | } 81 | 82 | /** 83 | * 释放boss线程池 84 | */ 85 | public synchronized void destroyBossLoopGroup() { 86 | if (bossPool != null) { 87 | try { 88 | bossPool.shutdownNow(); 89 | } catch (Throwable t) { 90 | t.printStackTrace(); 91 | } finally { 92 | bossPool = null; 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * 释放work线程池 99 | */ 100 | public synchronized void destroyWorkLoopGroup() { 101 | if (workPool != null) { 102 | try { 103 | workPool.shutdownNow(); 104 | } catch (Throwable t) { 105 | t.printStackTrace(); 106 | } finally { 107 | workPool = null; 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * 释放所有线程池 114 | */ 115 | public synchronized void destroy() { 116 | destroyBossLoopGroup(); 117 | destroyWorkLoopGroup(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/HeartbeatHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.netty.NettyTcpClient; 4 | import com.freddy.im.protobuf.MessageProtobuf; 5 | 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | 11 | /** 12 | *@ProjectName: NettyChat
13 | *@ClassName: HeartbeatHandler.java
14 | *@PackageName: com.freddy.im
15 | * 16 | *@Description: 心跳任务管理器
17 | * 18 | *@author: FreddyChen
19 | *@date: 2019/04/08 01:34
20 | *@email: chenshichao@outlook.com
21 | */ 22 | public class HeartbeatHandler extends ChannelInboundHandlerAdapter { 23 | 24 | private NettyTcpClient imsClient; 25 | public HeartbeatHandler(NettyTcpClient imsClient) { 26 | this.imsClient = imsClient; 27 | } 28 | 29 | @Override 30 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 31 | super.userEventTriggered(ctx, evt); 32 | if (evt instanceof IdleStateEvent) { 33 | IdleState state = ((IdleStateEvent) evt).state(); 34 | switch (state) { 35 | case READER_IDLE: { 36 | // 规定时间内没收到服务端心跳包响应,进行重连操作 37 | imsClient.resetConnect(false); 38 | break; 39 | } 40 | 41 | case WRITER_IDLE: { 42 | // 规定时间内没向服务端发送心跳包,即发送一个心跳包 43 | if (heartbeatTask == null) { 44 | heartbeatTask = new HeartbeatTask(ctx); 45 | } 46 | 47 | imsClient.getLoopGroup().execWorkTask(heartbeatTask); 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | 54 | private HeartbeatTask heartbeatTask; 55 | private class HeartbeatTask implements Runnable { 56 | 57 | private ChannelHandlerContext ctx; 58 | 59 | public HeartbeatTask(ChannelHandlerContext ctx) { 60 | this.ctx = ctx; 61 | } 62 | 63 | @Override 64 | public void run() { 65 | if (ctx.channel().isActive()) { 66 | MessageProtobuf.Msg heartbeatMsg = imsClient.getHeartbeatMsg(); 67 | if (heartbeatMsg == null) { 68 | return; 69 | } 70 | System.out.println("发送心跳消息,message=" + heartbeatMsg + "当前心跳间隔为:" + imsClient.getHeartbeatInterval() + "ms\n"); 71 | imsClient.sendMsg(heartbeatMsg, false); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/HeartbeatRespHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.netty.NettyTcpClient; 4 | import com.freddy.im.protobuf.MessageProtobuf; 5 | 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | /** 10 | *@ProjectName: NettyChat
11 | *@ClassName: HeartbeatRespHandler.java
12 | *@PackageName: com.freddy.im
13 | * 14 | *@Description: 心跳消息响应处理handler
15 | * 16 | *@author: FreddyChen
17 | *@date: 2019/04/08 01:08
18 | *@email: chenshichao@outlook.com
19 | */ 20 | public class HeartbeatRespHandler extends ChannelInboundHandlerAdapter { 21 | 22 | private NettyTcpClient imsClient; 23 | 24 | public HeartbeatRespHandler(NettyTcpClient imsClient) { 25 | this.imsClient = imsClient; 26 | } 27 | 28 | @Override 29 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 30 | MessageProtobuf.Msg heartbeatRespMsg = (MessageProtobuf.Msg) msg; 31 | if (heartbeatRespMsg == null || heartbeatRespMsg.getHead() == null) { 32 | return; 33 | } 34 | 35 | MessageProtobuf.Msg heartbeatMsg = imsClient.getHeartbeatMsg(); 36 | if (heartbeatMsg == null || heartbeatMsg.getHead() == null) { 37 | return; 38 | } 39 | 40 | int heartbeatMsgType = heartbeatMsg.getHead().getMsgType(); 41 | if (heartbeatMsgType == heartbeatRespMsg.getHead().getMsgType()) { 42 | System.out.println("收到服务端心跳响应消息,message=" + heartbeatRespMsg); 43 | } else { 44 | // 消息透传 45 | ctx.fireChannelRead(msg); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/IMSClientFactory.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.interf.IMSClientInterface; 4 | import com.freddy.im.netty.NettyTcpClient; 5 | 6 | /** 7 | *@ProjectName: NettyChat
8 | *@ClassName: IMSClientFactory.java
9 | *@PackageName: com.freddy.im
10 | * 11 | *@Description: ims实例工厂方法
12 | * 13 | *@author: FreddyChen
14 | *@date: 2019/03/31 20:54
15 | *@email: chenshichao@outlook.com
16 | */ 17 | public class IMSClientFactory { 18 | 19 | public static IMSClientInterface getIMSClient() { 20 | return NettyTcpClient.getInstance(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/IMSConfig.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | /** 4 | *@ProjectName: NettyChat
5 | *@ClassName: IMSConfig.java
6 | *@PackageName: com.freddy.im
7 | * 8 | *@Description: IMS默认配置,若不使用默认配置,应提供set方法给应用层设置
9 | * 10 | *@author: FreddyChen
11 | *@date: 2019/04/05 05:38
12 | *@email: chenshichao@outlook.com
13 | */ 14 | public class IMSConfig { 15 | 16 | // 默认重连一个周期失败间隔时长 17 | public static final int DEFAULT_RECONNECT_INTERVAL = 3 * 1000; 18 | // 连接超时时长 19 | public static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000; 20 | // 默认一个周期重连次数 21 | public static final int DEFAULT_RECONNECT_COUNT = 3; 22 | // 默认重连起始延时时长,重连规则:最大n次,每次延时n * 起始延时时长,重连次数达到n次后,重置 23 | public static final int DEFAULT_RECONNECT_BASE_DELAY_TIME = 3 * 1000; 24 | // 默认消息发送失败重发次数 25 | public static final int DEFAULT_RESEND_COUNT = 3; 26 | // 默认消息重发间隔时长 27 | public static final int DEFAULT_RESEND_INTERVAL = 8 * 1000; 28 | // 默认应用在前台时心跳消息间隔时长 29 | public static final int DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND = 3 * 1000; 30 | // 默认应用在后台时心跳消息间隔时长 31 | public static final int DEFAULT_HEARTBEAT_INTERVAL_BACKGROUND = 30 * 1000; 32 | // 应用在前台标识 33 | public static final int APP_STATUS_FOREGROUND = 0; 34 | // 应用在后台标识 35 | public static final int APP_STATUS_BACKGROUND = -1; 36 | public static final String KEY_APP_STATUS = "key_app_status"; 37 | // 默认服务端返回的消息发送成功状态报告 38 | public static final int DEFAULT_REPORT_SERVER_SEND_MSG_SUCCESSFUL = 1; 39 | // 默认服务端返回的消息发送失败状态报告 40 | public static final int DEFAULT_REPORT_SERVER_SEND_MSG_FAILURE = 0; 41 | // ims连接状态:连接中 42 | public static final int CONNECT_STATE_CONNECTING = 0; 43 | // ims连接状态:连接成功 44 | public static final int CONNECT_STATE_SUCCESSFUL = 1; 45 | // ims连接状态:连接失败 46 | public static final int CONNECT_STATE_FAILURE = -1; 47 | } 48 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/LoginAuthRespHandler.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.freddy.im.interf.IMSClientInterface; 6 | import com.freddy.im.netty.NettyTcpClient; 7 | import com.freddy.im.protobuf.MessageProtobuf; 8 | 9 | import java.util.UUID; 10 | 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.ChannelInboundHandlerAdapter; 13 | 14 | /** 15 | *@ProjectName: NettyChat
16 | *@ClassName: LoginAuthRespHandler.java
17 | *@PackageName: com.freddy.im
18 | * 19 | *@Description: 握手认证消息响应处理handler
20 | * 21 | *@author: FreddyChen
22 | *@date: 2019/04/07 23:11
23 | *@email: chenshichao@outlook.com
24 | */ 25 | public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter { 26 | 27 | private NettyTcpClient imsClient; 28 | 29 | public LoginAuthRespHandler(NettyTcpClient imsClient) { 30 | this.imsClient = imsClient; 31 | } 32 | 33 | @Override 34 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | MessageProtobuf.Msg handshakeRespMsg = (MessageProtobuf.Msg) msg; 36 | if (handshakeRespMsg == null || handshakeRespMsg.getHead() == null) { 37 | return; 38 | } 39 | 40 | MessageProtobuf.Msg handshakeMsg = imsClient.getHandshakeMsg(); 41 | if (handshakeMsg == null || handshakeMsg.getHead() == null) { 42 | return; 43 | } 44 | 45 | int handshakeMsgType = handshakeMsg.getHead().getMsgType(); 46 | if (handshakeMsgType == handshakeRespMsg.getHead().getMsgType()) { 47 | System.out.println("收到服务端握手响应消息,message=" + handshakeRespMsg); 48 | int status = -1; 49 | try { 50 | JSONObject jsonObj = JSON.parseObject(handshakeRespMsg.getHead().getExtend()); 51 | status = jsonObj.getIntValue("status"); 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | } finally { 55 | if (status == 1) { 56 | // 握手成功,马上先发送一条心跳消息,至于心跳机制管理,交由HeartbeatHandler 57 | MessageProtobuf.Msg heartbeatMsg = imsClient.getHeartbeatMsg(); 58 | if (heartbeatMsg == null) { 59 | return; 60 | } 61 | 62 | // 握手成功,检查消息发送超时管理器里是否有发送超时的消息,如果有,则全部重发 63 | imsClient.getMsgTimeoutTimerManager().onResetConnected(); 64 | 65 | System.out.println("发送心跳消息:" + heartbeatMsg + "当前心跳间隔为:" + imsClient.getHeartbeatInterval() + "ms\n"); 66 | imsClient.sendMsg(heartbeatMsg); 67 | 68 | // 添加心跳消息管理handler 69 | imsClient.addHeartbeatHandler(); 70 | } else { 71 | imsClient.resetConnect(false);// 握手失败,触发重连 72 | } 73 | } 74 | } else { 75 | // 消息透传 76 | ctx.fireChannelRead(msg); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/MsgDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.listener.OnEventListener; 4 | import com.freddy.im.protobuf.MessageProtobuf; 5 | 6 | /** 7 | *@ProjectName: NettyChat
8 | *@ClassName: MsgDispatcher.java
9 | *@PackageName: com.freddy.im
10 | * 11 | *@Description: 消息转发器,负责将接收到的消息转发到应用层
12 | * 13 | *@author: FreddyChen
14 | *@date: 2019/04/05 05:05
15 | *@email: chenshichao@outlook.com
16 | */ 17 | public class MsgDispatcher { 18 | 19 | private OnEventListener mOnEventListener; 20 | 21 | public MsgDispatcher() { 22 | 23 | } 24 | 25 | public void setOnEventListener(OnEventListener listener) { 26 | this.mOnEventListener = listener; 27 | } 28 | 29 | /** 30 | * 接收消息,并通过OnEventListener转发消息到应用层 31 | * @param msg 32 | */ 33 | public void receivedMsg(MessageProtobuf.Msg msg) { 34 | if(mOnEventListener == null) { 35 | return; 36 | } 37 | 38 | mOnEventListener.dispatchMsg(msg); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/MsgTimeoutTimer.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.interf.IMSClientInterface; 4 | import com.freddy.im.protobuf.MessageProtobuf; 5 | 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | /** 10 | *@ProjectName: NettyChat
11 | *@ClassName: MsgTimeoutTimer.java
12 | *@PackageName: com.freddy.im
13 | * 14 | *@Description: 消息发送超时定时器,每一条消息对应一个定时器
15 | * 16 | *@author: FreddyChen
17 | *@date: 2019/04/09 22:38
18 | *@email: chenshichao@outlook.com
19 | */ 20 | public class MsgTimeoutTimer extends Timer { 21 | 22 | private IMSClientInterface imsClient;// ims客户端 23 | private MessageProtobuf.Msg msg;// 发送的消息 24 | private int currentResendCount;// 当前重发次数 25 | private MsgTimeoutTask task;// 消息发送超时任务 26 | 27 | public MsgTimeoutTimer(IMSClientInterface imsClient, MessageProtobuf.Msg msg) { 28 | this.imsClient = imsClient; 29 | this.msg = msg; 30 | task = new MsgTimeoutTask(); 31 | this.schedule(task, imsClient.getResendInterval(), imsClient.getResendInterval()); 32 | } 33 | 34 | /** 35 | * 消息发送超时任务 36 | */ 37 | private class MsgTimeoutTask extends TimerTask { 38 | 39 | @Override 40 | public void run() { 41 | if (imsClient.isClosed()) { 42 | if (imsClient.getMsgTimeoutTimerManager() != null) { 43 | imsClient.getMsgTimeoutTimerManager().remove(msg.getHead().getMsgId()); 44 | } 45 | 46 | return; 47 | } 48 | 49 | currentResendCount++; 50 | if (currentResendCount > imsClient.getResendCount()) { 51 | // 重发次数大于可重发次数,直接标识为发送失败,并通过消息转发器通知应用层 52 | try { 53 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 54 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 55 | headBuilder.setMsgId(msg.getHead().getMsgId()); 56 | headBuilder.setMsgType(imsClient.getServerSentReportMsgType()); 57 | headBuilder.setTimestamp(System.currentTimeMillis()); 58 | headBuilder.setStatusReport(IMSConfig.DEFAULT_REPORT_SERVER_SEND_MSG_FAILURE); 59 | builder.setHead(headBuilder.build()); 60 | 61 | // 通知应用层消息发送失败 62 | imsClient.getMsgDispatcher().receivedMsg(builder.build()); 63 | } finally { 64 | // 从消息发送超时管理器移除该消息 65 | imsClient.getMsgTimeoutTimerManager().remove(msg.getHead().getMsgId()); 66 | // 执行到这里,认为连接已断开或不稳定,触发重连 67 | imsClient.resetConnect(); 68 | currentResendCount = 0; 69 | } 70 | } else { 71 | // 发送消息,但不再加入超时管理器,达到最大发送失败次数就算了 72 | sendMsg(); 73 | } 74 | } 75 | } 76 | 77 | public void sendMsg() { 78 | System.out.println("正在重发消息,message=" + msg); 79 | imsClient.sendMsg(msg, false); 80 | } 81 | 82 | public MessageProtobuf.Msg getMsg() { 83 | return msg; 84 | } 85 | 86 | @Override 87 | public void cancel() { 88 | if (task != null) { 89 | task.cancel(); 90 | task = null; 91 | } 92 | 93 | super.cancel(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/MsgTimeoutTimerManager.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im; 2 | 3 | import com.freddy.im.interf.IMSClientInterface; 4 | import com.freddy.im.protobuf.MessageProtobuf; 5 | 6 | import java.util.Iterator; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | import io.netty.util.internal.StringUtil; 11 | 12 | /** 13 | *@ProjectName: NettyChat
14 | *@ClassName: MsgTimeoutTimerManager.java
15 | *@PackageName: com.freddy.im
16 | * 17 | *@Description: 消息发送超时管理器,用于管理消息定时器的新增、移除等
18 | * 19 | *@author: FreddyChen
20 | *@date: 2019/04/09 22:42
21 | *@email: chenshichao@outlook.com
22 | */ 23 | public class MsgTimeoutTimerManager { 24 | 25 | private Map@ProjectName: BoChat
30 | *@ClassName: NettyServerDemo.java
31 | *@PackageName: com.bochat.im.netty
32 | * 33 | *@Description: TCP netty服务端
34 | * 35 | *@author: FreddyChen
36 | *@date: 2019/02/15 14:42
37 | *@email: chenshichao@outlook.com
38 | */ 39 | public class NettyServerDemo { 40 | 41 | public static void main(String[] args) { 42 | 43 | //boss线程监听端口,worker线程负责数据读写 44 | EventLoopGroup boss = new NioEventLoopGroup(); 45 | EventLoopGroup worker = new NioEventLoopGroup(); 46 | 47 | try { 48 | //辅助启动类 49 | ServerBootstrap bootstrap = new ServerBootstrap(); 50 | //设置线程池 51 | bootstrap.group(boss, worker); 52 | 53 | //设置socket工厂 54 | bootstrap.channel(NioServerSocketChannel.class); 55 | 56 | //设置管道工厂 57 | bootstrap.childHandler(new ChannelInitializer@ProjectName: NettyChat
13 | *@ClassName: IMSClientInterface.java
14 | *@PackageName: com.freddy.im.interf
15 | * 16 | *@Description: ims抽象接口,需要切换到其它方式实现im功能,实现此接口即可
17 | * 18 | *@author: FreddyChen
19 | *@date: 2019/03/31 20:04
20 | *@email: chenshichao@outlook.com
21 | */ 22 | public interface IMSClientInterface { 23 | 24 | /** 25 | * 初始化 26 | * 27 | * @param serverUrlList 服务器地址列表 28 | * @param listener 与应用层交互的listener 29 | * @param callback ims连接状态回调 30 | */ 31 | void init(Vector@ProjectName: NettyChat
5 | *@ClassName: IMSConnectStatusCallback.java
6 | *@PackageName: com.freddy.im.listener
7 | * 8 | *@Description: IMS连接状态回调
9 | * 10 | *@author: FreddyChen
11 | *@date: 2019/03/31 20:07
12 | *@email: chenshichao@outlook.com
13 | */ 14 | public interface IMSConnectStatusCallback { 15 | 16 | /** 17 | * ims连接中 18 | */ 19 | void onConnecting(); 20 | 21 | /** 22 | * ims连接成功 23 | */ 24 | void onConnected(); 25 | 26 | /** 27 | * ims连接失败 28 | */ 29 | void onConnectFailed(); 30 | } 31 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/listener/OnEventListener.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im.listener; 2 | 3 | import com.freddy.im.protobuf.MessageProtobuf; 4 | 5 | /** 6 | *@ProjectName: NettyChat
7 | *@ClassName: OnEventListener.java
8 | *@PackageName: com.freddy.im.listener
9 | * 10 | *@Description: 与应用层交互的listener
11 | * 12 | *@author: FreddyChen
13 | *@date: 2019/03/31 20:06
14 | *@email: chenshichao@outlook.com
15 | */ 16 | public interface OnEventListener { 17 | 18 | /** 19 | * 分发消息到应用层 20 | * 21 | * @param msg 22 | */ 23 | void dispatchMsg(MessageProtobuf.Msg msg); 24 | 25 | /** 26 | * 从应用层获取网络是否可用 27 | * 28 | * @return 29 | */ 30 | boolean isNetworkAvailable(); 31 | 32 | /** 33 | * 获取重连间隔时长 34 | * 35 | * @return 36 | */ 37 | int getReconnectInterval(); 38 | 39 | /** 40 | * 获取连接超时时长 41 | * 42 | * @return 43 | */ 44 | int getConnectTimeout(); 45 | 46 | /** 47 | * 获取应用在前台时心跳间隔时间 48 | * 49 | * @return 50 | */ 51 | int getForegroundHeartbeatInterval(); 52 | 53 | /** 54 | * 获取应用在前台时心跳间隔时间 55 | * 56 | * @return 57 | */ 58 | int getBackgroundHeartbeatInterval(); 59 | 60 | /** 61 | * 获取由应用层构造的握手消息 62 | * 63 | * @return 64 | */ 65 | MessageProtobuf.Msg getHandshakeMsg(); 66 | 67 | /** 68 | * 获取由应用层构造的心跳消息 69 | * 70 | * @return 71 | */ 72 | MessageProtobuf.Msg getHeartbeatMsg(); 73 | 74 | /** 75 | * 获取应用层消息发送状态报告消息类型 76 | * 77 | * @return 78 | */ 79 | int getServerSentReportMsgType(); 80 | 81 | /** 82 | * 获取应用层消息接收状态报告消息类型 83 | * 84 | * @return 85 | */ 86 | int getClientReceivedReportMsgType(); 87 | 88 | /** 89 | * 获取应用层消息发送超时重发次数 90 | * 91 | * @return 92 | */ 93 | int getResendCount(); 94 | 95 | /** 96 | * 获取应用层消息发送超时重发间隔 97 | * 98 | * @return 99 | */ 100 | int getResendInterval(); 101 | } 102 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/netty/NettyTcpClient.java: -------------------------------------------------------------------------------- 1 | package com.freddy.im.netty; 2 | 3 | 4 | import com.freddy.im.ExecutorServiceFactory; 5 | import com.freddy.im.HeartbeatHandler; 6 | import com.freddy.im.IMSConfig; 7 | import com.freddy.im.MsgDispatcher; 8 | import com.freddy.im.MsgTimeoutTimerManager; 9 | import com.freddy.im.interf.IMSClientInterface; 10 | import com.freddy.im.listener.IMSConnectStatusCallback; 11 | import com.freddy.im.listener.OnEventListener; 12 | import com.freddy.im.protobuf.MessageProtobuf; 13 | 14 | import java.util.Vector; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import io.netty.bootstrap.Bootstrap; 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.ChannelOption; 20 | import io.netty.channel.EventLoopGroup; 21 | import io.netty.channel.nio.NioEventLoopGroup; 22 | import io.netty.channel.socket.nio.NioSocketChannel; 23 | import io.netty.handler.timeout.IdleStateHandler; 24 | import io.netty.util.internal.StringUtil; 25 | 26 | /** 27 | *@ProjectName: NettyChat
28 | *@ClassName: NettyTcpClient.java
29 | *@PackageName: com.freddy.im.netty
30 | * 31 | *@Description: 基于netty实现的tcp ims
32 | * 33 | *@author: FreddyChen
34 | *@date: 2019/03/31 20:41
35 | *@email: chenshichao@outlook.com
36 | */ 37 | public class NettyTcpClient implements IMSClientInterface { 38 | 39 | private static volatile NettyTcpClient instance; 40 | 41 | private Bootstrap bootstrap; 42 | private Channel channel; 43 | 44 | private boolean isClosed = false;// 标识ims是否已关闭 45 | private Vector@ProjectName: NettyChat
17 | *@ClassName: TCPChannelInitializerHandler.java
18 | *@PackageName: com.freddy.im.netty
19 | * 20 | *@Description: Channel初始化配置
21 | * 22 | *@author: FreddyChen
23 | *@date: 2019/04/05 07:11
24 | *@email: chenshichao@outlook.com
25 | */ 26 | public class TCPChannelInitializerHandler extends ChannelInitializer@ProjectName: NettyChat
17 | *@ClassName: TCPReadHandler.java
18 | *@PackageName: com.freddy.im.netty
19 | * 20 | *@Description: 消息接收处理handler
21 | * 22 | *@author: FreddyChen
23 | *@date: 2019/04/07 21:40
24 | *@email: chenshichao@outlook.com
25 | */ 26 | public class TCPReadHandler extends ChannelInboundHandlerAdapter { 27 | 28 | private NettyTcpClient imsClient; 29 | 30 | public TCPReadHandler(NettyTcpClient imsClient) { 31 | this.imsClient = imsClient; 32 | } 33 | 34 | @Override 35 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 36 | super.channelInactive(ctx); 37 | System.err.println("TCPReadHandler channelInactive()"); 38 | Channel channel = ctx.channel(); 39 | if (channel != null) { 40 | channel.close(); 41 | ctx.close(); 42 | } 43 | 44 | // 触发重连 45 | imsClient.resetConnect(false); 46 | } 47 | 48 | 49 | 50 | @Override 51 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 52 | super.exceptionCaught(ctx, cause); 53 | System.err.println("TCPReadHandler exceptionCaught()"); 54 | Channel channel = ctx.channel(); 55 | if (channel != null) { 56 | channel.close(); 57 | ctx.close(); 58 | } 59 | 60 | // 触发重连 61 | imsClient.resetConnect(false); 62 | } 63 | 64 | @Override 65 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 66 | MessageProtobuf.Msg message = (MessageProtobuf.Msg) msg; 67 | if (message == null || message.getHead() == null) { 68 | return; 69 | } 70 | 71 | int msgType = message.getHead().getMsgType(); 72 | if (msgType == imsClient.getServerSentReportMsgType()) { 73 | int statusReport = message.getHead().getStatusReport(); 74 | System.out.println(String.format("服务端状态报告:「%d」, 1代表成功,0代表失败", statusReport)); 75 | if (statusReport == IMSConfig.DEFAULT_REPORT_SERVER_SEND_MSG_SUCCESSFUL) { 76 | System.out.println("收到服务端消息发送状态报告,message=" + message + ",从超时管理器移除"); 77 | imsClient.getMsgTimeoutTimerManager().remove(message.getHead().getMsgId()); 78 | } 79 | } else { 80 | // 其它消息 81 | // 收到消息后,立马给服务端回一条消息接收状态报告 82 | System.out.println("收到消息,message=" + message); 83 | MessageProtobuf.Msg receivedReportMsg = buildReceivedReportMsg(message.getHead().getMsgId()); 84 | if(receivedReportMsg != null) { 85 | imsClient.sendMsg(receivedReportMsg); 86 | } 87 | } 88 | 89 | // 接收消息,由消息转发器转发到应用层 90 | imsClient.getMsgDispatcher().receivedMsg(message); 91 | } 92 | 93 | /** 94 | * 构建客户端消息接收状态报告 95 | * @param msgId 96 | * @return 97 | */ 98 | private MessageProtobuf.Msg buildReceivedReportMsg(String msgId) { 99 | if (StringUtil.isNullOrEmpty(msgId)) { 100 | return null; 101 | } 102 | 103 | MessageProtobuf.Msg.Builder builder = MessageProtobuf.Msg.newBuilder(); 104 | MessageProtobuf.Head.Builder headBuilder = MessageProtobuf.Head.newBuilder(); 105 | headBuilder.setMsgId(UUID.randomUUID().toString()); 106 | headBuilder.setMsgType(imsClient.getClientReceivedReportMsgType()); 107 | headBuilder.setTimestamp(System.currentTimeMillis()); 108 | JSONObject jsonObj = new JSONObject(); 109 | jsonObj.put("msgId", msgId); 110 | headBuilder.setExtend(jsonObj.toString()); 111 | builder.setHead(headBuilder.build()); 112 | 113 | return builder.build(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /im_lib/src/main/java/com/freddy/im/protobuf/msg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3";// 指定protobuf版本 2 | option java_package = "com.freddy.im.protobuf";// 指定包名 3 | option java_outer_classname = "MessageProtobuf";// 指定生成的类名 4 | 5 | message Msg { 6 | Head head = 1;// 消息头 7 | string body = 2;// 消息体 8 | } 9 | 10 | message Head { 11 | string msgId = 1;// 消息id 12 | int32 msgType = 2;// 消息类型 13 | int32 msgContentType = 3;// 消息内容类型 14 | string fromId = 4;// 消息发送者id 15 | string toId = 5;// 消息接收者id 16 | int64 timestamp = 6;// 消息时间戳 17 | int32 statusReport = 7;// 状态报告 18 | string extend = 8;// 扩展字段,以key/value形式存放的json 19 | } -------------------------------------------------------------------------------- /im_lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 |