一、前言

MobileIMSDK 是什么?

MobileIMSDK  是一套专门为移动端开发的开源IM即时通讯框架,超轻量级、高度提炼,一套API优雅支持UDP 、TCP 、WebSocket 三种协议,支持iOS、Android、H5、标准Java平台,服务端基于Netty编写。

工程地址是:

本文将实现:

  • 1)基于springboot 集成 MobileIMSDK;
  • 2)开发IM服务端;
  • 3)开发客户端;
  • 4)实现Java客户端与客户端之间的通信。

* 补充说明:本文所示Demo源码,请从文末“本文小结”的最后链接中下载!

二、SpringBoot 集成 MobileIMSDK 准备

2.1 MobileIMSDK下载

MobileIMSDK下载地址:

需要用到的lib包:

  • 1)服务端所需jar包: sdk_binary/Server/
  • 2)客服端所需jar包: sdk_binary/Client_TCP/java/

如下图所示:

2.2 pom.xml中引入相关依赖

由于这里是maven项目,其中一部分jar包可通过maven仓库直接引入,而其余的则通过外部jar包引入方式使用即可~

如下4个需作为外部jar包在pom.xml中引入 :

<!-- [url=https://mvnrepository.com/artifact/com.google.code.gson/gson]https://mvnrepository.com/artifact/com.google.code.gson/gson[/url] -->

<dependency>

<groupId>com.google.code.gson</groupId>

<artifactId>gson</artifactId>

<version>2.8.5</version>

</dependency>

<!-- MobileIMSDK所需jar包依赖[注:这里是在本地lib中引入,maven中央仓库中暂无此jar包],要与<includeSystemScope>true</includeSystemScope>配合使用-->

<dependency>

<groupId>com.zhengqing</groupId>

<artifactId>MobileIMSDK4j</artifactId>

<scope>system</scope>

<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDK4j.jar</systemPath>

</dependency>

<dependency>

<groupId>com.zhengqing</groupId>

<artifactId>MobileIMSDKServerX_meta</artifactId>

<scope>system</scope>

<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_meta.jar</systemPath>

</dependency>

<dependency>

<groupId>com.zhengqing</groupId>

<artifactId>swing-worker-1.2(1.6-)</artifactId>

<scope>system</scope>

<systemPath>${project.basedir}/src/main/resources/lib/swing-worker-1.2(1.6-).jar</systemPath>

</dependency>

<dependency>

<groupId>com.zhengqing</groupId>

<artifactId>MobileIMSDKServerX_netty</artifactId>

<scope>system</scope>

<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_netty.jar</systemPath>

</dependency>

<plugins>

<!-- maven打包插件 -> 将整个工程打成一个 fatjar -->

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<!-- 作用:项目打成jar,同时把本地jar包也引入进去 -->

<configuration>

<includeSystemScope>true</includeSystemScope>

</configuration>

</plugin>

</plugins>

三、开发服务端

3.1 与客服端的所有数据交互事件(实现ServerEventListener类)

public class ServerEventListenerImpl implements ServerEventListener {

private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);

/**

* 用户身份验证回调方法定义.

* <p>

* 服务端的应用层可在本方法中实现用户登陆验证。

* <br>

* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,本回调是不会被调用的!

* <p>

* 根据MobileIMSDK的算法实现,本方法中用户验证通过(即方法返回值=0时)后

* ,将立即调用回调方法 {@link #onUserLoginAction_CallBack(int, String, IoSession)}。

* 否则会将验证结果(本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode)

* 方法进行回调)通知客户端)。

*

* @param userId  传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定

* @param token   用于身份鉴别和合法性检查的token,它可能是登陆密码,也可能是通过前置单点登陆接口拿到的token等,具体意义由业务层决定

* @param extra   额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容

* @param session 此客户端连接对应的 netty “会话”

* @return 0 表示登陆验证通过,否则可以返回用户自已定义的错误码,错误码值应为:>=1025的整数

*/

@Override

public int onVerifyUserCallBack(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 “会话”

*/

@Override

public void onUserLoginAction_CallBack(String userId, String extra, Channel session) {

logger.debug("【IM_回调通知OnUserLoginAction_CallBack】用户:"+ userId + " 上线了!");

}

/**

* 用户退出登录回调方法定义(可理解为下线通知回调)。

* <p>

* 服务端的应用层通常可在本方法中实现用户下线通知等。

*

* @param userId  下线的用户user_id

* @param obj

* @param session 此客户端连接对应的 netty “会话”

*/

@Override

public void onUserLogoutAction_CallBack(String userId, Object obj, Channel session) {

logger.debug("【DEBUG_回调通知OnUserLogoutAction_CallBack】用户:"+ userId + " 离线了!");

}

/**

* 通用数据回调方法定义(客户端发给服务端的(即接收user_id="0")).

* <p>

* MobileIMSDK在收到客户端向user_id=0(即接收目标是服务器)的情况下通过

* 本方法的回调通知上层。上层通常可在本方法中实现如:添加好友请求等业务实现。

*

* <p style="background:#fbf5ee;border-radius:4px;">

* <b><font color="#ff0000">【版本兼容性说明】</font></b>本方法用于替代v3.x中的以下方法:<br>

* <code>public boolean onTransBuffer_CallBack(String userId, String from_user_id

* , String dataContent, String fingerPrint, int typeu, Channel session);

* </code>

*

* @param userId       接收方的user_id(本方法接收的是发给服务端的消息,所以此参数的值肯定==0)

* @param from_user_id 发送方的user_id

* @param dataContent  数据内容(文本形式)

* @param session      此客户端连接对应的 netty “会话”

* @return true表示本方法已成功处理完成,否则表示未处理成功。此返回值目前框架中并没有特殊意义,仅作保留吧

* @since 4.0

*/

@Override

public boolean onTransBuffer_C2S_CallBack(Protocal p, Channel session) {

// 接收者uid

String userId = p.getTo();

// 发送者uid

String from_user_id = p.getFrom();

// 消息或指令内容

String dataContent = p.getDataContent();

// 消息或指令指纹码(即唯一ID)

String fingerPrint = p.getFp();

// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)

inttypeu = p.getTypeu();

logger.debug("【DEBUG_回调通知】[typeu="+ typeu + "]收到了客户端"+ from_user_id + "发给服务端的消息:str="+ dataContent);

returntrue;

}

/**

* 通道数据回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)).

* <p>

* <b>注意:</b>本方法当且仅当在数据被服务端成功在线发送出去后被回调调用.

* <p>

* 上层通常可在本方法中实现用户聊天信息的收集,以便后期监控分析用户的行为等^_^。

* <p>

* 提示:如果开启消息QoS保证,因重传机制,本回调中的消息理论上有重复的可能,请以参数 #fingerPrint

* 作为消息的唯一标识ID进行去重处理。

*

* <p style="background:#fbf5ee;border-radius:4px;">

* <b><font color="#ff0000">【版本兼容性说明】</font></b>本方法用于替代v3.x中的以下方法:<br>

* <code>public void onTransBuffer_C2C_CallBack(String userId, String from_user_id

* , String dataContent, String fingerPrint, int typeu);

*

* @param userId       接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0)

* @param from_user_id 发送方的user_id

* @param dataContent

* @since 4.0

*/

@Override

public void onTransBuffer_C2C_CallBack(Protocal p) {

// 接收者uid

String userId = p.getTo();

// 发送者uid

String from_user_id = p.getFrom();

// 消息或指令内容

String dataContent = p.getDataContent();

// 消息或指令指纹码(即唯一ID)

String fingerPrint = p.getFp();

// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)

inttypeu = p.getTypeu();

logger.debug("【DEBUG_回调通知】[typeu="+ typeu + "]收到了客户端"+ from_user_id + "发给客户端"+ userId + "的消息:str="+ dataContent);

}

/**

* 通用数据实时发送失败后的回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)).

* <p>

* 注意:本方法当且仅当在数据被服务端<u>在线发送</u>失败后被回调调用.

* <p>

* <b>此方法存的意义何在?</b><br>

* 发生此种情况的场景可能是:对方确实不在线(那么此方法里就可以作为离线消息处理了)、

* 或者在发送时判断对方是在线的但服务端在发送时却没有成功(这种情况就可能是通信错误

* 或对方非正常通出但尚未到达会话超时时限)。<br><u>应用层在此方法里实现离线消息的处理即可!</u>

*

* <p style="background:#fbf5ee;border-radius:4px;">

* <b><font color="#ff0000">【版本兼容性说明】</font></b>本方法用于替代v3.x中的以下方法:<br>

* <code>public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(String userId

* , String from_user_id, String dataContent, String fingerPrint, int typeu);

* </code>

*

* @param userId       接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0),此id在本方法中不一定保证有意义

* @param from_user_id 发送方的user_id

* @param dataContent  消息内容

* @param fingerPrint  该消息对应的指纹(如果该消息有QoS保证机制的话),用于在QoS重要机制下服务端离线存储时防止重复存储哦

* @return true表示应用层已经处理了离线消息(如果该消息有QoS机制,则服务端将代为发送一条伪应答包

* (伪应答仅意味着不是接收方的实时应答,而只是存储到离线DB中,但在发送方看来也算是被对方收到,只是延

* 迟收到而已(离线消息嘛))),否则表示应用层没有处理(如果此消息有QoS机制,则发送方在QoS重传机制超时

* 后报出消息发送失败的提示)

* @see #onTransBuffer_C2C_CallBack(Protocal)

* @since 4.0

*/

@Override

public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(Protocal p) {

// 接收者uid

String userId = p.getTo();

// 发送者uid

String from_user_id = p.getFrom();

// 消息或指令内容

String dataContent = p.getDataContent();

// 消息或指令指纹码(即唯一ID)

String fingerPrint = p.getFp();

// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)

inttypeu = p.getTypeu();

logger.debug("【DEBUG_回调通知】[typeu="+ typeu + "]客户端"+ from_user_id + "发给客户端"+ userId + "的消息:str="+ dataContent

+ ",因实时发送没有成功,需要上层应用作离线处理哦,否则此消息将被丢弃.");

returnfalse;

}

}

3.2 服务端主动发起消息的QoS回调通知(实现MessageQoSEventListenerS2C类)

public class MessageQoSEventS2CListnerImpl implements MessageQoSEventListenerS2C {

private static Logger logger = LoggerFactory.getLogger(MessageQoSEventS2CListnerImpl.class);

@Override

public void messagesLost(ArrayList<Protocal> lostMessages) {

logger.debug("【DEBUG_QoS_S2C事件】收到系统的未实时送达事件通知,当前共有"

+ lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!");

}

@Override

public void messagesBeReceived(String theFingerPrint) {

if(theFingerPrint != null) {

logger.debug("【DEBUG_QoS_S2C事件】收到对方已收到消息事件的通知,fp="+ theFingerPrint);

}

}

}

3.3 服务端配置

public class ServerLauncherImpl extends ServerLauncher {

// 静态类方法:进行一些全局配置设置

static{

// 设置MobileIMSDK服务端的网络监听端口

ServerLauncherImpl.PORT = 7901;

// 开/关Demog日志的输出

QoS4SendDaemonS2C.getInstance().setDebugable(true);

QoS4ReciveDaemonC2S.getInstance().setDebugable(true);

ServerLauncher.debug = true;

// TODO 与客户端协商一致的心跳敏感模式设置

//      ServerToolKits.setSenseMode(SenseMode.MODE_10S);

// 关闭与Web端的消息互通桥接器(其实SDK中默认就是false)

ServerLauncher.bridgeEnabled = false;

// TODO 跨服桥接器MQ的URI(本参数只在ServerLauncher.bridgeEnabled为true时有意义)

//     BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.31.190";

}

// 实例构造方法

public ServerLauncherImpl() throws IOException {

super();

}

/**

* 初始化消息处理事件监听者.

*/

@Override

protected void initListeners() {

// ** 设置各种回调事件处理实现类

this.setServerEventListener(newServerEventListenerImpl());

this.setServerMessageQoSEventListener(newMessageQoSEventS2CListnerImpl());

}

}

3.4 服务端启动类

温馨小提示:这里由于小编将服务端和客户端集成在同一个项目中,因此如下配置:

  • SpringBoot的CommandLineRunner接口主要用于实现在服务初始化后,去执行一段代码块逻辑(run方法),这段初始化代码在整个应用生命周期内只会执行一次!
  • @Order(value = 1) :按照一定的顺序去执行,value值越小越先执行

@Slf4j

@Component

@Order(value = 1)

public class ChatServerRunner implements CommandLineRunner {

@Override

public void run(String... strings) throws Exception {

log.info("================= ↓↓↓↓↓↓ 启动MobileIMSDK服务端 ↓↓↓↓↓↓ =================");

// 实例化后记得startup哦,单独startup()的目的是让调用者可以延迟决定何时真正启动IM服务

final ServerLauncherImpl sli = new ServerLauncherImpl();

// 启动MobileIMSDK服务端的Demo

sli.startup();

// 加一个钩子,确保在JVM退出时释放netty的资源

Runtime.getRuntime().addShutdownHook(newThread(sli::shutdown));

}

}

如果服务端与客户端不在同一个项目 ,服务端可直接通过如下方式启动即可~

四、开发客户端

4.1 客户端与IM服务端连接事件

@Slf4j

public class ChatBaseEventImpl implements ChatBaseEvent {

@Override

public void onLoginMessage(int dwErrorCode) {

if(dwErrorCode == 0) {

log.debug("IM服务器登录/连接成功!");

} else{

log.error("IM服务器登录/连接失败,错误代码:"+ dwErrorCode);

}

}

@Override

public void onLinkCloseMessage(int dwErrorCode) {

log.error("与IM服务器的网络连接出错关闭了,error:"+ dwErrorCode);

}

}

4.2 接收消息事件

@Slf4j

public class ChatTransDataEventImpl implements ChatTransDataEvent {

@Override

public void onTransBuffer(String fingerPrintOfProtocal, String userid, String dataContent, inttypeu) {

log.debug("[typeu="+ typeu + "]收到来自用户"+ userid + "的消息:"+ dataContent);

}

@Override

public void onErrorResponse(int errorCode, String errorMsg) {

log.debug("收到服务端错误消息,errorCode="+ errorCode + ", errorMsg="+ errorMsg);

}

}

4.3 消息是否送达事件

@Slf4j

public class MessageQoSEventImpl implements MessageQoSEvent {

@Override// 对方未成功接收消息的回调事件 lostMessages:存放消息内容

public void messagesLost(ArrayList<Protocal> lostMessages) {

log.debug("收到系统的未实时送达事件通知,当前共有"+ lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!");

}

@Override// 对方成功接收到消息的回调事件

public void messagesBeReceived(String theFingerPrint) {

if(theFingerPrint != null) {

log.debug("收到对方已收到消息事件的通知,fp="+ theFingerPrint);

}

}

}

4.4 MobileIMSDK初始化配置

public class IMClientManager {

private static IMClientManager instance = null;

/**

* MobileIMSDK是否已被初始化. true表示已初化完成,否则未初始化.

*/

privatebooleaninit = false;

public static IMClientManager getInstance() {

if(instance == null) {

instance = new IMClientManager();

}

return instance;

}

private IMClientManager() {

initMobileIMSDK();

}

public void initMobileIMSDK() {

if(!init) {

// 设置服务器ip和服务器端口

ConfigEntity.serverIP = "127.0.0.1";

ConfigEntity.serverPort = 8901;

// MobileIMSDK核心IM框架的敏感度模式设置

//           ConfigEntity.setSenseMode(SenseMode.MODE_10S);

// 开启/关闭DEBUG信息输出

ClientCoreSDK.DEBUG = false;

// 设置事件回调

ClientCoreSDK.getInstance().setChatBaseEvent(newChatBaseEventImpl());

ClientCoreSDK.getInstance().setChatTransDataEvent(newChatTransDataEventImpl());

ClientCoreSDK.getInstance().setMessageQoSEvent(newMessageQoSEventImpl());

init = true;

}

}

}

4.5 连接IM服务端,发送消息

服务类:

public interface IChatService {

/**

* 登录连接IM服务器请求

*

* @param username: 用户名

* @param password: 密码

* @return: void

*/

void loginConnect(String username, String password);

/**

* 发送消息

*

* @param friendId: 接收消息者id

* @param msg:      消息内容

* @return: void

*/

void sendMsg(String friendId, String msg);

}

服务实现类:

@Slf4j

@Service

@Transactional(rollbackFor = Exception.class)

public class ChatServiceImpl implements IChatService {

@Override

public void loginConnect(String username, String password) {

// 确保MobileIMSDK被初始化哦(整个APP生生命周期中只需调用一次哦)

// 提示:在不退出APP的情况下退出登陆后再重新登陆时,请确保调用本方法一次,不然会报code=203错误哦!

IMClientManager.getInstance().initMobileIMSDK();

// * 异步提交登陆名和密码

new LocalUDPDataSender.SendLoginDataAsync(username, password) {

/**

* 登陆信息发送完成后将调用本方法(注意:此处仅是登陆信息发送完成,真正的登陆结果要在异步回调中处理哦)。

* @param code 数据发送返回码,0 表示数据成功发出,否则是错误码

*/

protected void fireAfterSendLogin(int code) {

if(code == 0) {

log.debug("数据发送成功!");

} else{

log.error("数据发送失败。错误码是:"+ code);

}

}

}.execute();

}

@Override

public void sendMsg(String friendId, String msg) {

// 发送消息(异步提升体验,你也可直接调用LocalUDPDataSender.send(..)方法发送)

new LocalUDPDataSender.SendCommonDataAsync(msg, friendId) {

@Override

protected void onPostExecute(Integer code) {

if(code == 0) {

log.debug("数据已成功发出!");

} else{

log.error("数据发送失败。错误码是:"+ code + "!");

}

}

}.execute();

}

}

五、编写Controller进行测试

@RestController

@RequestMapping("/api")

@Api(tags = "聊天测试-接口")

public class ChatController {

@Autowired

private IChatService chatService;

@PostMapping(value = "/loginConnect", produces = Constants.CONTENT_TYPE)

@ApiOperation(value = "登陆请求", httpMethod = "POST", response = ApiResult.class)

public ApiResult loginConnect(@RequestParamString username, @RequestParamString password) {

chatService.loginConnect(username, password);

return ApiResult.ok();

}

@PostMapping(value = "/sendMsg", produces = Constants.CONTENT_TYPE)

@ApiOperation(value = "发送消息", httpMethod = "POST", response = ApiResult.class)

public ApiResult sendMsg(@RequestParam String friendId, @RequestParam String msg) {

chatService.sendMsg(friendId, msg);

return ApiResult.ok();

}

}

启动项目,访问:http://127.0.0.1:8080/swagger-ui.html

1) loginConnect接口:

任意输入一个账号密码登录连接IM服务端:

控制台日志如下:

2)sendMsg接口:

给指定用户发送消息:这里由于只有一个客户端,上一步登录了一个admin账号,因此小编给admin账号(也就是自己) 发送消息

控制台日志如下:

六、本文小结

关于集成可参考MobileIMSDK给出的文档一步一步实现。

该开源工程对应的官方文档比较齐全,需要哪个端,就去看对应端的手册就好了。

1)Demo安装和使用

