Welcome to reprint, reprint please indicate the source: juejin.cn/post/685844…

Writing in the front

In the last article developing commercial-grade IM(1) with me — Technology Selection and Protocol Definition, we completed the technology selection, to review:

Communication protocol
  • TCP
  • WebSocket
Transfer protocol
  • Protobuf
  • Json
Communication framework
  • Netty

Next, we will implement the interface definition and encapsulation of Android client and Java server respectively based on the above protocol and framework. At this stage, we only need to define the interface and appropriate encapsulation, no specific implementation is needed.

Due to space, only the core part of the code can be posted. In the following articles, we will also explain it in the form of text + part of the core code. If you need the complete code, please go to Github.

Post a Kula HD map town building:

This article only covers the definition and encapsulation of the interface, and the implementation will be covered separately in a subsequent article.

After analysis, our IM Service (IMS for short) should have the following interfaces:

  • Initialize the
  • The connection
  • reconnection
  • disconnect
  • Send a message
  • Release resources

So let’s begin the encapsulation.

The interface definition

This step is relatively simple: define an IMSInterface, write some interface methods in it, and then implement NettyTCPIMS and NettyWebSocketIMS, respectively.

/** * @author FreddyChen * @name IMS abstract interface */ public interface IMSInterface {}Copy the code
/** * @author FreddyChen * @name Netty TCP IM Service */ public class NettyTCPIMS implements IMSInterface {private NettyTCPIMS() {} public static NettyTCPIMS getInstance() { return SingletonHolder.INSTANCE; } private static class SingletonHolder { private static final NettyTCPIMS INSTANCE = new NettyTCPIMS(); }}Copy the code
/** * @author FreddyChen * @name Netty WebSocket IM Service, */ Public class NettyWebSocketIMS implements IMSInterface {private NettyWebSocketIMS() {} public static NettyWebSocketIMS getInstance() { return SingletonHolder.INSTANCE; } private static class SingletonHolder { private static final NettyWebSocketIMS INSTANCE = new NettyWebSocketIMS(); }}Copy the code

With the interface defined above, let’s define the specific methods separately (method implementation will be covered in a future article).

Initialize the

An excellent SDK should be configurable and easy to expand. After analysis, it is not difficult to find that IMS should support a large number of parameter configurations, such as:

  • Communication Protocol (TCP/WebSocket)
  • Transport Protocol (Protobuf/Json)
  • Connection timeout
  • Reconnection interval time
  • Server address
  • Interval before and after heartbeat
  • Whether to automatically resend messages
  • Maximum number of retransmissions of messages
  • Message retransmission interval

The above parameters should not be fixed in IMS. IMS can provide default values and support the application layer (caller) to configure them. We can use “Builder mode” to optimize this situation. Therefore, the initialization interface method can be defined as:

/** * initialize ** @param context * @param options IMS initialization * @param connectStatusListener IMS connection status listener * @param MsgReceivedListener IMS message receiving listener */ void init(Context Context, IMSOptions options, IMSConnectStatusListener connectStatusListener, IMSMsgReceivedListener msgReceivedListener);Copy the code
/** * @author FreddyChen * @name IMS initialization configuration item */ public class IMSOptions {private CommunicationProtocol communicationProtocol; Private TransportProtocol TransportProtocol; Private int connectTimeout; // Connection timeout duration, in milliseconds private int reconnectInterval; // Reconnection interval, in milliseconds private int reconnectCount; / / a single address a cycle number of rewiring the largest private int foregroundHeartbeatInterval; / / application at the front desk when the heartbeat interval, unit: ms private int backgroundHeartbeatInterval; Private Boolean autoResend; private Boolean autoResend; // Whether to automatically resend messages private int resendInterval; Private int resendCount; private int resendCount; Private List<String> serverList; Private IMSOptions(Builder Builder) {if (Builder == null) return; this.communicationProtocol = builder.communicationProtocol; this.transportProtocol = builder.transportProtocol; this.connectTimeout = builder.connectTimeout; this.reconnectInterval = builder.reconnectInterval; this.reconnectCount = builder.reconnectCount; this.foregroundHeartbeatInterval = builder.foregroundHeartbeatInterval; this.backgroundHeartbeatInterval = builder.backgroundHeartbeatInterval; this.autoResend = builder.autoResend; this.resendInterval = builder.resendInterval; this.resendCount = builder.resendCount; this.serverList = builder.serverList; } public CommunicationProtocol getCommunicationProtocol() { return communicationProtocol; } public TransportProtocol getTransportProtocol() { return transportProtocol; } public int getConnectTimeout() { return connectTimeout; } public int getReconnectInterval() { return reconnectInterval; } public int getReconnectCount() { return reconnectCount; } public int getForegroundHeartbeatInterval() { return foregroundHeartbeatInterval; } public int getBackgroundHeartbeatInterval() { return backgroundHeartbeatInterval; } public boolean isAutoResend() { return autoResend; } public int getResendInterval() { return resendInterval; } public int getResendCount() { return resendCount; } public List<String> getServerList() { return serverList; } public static class Builder { private CommunicationProtocol communicationProtocol; Private TransportProtocol TransportProtocol; Private int connectTimeout; // Connection timeout duration, in milliseconds private int reconnectInterval; // Reconnection interval, in milliseconds private int reconnectCount; / / a single address a cycle number of rewiring the largest private int foregroundHeartbeatInterval; / / application at the front desk when the heartbeat interval, unit: ms private int backgroundHeartbeatInterval; Private Boolean autoResend; private Boolean autoResend; // Whether to automatically resend messages private int resendInterval; Private int resendCount; private int resendCount; Private List<String> serverList; Public Builder() {this.connectTimeout = imsconfig.connect_timeout; this.reconnectInterval = IMSConfig.RECONNECT_INTERVAL; this.reconnectCount = IMSConfig.RECONNECT_COUNT; this.foregroundHeartbeatInterval = IMSConfig.FOREGROUND_HEARTBEAT_INTERVAL; this.backgroundHeartbeatInterval = IMSConfig.BACKGROUND_HEARTBEAT_INTERVAL; this.autoResend = IMSConfig.AUTO_RESEND; this.resendInterval = IMSConfig.RESEND_INTERVAL; this.resendCount = IMSConfig.RESEND_COUNT; } public Builder setCommunicationProtocol(CommunicationProtocol communicationProtocol) { this.communicationProtocol = communicationProtocol; return this; } public Builder setTransportProtocol(TransportProtocol transportProtocol) { this.transportProtocol = transportProtocol;  return this; } public Builder setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; return this; } public Builder setReconnectInterval(int reconnectInterval) { this.reconnectInterval = reconnectInterval; return this; } public Builder setReconnectCount(int reconnectCount) { this.reconnectCount = reconnectCount; return this; } public Builder setForegroundHeartbeatInterval(int foregroundHeartbeatInterval) { this.foregroundHeartbeatInterval = foregroundHeartbeatInterval; return this; } public Builder setBackgroundHeartbeatInterval(int backgroundHeartbeatInterval) { this.backgroundHeartbeatInterval = backgroundHeartbeatInterval; return this; } public Builder setAutoResend(boolean autoResend) { this.autoResend = autoResend; return this; } public Builder setResendInterval(int resendInterval) { this.resendInterval = resendInterval; return this; } public Builder setResendCount(int resendCount) { this.resendCount = resendCount; return this; } public Builder setServerList(List<String> serverList) { this.serverList = serverList; return this; } public IMSOptions build() { return new IMSOptions(this); }}}Copy the code
/** * IMS config */ public class IMSConfig {public static final int CONNECT_TIMEOUT = 10 * 1000; Public static final int RECONNECT_INTERVAL = 8 * 1000; Public static final int RECONNECT_COUNT = 3; Public static final int FOREGROUND_HEARTBEAT_INTERVAL = 8 * 1000; // Maximum number of reconnections for an ADDRESS ina period public static final int FOREGROUND_HEARTBEAT_INTERVAL = 8 * 1000; Public static final int BACKGROUND_HEARTBEAT_INTERVAL = 30 * 1000; public static final int BACKGROUND_HEARTBEAT_INTERVAL = 30 * 1000; Public static final Boolean AUTO_RESEND = true; Public static final int RESEND_INTERVAL = 3 * 1000; // Automatic retransmission interval, in milliseconds}Copy the code
/** * @author FreddyChen * @name public enum CommunicationProtocol {TCP, WebSocket}Copy the code
/** * @author FreddyChen * @name */ public enum TransportProtocol {Protobuf, Json}Copy the code
/** * @author FreddyChen * @name IMS connection status listener */ public interface IMSConnectStatusListener {void onUnconnected(); void onConnecting(); void onConnected(); void onConnectFailed(); }Copy the code
Public interface IMSMsgReceivedListener {void onMsgReceived(IMSMsg MSG); /** * @author FreddyChen * @name IMS message receiver */ public interface IMSMsgReceivedListener {void onMsgReceived(IMSMsg MSG);  }Copy the code

Protobuf and Json transport protocols are supported, so you need to package an IMSMsg implementation compatible:

/** * @author FreddyChen * @name IMS message, generic message format definition, can be converted to JSON or protobuf transmission */ public class IMSMsg {private String msgId; Private int msgType; // Message type private String sender; // Sender id private String receiver; Private long timestamp; // Message sending time, in milliseconds private int report; // Message sending status report private String Content; Private int contentType; // Message content type private String data; Public IMSMsg(Builder Builder) {if(Builder == null) {return; } this.msgId = builder.msgId; this.msgType = builder.msgType; this.sender = builder.sender; this.receiver = builder.receiver; this.timestamp = builder.timestamp; this.report = builder.report; this.content = builder.content; this.contentType = builder.contentType; this.data = builder.data; } public String getMsgId() { return msgId; } public int getMsgType() { return msgType; } public String getSender() { return sender; } public String getReceiver() { return receiver; } public long getTimestamp() { return timestamp; } public int getReport() { return report; } public String getContent() { return content; } public int getContentType() { return contentType; } public String getData() { return data; } public static class Builder { private String msgId; Private int msgType; // Message type private String sender; // Sender id private String receiver; Private long timestamp; // Message sending time, in milliseconds private int report; // Message sending status report private String Content; Private int contentType; // Message content type private String data; Public Builder() {this.msgid = UUID. GenerateShortUuid (); } public Builder setMsgType(int msgType) { this.msgType = msgType; return this; } public Builder setSender(String sender) { this.sender = sender; return this; } public Builder setReceiver(String receiver) { this.receiver = receiver; return this; } public Builder setTimestamp(long timestamp) { this.timestamp = timestamp; return this; } public Builder setReport(int report) { this.report = report; return this; } public Builder setContent(String content) { this.content = content; return this; } public Builder setContentType(int contentType) { this.contentType = contentType; return this; } public Builder setData(String data) { this.data = data; return this; } public IMSMsg build() { return new IMSMsg(this); }}}Copy the code

The connection

/** * connect */ void connect();Copy the code

The first connection can also be considered a reconnect, so you can call reconnect(true) directly when you call connect().

reconnection

/** * reconnect(Boolean isFirstConnect); /** * reconnect(Boolean isFirstConnect);Copy the code

During the reconnection, determine whether the connection is the first one according to isFirstConnect. If the connection is the first one, you can directly make the connection. Otherwise, you can delay the connection for a period of time. It helps to improve the success rate of the next connection and avoid making connections too frequently, saving resources.

disconnect

/** * disconnect();Copy the code

Disconnecting only disconnects long connections and does not release resources. The next time you connect, you don’t need to call the init() method again to initialize.

Send a message

There are many ways to send messages. One way is to send messages directly without paying attention to the message sending status. The other is to add the message sending status listener to facilitate the application layer perception. In addition, you can add the message resending timer. If you add the message resending timer, the message automatically resends the specified maximum number of times after the message is sent out. The message fails to be sent when the number of times exceeds the maximum number of times.

/** * @param MSG */ void sendMsg(IMSMsg MSG); Void sendMsg(IMSMsg MSG, IMSMsg); IMSMsgSentStatusListener listener); /** * @param MSG * @param isJoinResendManager */ void sendMsg(IMSMsg MSG, boolean isJoinResendManager); /** * Send message * reload ** @param MSG * @param Listener Message sending status listener * @param isJoinResendManager Whether to join the message resending manager */ void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener, boolean isJoinResendManager);Copy the code

Message sending status listeners are defined as follows:

/** * @author FreddyChen * @name IMS message sending status listener */ public interface IMSMsgSentStatusListener {/** * Message sending success */ void onSendSucceed(IMSMsg msg); /** * message sending failed */ void onSendFailed(IMSMsg MSG, String errMsg); }Copy the code

Note: The message sent successfully means that the message sent by client A has reached the server and received the reply receipt from the server, but does not necessarily reach another client B. For client A, the message is successfully sent as long as it reaches the server.

Release resources

/** * release resources */ void release();Copy the code

To release resources means to disconnect the long connection and release all resources. The next time you need to connect, you need to call init() again to initialize the connection.

Paste the final IMSInterface:

/** * @author FreddyChen * @name IMS abstract interface * @desc */ public interface IMSInterface {/** * Initialization ** @param context * @param options IMS initialization * @param ConnectStatusListener IMS connection status listener * @param msgReceivedListener IMS message receiving listener */ IMSInterface init(Context Context, IMSOptions options, IMSConnectStatusListener connectStatusListener, IMSMsgReceivedListener msgReceivedListener); /** * connect */ void connect(); /** * reconnect(Boolean isFirstConnect); /** * reconnect(Boolean isFirstConnect); /** * disconnect(); /** * @param MSG */ void sendMsg(IMSMsg MSG); Void sendMsg(IMSMsg MSG, IMSMsg); IMSMsgSentStatusListener listener); /** * @param MSG * @param isJoinResendManager */ void sendMsg(IMSMsg MSG, boolean isJoinResendManager); /** * Send message * reload ** @param MSG * @param Listener Message sending status listener * @param isJoinResendManager Whether to join the message resending manager */ void sendMsg(IMSMsg msg, IMSMsgSentStatusListener listener, boolean isJoinResendManager); /** * release resources */ void release(); }Copy the code

Then implement NettyTCPIMS and NettyWebSocket respectively. As for the specific implementation, it will be explained separately in the next article.

Java server-side code

Java server code is basically the same, considering the length and other reasons, the code will not be posted, has been uploaded to Kulachat-server, students who need to jump to Github to check.

Post the Java server code structure:

Note: msg.proto on the Java server, I directly copy the file written on the Android client before, and suggest that you try to keep the version of protobuf consistent, otherwise there may be protocol compatibility problems.

Write in the last

So far, we have defined the basic interface of IMS, and we can implement it on the basis of the interface definition. As I am writing the article while writing the project, it is inevitable that there are some inconsiderate places, I hope you can point out to me, and improve together.

In the next article, I will explain the connection and reconnection part, and analyze in detail when the connection should be reconnected and how to execute the logic of reconnection, etc. The stability of the long connection is the focus of our attention, only when the long connection is stable, can we continue to develop other functions.

It is too exhausting to write the article while writing the project. At the same time, I have to take into account the Android client and Java server, and I am busy with my work, so the progress will be a little slow, about one article every ten days. So don’t worry.

PS: The newly opened public account can not leave a message, if you have different opinions or suggestions, you can go to the nuggets comment or add to the QQ group: 1015178804, if the group is full, you can also give me a private message on the public account, thank you.

Post the official number:

FreddyChen

See you next article, Goodby