全平台开源即时通讯 IM 聊天框架 MobileIMSDK 的服务端开发指南,支持鸿蒙 NEXT

写在前面
在着手基于 MobileIMSDK 开发自已的即时通讯应用前,建议以 Demo 工程为脚手架,快速上手 MobileIMSDK!
Demo 工程主要用于演示 SDK 的 API 调用等,它位于 SDK 完整下载包的如下目录:

如上图目录 demo_src/Server/所示,这是一个完整的 IDEA 工程(含完整的可运行 Demo 源码)。
如果你只是想看看 Demo 的话,可以下载编译好的 Demo 包立即体验:它位于 SDK 完整下载包的 demo_binary/server/ 目录下。
第一部分:集成准备
第 1 步:下载 SDK 并找到 lib 包
① 马上下载:
最新版打包下载:Github 点此进入(Gitee 同步托管、Gitcode 同步托管),或者前往 MobileIMSDK 的 Github 自行同步代码。
② 找到 lib 包:
位于 SDK 完整下载包的 sdl_binary/Server/ 目录下:

第 2 步:引用 lib 包和依赖库
提示:MobileIMSDK 的 Server 端 lib 包支持 Java 1.7(含)及以上版本。
2.1 引用 lib 包(IDEA 工程,本地 jar 方式)
① IDEA 中如何引用第 3 方本地 jar 包?
跟所有 Java 工程一样引用 jar 包很简单,如果没试过,请查看:IDEA 引入本地 jar 包的两种方法 或自行百度查找资料。
② 以 MobileIMSDK Server 的 Demo 工程为例,结果如下图:

2.2 引用 lib 包(IDEA 工程,Maven 方式)
① IDEA 中如何在 pom.xml 中引用本地 jar 包和依赖库?
在 pom.xml 加入以下配置:(以下配置参考自 IM 产品 RainbowChat)
② 以RainbowChat的 Server 工程为例,示例如下图:

2.3 引用 lib 包(Eclipse 工程)
① Eclipse 中如何引用第 3 方 jar 包?
跟所有 Java 工程一样引用 jar 包很简单,如果没试过,请查看:Eclipse 中导入外部 jar 包 或 Eclipse 下导入外部 jar 包的 3 种方式。
② 以 MobileIMSDK Server 的 Demo 工程为例,结果如下图:

