原文转载自:https://www.t-io.org/1154654880242212864
对接基于TIO旗舰版(其实大同小异),基本是按照新手对接的样子写的文档,老司机可能会觉得啰嗦,望见谅。
java ByteBuffer 操作
object-c byte 操作
socket 基础操作
以上三个是对接的前提
官网文档中未详细描述App对接的流程,所以在对接的时候出现了一些小问题,对此先对对接流程进行补充整理如下:
1、连接(必须SSL)
2、握手
3、心跳
注:握手验签的时候,sgin MD5后必须为大写,后台代码强制为大写验证;
android:OkSocket
ios: CocoaAsyncSocket
socket 基础配置及联系
package com.biuo.sdk;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import android.content.Context;
import android.support.annotation.NonNull;
import com.biuo.sdk.common.sendable.BaseSend;
import com.biuo.sdk.common.sendable.HeartBeatSend;
import com.biuo.sdk.constans.Constants;
import com.biuo.sdk.exception.SendException;
import com.biuo.sdk.handler.ImActionHandler;
import com.biuo.sdk.handler.ImHeadHandler;
import com.xuhao.didi.socket.client.sdk.OkSocket;
import com.xuhao.didi.socket.client.sdk.client.ConnectionInfo;
import com.xuhao.didi.socket.client.sdk.client.OkSocketOptions;
import com.xuhao.didi.socket.client.sdk.client.OkSocketSSLConfig;
import com.xuhao.didi.socket.client.sdk.client.bean.IPulse;
import com.xuhao.didi.socket.client.sdk.client.connection.IConnectionManager;
/**
* IM连接管理
*
* @author zhangjunhu
*/
public class ImClient {
private static ImClient mImClient;
public static Context context;
private ConnectionInfo mConnectionInfo;
private String ip;
private String cId;
private int port;
private int connectTimeout = 1;//连接超时时间,单位:秒
private int heartbeatTimeout = 30 * 1000;//心跳超时时间,单位:毫秒
private int reconnectMaximum = 60; //最大重连次数
private boolean isSslConnect = true;//是否开启SSL链接
private IConnectionManager connectionManager;
public static ImClient getInstance() {
if (null == mImClient) {
mImClient = new ImClient();
}
return mImClient;
}
private ImClient() {
}
/**
* 初始化调用,在Application中初始化,为后续网络状态判断等提供
*
* @param context
*/
public void init(Context context) {
ImClient.context = context;
}
/**
*
* @param ip
* @param port
* @return
*/
public ImClient setConnectInfo(@NonNull String ip, @NonNull int port) {
this.ip = ip;
this.port = port;
return this;
}
/**
* 连接超时时间,单位:秒
*
* @param connectTimeout
* @return
*/
public ImClient setConnectTimeout(@NonNull int connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}
/**
* 心跳超时时间,单位:毫秒
*
* @param heartbeatTimeout
* @return
*/
public ImClient setHeartbeatTimeout(@NonNull int heartbeatTimeout) {
this.heartbeatTimeout = heartbeatTimeout;
return this;
}
/**
* 最大重连次数
*
* @param reconnectMaximum
* @return
*/
public ImClient setReconnectMaximum(@NonNull int reconnectMaximum) {
this.reconnectMaximum = reconnectMaximum;
return this;
}
public ImClient isSslConnect(@NonNull boolean isSslConnect) {
this.isSslConnect = isSslConnect;
return this;
}
public String getcId() {
return cId;
}
public void setcId(String cId) {
this.cId = cId;
}
private ConnectionInfo getConnectionInfo() {
if (mConnectionInfo == null) {
mConnectionInfo = new ConnectionInfo(this.ip, this.port);
}
return mConnectionInfo;
}
private OkSocketSSLConfig getSSlConfig() {
OkSocketSSLConfig.Builder mSSLConfig = new OkSocketSSLConfig.Builder();
try {
mSSLConfig.setProtocol(SSLContext.getInstance("SSL").getProtocol());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return mSSLConfig.build();
}
private OkSocketOptions.Builder getDefaultSocketOptions(@NonNull OkSocketOptions okSocketOptions) {
return new OkSocketOptions.Builder(okSocketOptions)
.setWritePackageBytes(Constants.MAX_LENGTH_OF_BODY)
.setReadPackageBytes(Constants.MAX_LENGTH_OF_BODY)
.setConnectionHolden(true)
.setConnectTimeoutSecond(connectTimeout)//链接超时时间设置
.setPulseFrequency(heartbeatTimeout)//心跳设置
.setPulseFeedLoseTimes(reconnectMaximum)//最大重连次数
.setReaderProtocol(ImHeadHandler.getInstance());//设置报文头解析
}
/**
* 连接
*/
public synchronized void connect() {
connectionManager = OkSocket.open(getConnectionInfo());
connectionManager.setIsConnectionHolder(true);
OkSocketOptions.Builder builder = getDefaultSocketOptions(connectionManager.getOption());
if (isSslConnect) {//开启SSL链接
builder.setSSLConfig(getSSlConfig());
}
connectionManager.option(builder.build());
connectionManager.registerReceiver(ImActionHandler.getInstance(this));//监听消息发送接口及状态
connectionManager.connect();
}
/**
* 获取链接管理器
*
* @return
*/
public IConnectionManager getConnectionManager() {
return connectionManager;
}
/**
* 关闭连接
*/
public synchronized void disconnect() {
if (null == connectionManager || !connectionManager.isConnect() || connectionManager.isDisconnecting()) {
return;
}
connectionManager.disconnect();
}
/**
* 获取心跳检测配置
*
* @return
*/
public IPulse getHeartBeatPulse() {
return connectionManager.getPulseManager()
.setPulseSendable(new HeartBeatSend());//只需要设置一次,下一次可以直接调用pulse()
}
/**
* 信息发送
*
* @param baseSend
* @throws SendException
*/
public void sendData(BaseSend baseSend) throws SendException {
if (null == connectionManager || !connectionManager.isConnect() || connectionManager.isDisconnecting())
throw new SendException("服务未连接");
connectionManager.send(baseSend);
}
}
package com.biuo.sdk.common.sendable;
import android.annotation.SuppressLint;
import com.biuo.sdk.common.Command;
import com.biuo.sdk.common.ImPacket;
import com.biuo.sdk.common.bs.Base;
import com.biuo.utils.SystemTools;
import com.xuhao.didi.core.iocore.interfaces.ISendable;
/**
* 信息发送基类
* @author zhangjunhu
*/
public class BaseSend implements ISendable {
private Command command;
private Base base;
public BaseSend(Command command) {
this.command = command;
}
public BaseSend(Command command, Base base) {
this.command = command;
this.base = base;
}
public void setCommand(Command command) {
this.command = command;
}
public void setBody(Base base) {
this.base = base;
}
@SuppressLint("NewApi")
@Override
public byte[] parse() {
return SystemTools.encode(new ImPacket(command, base)).array();
}
}
package com.biuo.utils;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import android.annotation.SuppressLint;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson.JSON;
import com.biuo.sdk.entity.ImPacket;
import com.biuo.sdk.common.bs.Base;
import com.biuo.sdk.constans.Constants;
import com.biuo.sdk.exception.DecodeException;
import com.goboosoft.commons.utils.LogUtils;
/**
* 数据加解密
* @author zhangjunhu
*/
@SuppressLint("SimpleDateFormat")
public final class SystemTools {
/**
*
* @param imPacket
* @return
*/
public static ByteBuffer encode(ImPacket imPacket) {
byte[] body = imPacket.getBody();
if (body == null) {
String bodyStr = imPacket.getBodyStr();
if (StrUtil.isBlank(bodyStr)) {
Base bodyObj = imPacket.getBodyObj();
if (bodyObj != null) {
bodyStr = JSON.toJSONString(bodyObj);
}
}
if (StrUtil.isNotBlank(bodyStr)) {
try {
body = bodyStr.getBytes(Constants.CHARSET);
} catch (UnsupportedEncodingException e) {
LogUtils.e(e.getMessage());
}
}
}
int bodyLen = 0;
boolean isCompress = false;
if (body != null) {
bodyLen = body.length;
if (bodyLen > Constants.SIZE_FOR_COMPRESS) {
try {
byte[] gzipedbody = ZipUtil.gzip(body);
if (gzipedbody.length < body.length) {
LogUtils.i("压缩前:" + body.length + ", 压缩后: " + gzipedbody.length);
body = gzipedbody;
bodyLen = gzipedbody.length;
isCompress = true;
}
} catch (Throwable e) {
LogUtils.e(e.getMessage());
}
}
}
int allLen = Constants.LEAST_HEADER_LENGTH + bodyLen;
LogUtils.i("allLen: " + allLen + ", bodyLen: " + bodyLen + ", getCommand: " + imPacket.getCommand().getValue());
ByteBuffer buffer = ByteBuffer.allocate(allLen);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putShort((short) bodyLen);
buffer.putShort(imPacket.getCommand().getValue());
buffer.put((byte) (isCompress ? 1 : 0)) ;
if (body != null) {
buffer.put(body);
}
return buffer;
}
public static String decode(ByteBuffer buffer, short bodyLength, boolean isGzipped) throws DecodeException, UnsupportedEncodingException {
String content = "";
if (bodyLength > Constants.MAX_LENGTH_OF_BODY || bodyLength < 0) {
throw new DecodeException("bodyLength [" + bodyLength + "] is not right");
}
if (buffer.array().length == 0) {
return content;
}
byte[] contentBytes = buffer.array();
if (isGzipped) {
byte[] dst = new byte[bodyLength];
buffer.get(dst);
contentBytes = ZipUtil.unGzip(dst);
}
content = new String(contentBytes, Constants.CHARSET);
return content;
}
}
package com.biuo.sdk.handler;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.biuo.sdk.constans.Constants;
import com.xuhao.didi.core.protocol.IReaderProtocol;
/**
* IM消息头解析
* @author zhangjunhu
*/
public class ImHeadHandler implements IReaderProtocol {
public static ImHeadHandler getInstance() {return new ImHeadHandler();}
private ImHeadHandler() {}
@Override
public int getHeaderLength() {
return Constants.LEAST_HEADER_LENGTH;
}
@Override
public int getBodyLength(byte[] header, ByteOrder byteOrder) {
ByteBuffer byteBuffer = ByteBuffer.wrap(header);
return byteBuffer.getShort();
}
}
主要对IM的连接状及消息收发状态进行监听及处理
package com.biuo.sdk.handler;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import org.greenrobot.eventbus.EventBus;
import android.annotation.SuppressLint;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.biuo.sdk.ImClient;
import com.biuo.sdk.common.Command;
import com.biuo.sdk.common.bs.P2PChatNtf;
import com.biuo.sdk.common.enums.ConnectStatus;
import com.biuo.sdk.event.ImConnectEvent;
import com.biuo.sdk.event.MsgAcceptEnent;
import com.biuo.sdk.exception.DecodeException;
import com.biuo.utils.SystemTools;
import com.goboosoft.commons.utils.AppConfig;
import com.goboosoft.commons.utils.LogUtils;
import com.xuhao.didi.core.iocore.interfaces.IPulseSendable;
import com.xuhao.didi.core.iocore.interfaces.ISendable;
import com.xuhao.didi.core.pojo.OriginalData;
import com.xuhao.didi.socket.client.sdk.client.ConnectionInfo;
import com.xuhao.didi.socket.client.sdk.client.action.SocketActionAdapter;
/**
* IM状态处理
* @author zhangjunhu
*/
public class ImActionHandler extends SocketActionAdapter {
private ImClient mClient;
public static ImActionHandler getInstance(ImClient mClient) {return new ImActionHandler(mClient);}
private ImActionHandler(ImClient mClient) {
this.mClient = mClient;
}
@SuppressLint("CheckResult")
@Override
public void onSocketConnectionSuccess(ConnectionInfo info, String action) {
EventBus.getDefault().postSticky(ImConnectEvent.getInstance(ConnectStatus.CONNECT_SUCCESS));
}
@Override
public void onSocketConnectionFailed(ConnectionInfo info, String action, Exception e) {
EventBus.getDefault().postSticky(ImConnectEvent.getInstance(ConnectStatus.CONNECT_FAIL));
}
@Override
public void onSocketDisconnection(ConnectionInfo info, String action, Exception e) {
EventBus.getDefault().postSticky(ImConnectEvent.getInstance(ConnectStatus.CONNECT_CLOSE));
}
public void onSocketReadResponse(ConnectionInfo info, String action, OriginalData data) {
//每次收到消息进行oksocket 的喂狗操作
//对协议头进行解析
ByteBuffer headBuffer = ByteBuffer.wrap(data.getHeadBytes());
short bodyLength = headBuffer.getShort();
short commandValue = headBuffer.getShort();
boolean isGzipped = headBuffer.get() == (byte) 1;
String content = null;
try {
//对内容进行解码
content = SystemTools.decode(ByteBuffer.wrap(data.getBodyBytes()), bodyLength, isGzipped);
} catch (DecodeException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Command command = Command.from(commandValue);
if (command == null) {
LogUtils.e("消息命令码【" + commandValue + "】不正确");
return;
}
switch (Command.from(commandValue)) {
case HANDSHAKE_RESP://握手响应 握手成功后,开始心跳
break;
case JOIN_GROUP_RESP://进入群组响应
break;
case JOIN_GROUP_NTY://进入群组通知
break;
case LEAVE_GROUP_NTY://离开群组通知
break;
case P2P_CHAT_NTY://(私聊)通知
break;
case GROUP_CHAT_NTY://群聊通知
break;
case MSG_TIP://消息提示
break;
case PAGE_ONLINE_RESP://分页获取在线观众响应
break;
case UPDATE_TOKEN_RESP://更新token响应
break;
case UNSEND_MSG_NTY://撤回消息通知
break;
case P2P_ALREADY_READ_NTY://已读通知: 服务器告诉张三,张三发给李四的私聊,李四已经阅读
break;
case P2P_RECENT_CHAT_LIST_RESP://查询最近私聊列表响应
break;
default:
break;
}
}
public void onSocketWriteResponse(ConnectionInfo info, String action, ISendable data) {
}
public void onPulseSend(ConnectionInfo info, IPulseSendable data) {
}
}
上面放了大量的源码,结合官网的文档及oksocket,消息的接口及发送不成任何问题;
1、关于心跳,稍微看下文档,就能很快解决。
2、上述代码完整性,不是不愿意贴出所有代码,是留点学习空间。
3、对于消息的处理、网络处理等等需要自己根据实际业务去处理。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。