2)开发者指南

3)API文档

另外:作者给出了通过Java GUI编程实现的一个小demo,我们可以先将其运行起来,先体验一下功能,代码量也不是太多,我们可以通过debug方式查看执行流程。

清楚执行流程之后我们就可以将demo中的代码移植到我们自己的项目中加以修改运用于自己的业务中,切勿拿起就跑,否则一旦运气不好,将浪费更多的时间去集成,这样很不好!

最后:案例demo中相关代码注释都有,这里就简单说下整个流程吧:

  • 1)首先启动IM服务端
  • 2)用户在客户端登录一个用户与服务端建立连接保持通信( 客户端ChatServiceImpl中loginConnect方法为登录连接服务端事件;服务端ServerEventListenerImpl中onUserLoginVerify方法为服务端接收的上线通知事件);
  • 3)客户端通过 ChatServiceImpl中sendMsg方法发送一条消息,如果对方在线能接收消息则走服务端ServerEventListenerImpl中onTransferMessage4C2C方法,否则走onTransferMessage_RealTimeSendFaild方法;如果对方成功接收到消息,客户端将走MessageQoSEventImpl中messagesBeReceived事件,否则走messagesLost事件;
  • 4)客户端通过ChatMessageEvent中onRecieveMessage回调事件接收消息。