第二部分:编写代码
第 1 步:准备回调通知的实现类
① 框架基本事件回调实现类:
/**
与客服端的所有数据交互事件在此 ServerEventListener 子类中实现即可。
@author Jack Jiang
@version 1.0
@since 3.1*/public class ServerEventListenerImpl implements ServerEventListener{private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);
/**
用户身份验证回调方法定义(即验证客户端连接的合法性,合法就允许正常能信,否则断开).
<p>
服务端的应用层可在本方法中实现用户登陆验证。
<br>
注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,本回调是不会被调用的!
<p>
根据 MobileIMSDK 的算法实现,本方法中用户验证通过(即方法返回值=0 时)后
,将立即调用回调方法 {@link #onUserLoginSucess(int, String, IoSession)}。
否则会将验证结果(本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginResponse(int userId, int errorCode)
方法进行回调)通知客户端)。
@param userId 传递过来的准一 id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的 id 等,具体意义由业务层决定
@param token 用于身份鉴别和合法性检查的 token,它可能是登陆密码,也可能是通过前置单点登陆接口拿到的 token 等,具体意义由业务层决定
@param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容
@param session 此客户端连接对应的 netty “会话”
@return 0 表示登陆验证通过,否则可以返回用户自已定义的错误码,错误码值应为:>=1025 的整数*/@Overridepublic int onUserLoginVerify(String userId, String token, String extra, Channel session){logger.debug("【DEBUG_回调通知】正在调用回调方法:OnVerifyUserCallBack...(extra="+extra+")");return 0;}
/**
用户登录验证成功后的回调方法定义(在业务上可理解为该用户的上线通知).
<p>
服务端的应用层通常可在本方法中实现用户上线通知等。
<br>
注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,回调也是一定会被调用。
@param userId 传递过来的准一 id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的 id 等,具体意义由业务层决定
@param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容。为了丰富应用层处理的手段,在本回调中也把此字段传进来了
@param session 此客户端连接对应的 netty “会话”*/@Overridepublic void onUserLoginSucess(String userId, String extra, Channel session){logger.debug("【IM_回调通知 onUserLoginSucess】用户:"+userId+" 上线了!");}
/**
用户退出登录回调方法定义(可理解为下线通知回调)。
<p>
服务端的应用层通常可在本方法中实现用户下线通知等。
@param userId 下线的用户 user_id
@param session 此客户端连接对应的 netty “会话”
@param beKickoutCode 被踢原因编码,本参数当为-1 时表示本次 logout 事件不是源自“被踢”,否则被踢原因编码请见 {@link PKickoutInfo}类中的常量定义
@see {@link OnlineProcessor#setBeKickoutCodeForChannel(Channel, int)}*/@Overridepublic void onUserLogout(String userId, Channel session, int beKickoutCode){logger.debug("【DEBUG_回调通知 onUserLogout】用户:"+userId+" 离线了(beKickoutCode="+beKickoutCode+")!");}
/**
收到客户端发送给“服务端”的数据回调通知(即:消息路径为“C2S”的消息)前的处理逻辑。
<p>
<b>本方法的默认实现</b>:<font color="green">当开发者不需要本方法进行额外逻辑处理时,请直接返回 true 即可!</font>
<p>
<b>本方法的典型用途</b>:开发者可在本方法中实现如:用户聊天内容的鉴黄、过滤、篡改等等,把内容审读权限交给开发者,就看怎么用了。
@param p 消息/指令的完整协议包对象
@param session 消息发送者的“会话”引用(也就是客户端的网络连接对象)
@return true 表示经过本方法后将正常进入 {@link #onTransferMessage4C2S(Protocal, Channel)}继续正常逻辑 ,false 表示该条指令将不会继续处理(直接被丢弃)
@see #onTransferMessage4C2S(Protocal, Channel)
@since 6.2*/@Overridepublic boolean onTransferMessage4C2CBefore(Protocal p, Channel session){return true;}
/**
收到客户端发送给“其它客户端”的数据回调通知(即:消息路径为“C2C”的消息)前的处理逻辑。
<p>
<b>本方法的默认实现</b>:<font color="green">当开发者不需要本方法进行额外逻辑处理时,请直接返回 true 即可!</font>
<p>
<b>本方法的典型用途</b>:开发者可在本方法中实现如:用户聊天内容的鉴黄、过滤、篡改等等,把内容审读权限交给开发者,就看怎么用了。
@param p 消息/指令的完整协议包对象
@param session 消息发送者的“会话”引用(也就是客户端的网络连接对象)
@return true 表示经过本方法后将正常进入 {@link #onTransferMessage4C2C(Protocal)}继续正常逻辑 ,false 表示该条指令将不会继续处理(直接被丢弃)
@see #onTransferMessage4C2C(Protocal)
@since 6.2*/@Overridepublic boolean onTransferMessage4C2SBefore(Protocal p, Channel session){return true;}
/**
收到客户端发送给“服务端”的数据回调通知(即:消息路径为“C2S”的消息).
<p>
MobileIMSDK 在收到客户端向 userId="0"(即接收目标是"服务器")的情况下通过
本方法的回调通知上层。
<p>
<b>本方法的典型用途</b>:开发者通常可在本方法中实现如:添加好友请求等需要服务端进行处理的业务。
@param p 消息/指令的完整协议包对象
@param session 此客户端连接对应的 netty “会话”
@return true 表示本方法已成功处理完成,否则表示未处理成功。此返回值目前框架中并没有特殊意义,仅作保留吧
@see Protocal
@since 4.0*/@Overridepublic boolean onTransferMessage4C2S(Protocal p, Channel session){// 接收者 uidString userId = p.getTo();// 发送者 uidString from_user_id = p.getFrom();// 消息或指令内容 String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一 ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]收到了客户端"+from_user_id+"发给服务端的消息:str="+dataContent);return true;}
/**
收到客户端发送给“其它客户端”的数据回调通知(即:消息路径为“C2C”的消息).
<p>
<b>注意:</b>本方法当且仅当在数据被服务端成功实时发送(“实时”即意味着对方在线的情况下)出去后被回调调用.
<p>
<b>本方法的典型用途</b>:开发者可在本方法中可以实现用户聊天信息的收集,以便后期监控分析用户的行为等^_^。
开发者可以对本方法不作任何代码实现,也不会影响整个 MobileIMSDK 的运行,因为本回调并非关键逻辑,只是个普通消息传输结果的回调而已。
<p>
提示:如果开启消息 QoS 保证,因重传机制,本回调中的消息理论上有重复的可能,请以参数 #fingerPrint
作为消息的唯一标识 ID 进行去重处理。
@param p 消息/指令的完整协议包对象
@see Protocal
@since 4.0*/@Overridepublic void onTransferMessage4C2C(Protocal p){// 接收者 uidString userId = p.getTo();// 发送者 uidString from_user_id = p.getFrom();// 消息或指令内容 String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一 ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]收到了客户端"+from_user_id+"发给客户端"+userId+"的消息:str="+dataContent);}
/**
服务端在进行消息发送时,当对方在线但实时发送失败、以及其它各种问题导致消息并没能正常发出时
,将无条件走本回调通知。
<p>
<b>注意:</b>本方法当且仅当在数据被服务端<u>在线发送</u>失败后被回调调用.
<p>
<b>举个例子:以下是一段典型的服务端消息/指令发送代码:</b>
<pre style="border: 1px solid #eaeaea;background-color: #fff6ea;border-radius: 6px;">
// 消息接收者的 id(这个 id 由你自已定义,对于 MobileIMSDK 来说只要保证唯一性即可)
String destinationUserId = "400069";
// 这是要发送的消息("你好"是消息内容、“0”是消息发送者)
final Protocal p = ProtocalFactory.createCommonData("你好", "0", destinationUserId, true, null, -1);
// 对方在线的情况下,才需要实时发送,否则走离线处理逻辑
if(OnlineProcessor.isOnline(destinationUserId)) {
}
else{
}
<br>
<font color="#0000ff">如上代码所示:“【1】【3】”代码处,开发者可以自行明确地进行离线逻辑处理,“【2】”处如
果实时发送时出现任何问题,将会走本回调方法进行通知,框架正是通过此回调进一步确保消息可靠性保证的。</font>
</pre>
<p>
<p>
<b>本方法的典型用途</b>:<br>
开发者可在本方法中实现离线消息的持久化存储(反正进到本回调通知的消息,就是应该被离线存储起来的)。
<p>
<b>此方法存的意义何在?</b><br>
发生此种情况的场景可能是:对方确实不在线(那么此方法里就可以作为离线消息处理了)、或者在发送时判断对方是在线的
但服务端在发送时却没有成功(这种情况就可能是通信错误或对方非正常通出但尚未到达会话超时时限)。<br><u>应用层在
此方法里实现离线消息的处理即可!</u>
@param p 消息/指令的完整协议包对象
@return true 表示应用层已经处理了离线消息(如果该消息有 QoS 机制,则服务端将代为发送一条伪应答包
(伪应答仅意味着不是接收方的实时应答,而只是存储到离线 DB 中,但在发送方看来也算是被对方收到,只是延
迟收到而已(离线消息嘛))),否则表示应用层没有处理(如果此消息有 QoS 机制,则发送方在 QoS 重传机制超时
后报出消息发送失败的提示)
@see Protocal
@see #onTransferMessage4C2C(Protocal)
@since 4.0*/@Overridepublic boolean onTransferMessage_RealTimeSendFaild(Protocal p){// 接收者 uidString userId = p.getTo();// 发送者 uidString from_user_id = p.getFrom();// 消息或指令内容 String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一 ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]客户端"+from_user_id+"发给客户端"+userId+"的消息:str="+dataContent+",因实时发送没有成功,需要上层应用作离线处理哦,否则此消息将被丢弃.");return false;}
/**
<b>注意:</b><font color="red">本回调仅用于与 Web 的互通模式下,默认情况下本方法可什么也不做,无任何影响。如你对此回调有疑问可跟 Jack Jiang 进行技术讨论!</font>
{@inheritDoc}
@since 6.2*/@Overridepublic void onTransferMessage4C2C_AfterBridge(Protocal p){
// 默认本方法可}}
② 服务端主动发起消息的 QoS 回调通知实现类:
第 2 步:服务端最终配置和实现
/**
IM 服务的启动主类。
<p>
<b>友情提示:</b>其实 MobileIMSDK 的服务端并非只能以 main 的主类方式独立启动,你完全可以把它放到诸如 java 的 Web 工程里作为子模块运行,不会有任何问题!
@author Jack Jiang
@version 1.0*/public class ServerLauncherImpl extends ServerLauncher{private static Logger logger = LoggerFactory.getLogger(ServerLauncherImpl.class);
/**
静态类方法:进行一些全局配置设置。*/static{// 设置 MobileIMSDK 服务端的 UDP 网络监听端口 GatewayUDP.PORT = 7901;// 设置 MobileIMSDK 服务端的 TCP 网络监听端口 GatewayTCP.PORT = 8901;// 设置 MobileIMSDK 服务端的 WebSocket 网络监听端口 GatewayWebsocket.PORT = 3000;
// 设置 MobileIMSDK 服务端仅支持 UDP 协议// ServerLauncher.supportedGateways = Gateway.SOCKET_TYPE_UDP;// 设置 MobileIMSDK 服务端仅支持 TCP 协议// ServerLauncher.supportedGateways = Gateway.SOCKET_TYPE_TCP;// 设置 MobileIMSDK 服务端仅支持 WebSocket 协议// ServerLauncher.supportedGateways = Gateway.SOCKET_TYPE_WEBSOCKET;// 设置 MobileIMSDK 服务端同时支持 UDP、TCP、WebSocket 三种协议 ServerLauncher.supportedGateways = Gateway.SOCKET_TYPE_UDP | Gateway.SOCKET_TYPE_TCP | Gateway.SOCKET_TYPE_WEBSOCKET;
// 开/关 Demog 日志的输出 QoS4SendDaemonS2C.getInstance().setDebugable(true);QoS4ReciveDaemonC2S.getInstance().setDebugable(true);
// 与客户端协商一致的心跳频率模式设置// ServerToolKits.setSenseModeUDP(SenseModeUDP.MODE_15S);ServerToolKits.setSenseModeTCP(SenseModeTCP.MODE_5S);ServerToolKits.setSenseModeWebsocket(SenseModeWebsocket.MODE_5S);// ServerToolKits.setSenseModeWebsocket(SenseModeWebsocket.MODE_30S);
// 关闭与 Web 端的消息互通桥接器(其实 SDK 中默认就是 false)ServerLauncher.bridgeEnabled = false;// TODO 跨服桥接器 MQ 的 URI(本参数只在 ServerLauncher.bridgeEnabled 为 true 时有意义)// BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.0.190";
// 设置最大 TCP 帧内容长度(不设置则默认最大是 6 * 1024 字节)// GatewayTCP.TCP_FRAME_MAX_BODY_LENGTH = 60 * 1024;
SslContext sslContext = createSslContext();// 开启 TCP 协议的 SSL/TLS 加密传输(请确保客户端也已开发 SSL)// GatewayTCP.sslContext = sslContext;// 开启 WebSocket 协议的 SSL/TLS 加密传输(请确保 SSL 证书是正规 CA 签发,否则浏览器是不允许的)// GatewayWebsocket.sslContext = sslContext;}
/**
实例构造方法。
@throws IOException*/public ServerLauncherImpl() throws IOException{super();}
/**
初始化消息处理事件监听者.*/@Overrideprotected void initListeners(){// ** 设置各种回调事件处理实现类 this.setServerEventListener(new ServerEventListenerImpl());this.setServerMessageQoSEventListener(new MessageQoSEventS2CListnerImpl());}
/**
创建 SslContext 对象,用于开启 SSL/TLS 加密传输。
@return 如果成功创建则返回 SslContext 对象,否则返回 null/private static SslContext createSslContext(){try {/* 示例 1:使用证书(证书位于绝对路径)*/// // 证书文件// File certChainFile = new File("c:/certs/netty-cert2.crt");// // 证书文件// File keyFile = new File("c:/certs/netty-key2.pk8");// // 私钥密码(注意:Netty 只支持.pk8 格式,如何生成,见 JackJiang 文章:)// String keyPassword = "123456";// // 生成 SslContext 对象(为了方便理解,此处使用的是单向认证)// SslContext sslCtx = SslContextBuilder.forServer(certChainFile, keyFile, keyPassword).clientAuth(ClientAuth.NONE).build();
// SelfSignedCertificate ssc = new SelfSignedCertificate();// SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
return sslCtx; } catch (Exception e) { logger.warn("createSslContext()时出错了,原因:"+e.getMessage(), e); } return null; } /** * Demo 程序主入口函数。 * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 实例化后记得 startup 哦,单独 startup()的目的是让调用者可以延迟决定何时真正启动 IM 服务 final ServerLauncherImpl sli = new ServerLauncherImpl(); // 启动 MobileIMSDK 服务端的 Demo sli.startup(); // 加一个钩子,确保在 JVM 退出时释放 netty 的资源 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { sli.shutdown(); } }); }
}
第三部分:常见开发问题附录
附录 1:可以让客户端更省电吗?
为了配合 Android、iOS 客户端,Server 端也需要进行设置。
请调用以下 API 进行设置即可(框架默认工作在 SenseMode.MODE_15S 模式下):
// MobileIMSDK 核心 IM 框架的服务端敏感度模式设置
ServerLauncherImpl.setSenseMode(SenseMode.MODE_15S);
MobileIMSDK 预定义了多种模式,详细 API 说明:点此进入。
特别说明:为了保证算法的一致性,以上设置需所有平台客户端和服务端都保持一致,否则将发生不可预测问题。
附录 2:服务端如何向客户端推送/发送数据(或消息)?
服务端使用 LocalSendHelper 类中的 sendData 系列方法即可,详见下图:

API 文档在线地址:http://docs.52im.net/extend/docs/api/mobileimsdk/server_tcp/
附录 3:核心库工程与 Demo 演示工程的关系说明
如下图所示:从 Github 或 淘宝 得到的核心库工程和 Demo 演示工程

(▲ 左边为 MobileIMSDK 的各平台核心库工程,右边为各平台的 Demo 演示工程)
* 什么是核心库工程?
核心库工程就是 MobileIMSDK 的所有框架源码,它只是个 lib 库,它的作用就像 Spring boot、Struts、log4j 这些第 3 库 lib 库一样:是打成 jar 包放到您的工程里使用的,您调用它就能实现它提供的功能,它自已本身并不能自已运行(你不可能让 log4j 或 Spring boot 能双击就运行吧?)
* 什么是 Demo 演示工程?
正如“什么是核心库工程?”一节所说,MobileIMSDK 的核心库是不能直接运行的,它需要打成 jar 包被您的工程引用并调用后,才能发挥它的作用,所以 MobileIMSDK 的 Demo 演示工程的目的就是为了告诉你:如何引用 MobileIMSDK 的核心库 jar 包、如何调用 MobileIMSDK 的 API,读 Demo 代码就知道如何使用它了(所以 Demo 代码唯一的意义就是为您演示库的调用,别无他用)!
* “我”的工程中使用使用核心库工程?
为了方便日后的升级,建议使用 MobileIMSDK 编译好的核心库 jar 包,当然您也可以直接把 MobileIMSDK 核心库源库放到您的工程中(而不是使用编译好的 jar 包)。
* 您可以在 MobileIMSDK 的 Github 如下目录中找到打包编译好的 jar 包:

附录 4:如何开启 SSL/TLS 传输加密
1 您需要准备一个 SSL/TLS 证书(支持自签名证书)
可以使用正规 CA 机构签发的证书,也可以使用自签名证书,如何生成自签名证书可自行百度,这方面资料很丰富。
证书文件就像这样:

注:如果你不想买证书,也不知道如何生成自签名证书,可以跟着这篇文章自已做《手把手教你为基于 Netty 的 IM 生成自签名 SSL/TLS 证书》。
2 代码中启用 SSL/TLS 加密
* 将准备好的证书放置到服务端工程的此目录下(以 MobileIMSDK 的服务端 Demo 工程为例):


* 启用 SSL/TLS 配置(取消此行代码注释即可):(内容参考自:http://www.52im.net/thread-63-1-1.html)

* 启用 SSL/TLS 后的运行效果(服务端控制台 log 中出现此字样即表示 SSL/TLS 启用成功!):

(▲ 服务端启动时控制台下关于已开启 TLS/SSL 加密的 log 输出(run.bat 运行时))

(▲ 服务端启动时控制台下关于已开启 TLS/SSL 加密的 log 输出(IDEA 中运行时))

(▲ 客户端发起 WebSocket 连接时,服务端控制台下带有 TLS/SSL 信息的握手 log)
评论