附:本文案例demo源码下载:

附录:更多IM聊天新手实践代码

  1. 跟着源码学IM(一):手把手教你用Netty实现心跳机制、断线重连机制
  2. 跟着源码学IM(二):自已开发IM很难?手把手教你撸一个Andriod版IM
  3. 跟着源码学IM(三):基于Netty,从零开发一个IM服务端
  4. 跟着源码学IM(四):拿起键盘就是干,教你徒手开发一套分布式IM系统
  5. 跟着源码学IM(五):正确理解IM长连接、心跳及重连机制,并动手实现
  6. 跟着源码学IM(六):手把手教你用Go快速搭建高性能、可扩展的IM系统
  7. 跟着源码学IM(七):手把手教你用WebSocket打造Web端IM聊天
  8. 跟着源码学IM(八):万字长文,手把手教你用Netty打造IM聊天
  9. 跟着源码学IM(九):基于Netty实现一套分布式IM系统
  10. 跟着源码学IM(十):基于Netty,搭建高性能IM集群(含技术思路+源码)

SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能的更多相关文章

  1. SpringBoot学习笔记(五):SpringBoot集成lombok工具、SpringBoot集成Shiro安全框架

    SpringBoot集成lombok工具 什么是lombok? 自动生成setget方法,构造函数,打印日志 官网:http://projectlombok.org/features/index. 平 ...

  2. layim即时通讯实例各功能整合

    一.系统演示1.1 聊天窗体主界面演示 1.2 模拟两人在线聊天(点击图片查看演示视频) 1.3 在线演示> 在线演示,点击进入系统到这里,若是您想要的,接下来听我娓娓道来二.开发工具开发软件: ...

  3. SpringBoot集成Shiro安全框架

    跟着我的步骤:先运行起来再说 Spring集成Shiro的GitHub:https://github.com/yueshutong/shiro-imooc 一:导包 <!-- Shiro安全框架 ...

  4. 基于Facebook开源框架SocketRocket的即时通讯

    SocketRocket 介绍: SocketRock 是 Facebook 开源的框架,基于 WebSocket 客户端类库,适用于 iOS.Mac OS.tv OS.GitHub 传送门:http ...

  5. java SSM 框架 代码生成器 websocket即时通讯 shiro redis

    1.   权限管理:点开二级菜单进入三级菜单显示 角色(基础权限)和按钮权限      角色(基础权限): 分角色组和角色,独立分配菜单权限和增删改查权限.      按钮权限: 给角色分配按钮权限. ...

  6. ENTBOOST 2014.180L 发布,开源企业IM免费企业即时通讯

    ENTBOOST,VERSION 2014.180 Linux版本发布,主要增加企业IM应用集成功能,完善安卓SDK功能及部分BUG修正: 7/1(明天)发布Windows版本,敬请关注! ENTBO ...

  7. 开源企业IM-免费企业即时通讯-ENTBOOST V2014.180 Linux版本号正式公布

    ENTBOOST,VERSION 2014.180 Linux版本号公布,主要添加企业IM应用集成功能,完好安卓SDK功能及部分BUG修正: 7/1(明天)公布Windows版本号,敬请关注! ENT ...

  8. 开源企业IM-免费企业即时通讯-ENTBOOST V2014.180 Windows版本号正式公布

    ENTBOOST,VERSION 2014.180 Linux版本号公布,主要添加企业IM应用集成功能,完好安卓SDK功能及部分BUG修正. 下一版本号公布时间.7月15日.敬请关注. ENTBOOS ...

  9. 开源企业IM-免费企业即时通讯-ENTBOOST V0.9版本号公布

    ENTBOOST V0.9版本号公布,更新内容:1.完好多人群组聊天,提高群组聊天性能及稳定性:2.苹果IOS SDK.添加联系人管理功能,优化API和内部流程.修复部分BUG.3.添加企业应用功能集 ...

  10. 开源企业IM-免费企业即时通讯-ENTBOOST V2014.183 Windows版本号正式宣布

    ENTBOOST,VERSION 2014.183 Windows(点击下载)版本号公布.主要添加PC端P2P(点对点)文件传输功能:公布安卓Android手机clientAPP 1.0版本号.公布苹 ...

随机推荐

  1. JavaScript 语句后可以省略分号么?

    摘自知乎:https://www.zhihu.com/question/20298345 田乐:加与不加是风格问题,风格争议不需要有个定论.关键的问题在于如何"争论",处理好冲突, ...

  2. 详解 Hough 变换(基本原理与直线检测)

    Hough 变换原理与应用 前言: 详细介绍了 Hough 变换的基本思想.基本原理和应用等.其中大多都是自己的理解,难免有偏差,仅供参考. 文章目录 Hough 变换原理与应用 1. 基本概述 1. ...

  3. TOYOTA SYSTEMS Programming Contest 2024(AtCoder Beginner Contest 377) 补题记录(A-E)

    AtCoder Beginner Contest 377 A - Rearranging ABC 字符串有ABC三个字母即可. #include<bits/stdc++.h> using ...

  4. Python计算1到100的加和

    print(sum(range(1,101))) print(sum([x for x in range(1,101)])) sum_value = 0 for i in range(1,101): ...

  5. docker login 私有仓库时报错

    连接私有harbor报错如下: docker login 192.168.1.88 -uadmin -pHarbor12345 WARNING! Using --password via the CL ...

  6. isObject:判断数据是不是引用类型的数据 (例如: arrays, functions, objects, regexes, new Number(0),以及 new String(''))

    function isObject(value) { let type = typeof value; return value != null && (type == 'object ...

  7. P6419 COCI2014-2015#1 Kamp

    P6419 COCI2014-2015#1 Kamp 换根 \(dp\) 的 trick. 题面 钦定 \(k\) 个关键点,求每个点出发,访问完所有关键点的距离最小值. 思路 设 \(g_u\) 为 ...

  8. 2023NOIP A层联测20 T3 点餐

    2023NOIP A层联测20 点餐 题目很好,可惜考试没想到. 思路 可以按照 \(b\) 从小到大排序,固定选择个数 \(k\),枚举选择的盘子 \(x\) 的 \(b\) 最大,最优解肯定是贪心 ...

  9. Stratum挖矿协议&XMR挖矿流量分析

    目录 前言 区块链和挖矿相关概念 挖矿木马 挖矿协议Stratum Stratum工作过程 XMR挖矿流量分析 环境搭建 流量分析 总结 前言 之前参与了一个关于"挖矿行为检测"的 ...

  10. Java Study For Six Day( 面向对象二)

    static(静态)关键字 用于修饰成员(成员变量和成员函数) 被修饰后的成员具备以下的特点 随着类的加载而加载 优先于对象存在 被所有的对象共享 可以被类名直接调用 静态注意事项 静态方法只能访问静 ...