关于im:融云-IM-SDK-如何插入消息

应用融云 IMKit SDK 集成的时候,须要插入一条音讯,而后及时刷新 UI,然而集成完,因为对 SDK 不相熟,只有退出聊天页面再进来才会刷新进去。于是后盾提工单,技术大大给提供了计划,一语中的,解决了我的需要,非常感谢,特此记录,留给须要的敌人 //下述代码须要在聊天页子类调用, 因为 appendAndDisplayMessage 是聊天页的办法RCTextMessage *msg = [RCTextMessage messageWithContent:@"hello world"];RCMessage *insertMsg = [[RCIMClient sharedRCIMClient] insertOutgoingMessage:ConversationType_PRIVATE targetId:self.targetId sentStatus:SentStatus_SENT content:msg];[self appendAndDisplayMessage:insertMsg];想理解更多能够自行到融云官网查看 (https://www.rongcloud.cn/)

November 6, 2020 · 1 min · jiezi

关于im:为融云聊天页面的输入框添加-Placeholder

产品要求给输入框加个Placeh,其实挺简略一性能,寻遍他们的官网https://www.rongcloud.cn/和文...://docs.rongcloud.cn/v4/都没有找到相干材料,事实很残暴,SDK 木有这个接口,只能本人实现了,思来想去,用了个笨办法,加个 UILabel 一试,还真行,有须要的您请往下看。 其实就是给输入框价格 UILabel,在该显示的时候显示,该暗藏的时候暗藏就完事儿了,代码如下: 在聊天页面增加一个 UILabel 属性@property(nonatomic, strong) UILabel *placeholderLabel;初始化并增加 placeholderLabel 对象- (void)configPlaceholder { //初始化和设置 self.placeholderLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 180, 20)]; [self.chatSessionInputBarControl.inputTextView addSubview:self.placeholderLabel]; self.placeholderLabel.text = @"测试 Placeholder"; self.placeholderLabel.textColor = [UIColor grayColor]; self.placeholderLabel.userInteractionEnabled = YES; //增加点击手势 UITapGestureRecognizer *tapLabel = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapPlaceholderLabel)]; [self.placeholderLabel addGestureRecognizer:tapLabel];} - (void)tapPlaceholderLabel { [self.chatSessionInputBarControl updateStatus:KBottomBarKeyboardStatus animated:YES];}在内容发生变化和点击发送后,设置 placeholder 成果的显示- (void)inputTextView:(UITextView *)inputTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { //在内容发生变化和点击发送后,设置 placeholder 成果的显示 if ((range.location == 0 && [text isEqualToString:@""]) || [text isEqualToString:@""]) { self.placeholderLabel.hidden = NO; } else { self.placeholderLabel.hidden = YES; }}还要提一下,融云的 SDK 有撤回音讯当前的“从新编辑”性能,在这个时候,要敞开 placeholder 成果的显示- (void)didTapReedit:(RCMessageModel *)model { self.placeholderLabel.hidden = YES; [super didTapReedit:model];}到这儿性能就实现了,placeholderLabel 的具体文字效果能够自行批改调整,心愿下面的代码能够帮到你,喜爱的话,点个赞吧。 ...

November 6, 2020 · 1 min · jiezi

关于im:30-分钟集成融云-IM-即时通讯

最近公司要做一个社交 app,对于工夫就是金钱的当今社会,招聘大量人才去搭建通信零碎必定是不划算的,破费人力物力财力做进去的 app,可能还没人用。那就瞎了。所以毋庸置疑,一拍即合,用第三方的。就开始了对于目前市面上支流的第三方 IM SDK 进行调研。其中有腾讯云,网易云信,融云,环信等。列出了一堆比照条件,最初领导拍板用哪个。末端程序员是没有选择权的。好好搬砖就能够了~要明确本人的身份,嘎嘎 过程不说了,最初抉择了用融云,废话不多说,间接勒~这里只介绍一下如何疾速集成,让俩人聊起来,这也算是一个里程碑啊。对于程序员来说,聊不起来可就毁了,领导都特么奶凶奶凶的~~~ 1.先到融云官网 (https://www.rongcloud.cn/) 进行注册(注册按钮本人找吧),这个能够让你们产品经理或者啥领导去做,能够用公司的邮箱,别用本人的吧,前期本人换了地儿,对公司也是损失不是。注册后增加利用,拿到 appkey 2.xcode 创立一个新工程,或者找本人公司的我的项目,这里我举荐应用 pod 形式治理第三方,方便快捷,省时省力。因为手动形式太落后了,且配置繁琐,稍有脱漏就会报错,有些报错排查起来费时费力费神费电,所以还是老老实实的用 pod 吧。不听老人言,吃亏在眼前,听哥的没错,融云文档写了如何用 pod,几行命令的事。弄完后,也就是把 SDK 集成好了,跑一下工程,如果不报错,恭喜你兄嘚,马上能够聊天了,看下一步 3.须要在 appDelegate 中导入头文件。#import <RongIMKit/RongIMKit.h>。对了,咱们用的是带界面的 SDK,疾速集成不麻烦。 4.初始化 SDK - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //下边引号内须要替换为你的 appkey,别特么一成不变的抄哈,嘎嘎 [[RCIM sharedRCIM] initWithAppKey:@"融云开发者后盾的 AppKey"]; return YES;}5.这一步该连贯融云了兄嘚 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[RCIM sharedRCIM] initWithAppKey:@"获取到的 AppKey"]; [[RCIM sharedRCIM] connectWithToken:@"开发者的 server 通过申请 server api 获取到的 token 值" dbOpened:^(RCDBErrorCode code) {} success:^(NSString *userId) {} error:^(RCConnectErrorCode status) {}]; return YES;}敲黑板1:在这我得多说你几句,必须要看胜利回调和失败回调的调用,进了 success 就是胜利了,进了 error 就是谬误了。谬误了你要看 status 状态码啊,依据错误码来找问题。我在调试过程中就遇到了 RC_CONN_TOKEN_INCORRECT 错误码,顾名思义:token 不正确。这个就要找本人的服务端人员看是哪里问题导致的 token 不正确了。 ...

November 6, 2020 · 1 min · jiezi

关于im:融云-imkit-解析

阐明本篇文章简略介绍一下融云 imkit 蕴含的性能,大家可在浏览之后来对大体内容有一个根底的理解。具体内容还请翻阅 官网文档 根本内容融云 imkit 是为了不便开发者疾速集成而开发的一套 UI 库,外面次要蕴含三局部内容: 用户信息会话列表会话页面 用户信息 用户信息是指融云 SDK 提供了一套残缺的用户信息的显示与存储机制,外面蕴含了用户信息、群信息、群名片信息,开发者仅仅须要将本人 App Server 内的用户信息包装成一个融云的特有的用户信息对象,而后传递给对应的接口即可。之后融云 SDK 会本人帮忙显示对应用户的用户信息,并存储在本地数据库,不便后续的读取。 大体的显示流程可参考官网的流程图: 会话列表 会话列表是融云 SDK 依据音讯生成的一个 list 当收到一条新音讯并且会话列表没有以后会话的时候,SDK 会主动生成一条新的会话数据,并增加到 tableview 中。 须要留神的是融云的会话列表不会存储在服务器中,只在本地存储。当切换设施时,须要去融云开发者后盾开明多设施同步,这样在新设施登录的时候,会触发融云的音讯弥补,当挪动端接管到音讯的时候,会在新设施也生成一个新的会话列表。失常状况就是你设置几天就弥补几天。 反对的性能有: 会话置顶会话删除会话免打搅已读回执显示有人@显示会话页面 融云的会话页面整体可分为两局部 音讯展示区输入框音讯展现音讯展现就是以后用户收发音讯展现的中央,和惯例 APP 一样,接管在右边,发送在左边。发送方是不显示昵称的,接管方可依据配置来抉择是否显示昵称。 SDK 自带的音讯展现有 文本音讯语音音讯图片音讯GIF 音讯视频音讯地位音讯文件音讯小灰条音讯开发者还能依据本人的需要来自定义其余音讯,自定义音讯有两种用法: 发送其余须要展现的音讯,对应绑定一套 UI 组件,收到音讯后,融云SDK 帮忙你把绑定的这套 UI 展现到界面上。当做管制音讯:管制音讯就是你不展现到界面上,然而你能够利用音讯机制来做解决,从服务器或者其余中央下发一个指令,收到这个音讯后,UI不会产生任何变动。但你能够依据这条音讯来解决你的业务操作。比方刷新某个页面,获取某个信息等等开发者能够继承融云的会话页面,在其子类来进行其余操作,在进入会话页面的时候,SDK 会主动拉取以后会话的历史聊天记录,这个操作会先从本地数据库获取,如果不够 10 条的话,会从服务器获取,须要开明历史音讯云存储性能。(融云 SDK 会在本地搭建一套数据库,用来存储你所有的聊天内容) 在获取到历史记录之后,SDK 会主动帮你展现到 音讯展示区。展现进去的音讯都反对以下性能: 发送进来的音讯反对已读回执(单群聊)音讯撤回:kit 默认为两分钟音讯多选音讯转发:反对合并转发音讯援用音讯删除输入框SDK 提供的输入框共分为四局部: 文本输出语音输入表情扩大板文本输出: 文本输出反对用户输出任何文本 群聊输出 @ 可触发@ 人性能语音输入: 语音输入分为一般语音音讯和高清语音音讯 高清语音音讯是在2.9.25之后反对的。倡议应用此套计划。表情: SDK 有一套默认的 emoji 表情,且反对表情自定义。 ...

November 6, 2020 · 1 min · jiezi

关于im:融云主办-WICC-2020-探寻互联网通信云技术风向标

2020 年 10 月 31 日,寰球通信云行业领导者融云将以“融汇通信·云启将来”为主题,在深圳主办第二届寰球互联网通信云大会(以下简称 WICC 2020)。WICC 2020 星散了信通院、华为、阿里云、小鹏汽车、好将来、奇虎360、虎牙直播等行业机构和领军企业的技术首领,他们将就通信云技术将来所施展的价值与作用,开展巅峰对话与技术分享,帮忙开发者们疾速洞察 5G、新基建以及国产化等热点畛域下,通信云技术正在产生的改革。     本届通信云技术大会几个外围关键词是:聚焦前沿、技术赋能、科技兴邦,别离贯通于上午的主论坛和下午的“新趋势”、“新体验”、“新架构”三个分论坛中。 聚焦前沿:通信云技术撑持新基建、5G 生态的国家倒退策略 在新基建、5G 生态等国家倒退策略中,通信云技术将如何在将来十年施展科技强国的作用?如何更好地反对国产化零碎及平台?如何赋能各场景利用并继续当先世界?带着这些前沿问题,开发者们将在 WICC 2020 中找到答案。 WICC 主论坛中“新基建下的国家通信云生态建设”、“从前台通信到架构构建,5G 生态的关键技术挑战”、“通信云产业格局与将来发展趋势”等内容,将从国家策略及行业倒退高度登程,邀请信通院专家、华为技术首领、产业投资人别离站在不同角度,深层次解析通信云技术在撑持 5G、新基建演进中的重要作用。 作为主办方代表,融云 CTO 杨攀将带来“新常态下的互联网通信技术新趋势”的演讲,并在顶峰对话中,与技术首领一起探讨将来十年新基建下的通信云技术场景及利用价值,探寻互联网通信云技术倒退的风向标。 技术赋能: 探讨通信云技术的新趋势、新体验、新架构 随着 5G 大规模商用,通信云技术作为底层根底能力,赋能各行业畛域,必将解锁更多翻新利用场景。尤其是疫情带动下,实时音视频技术与即时通讯联合施展协同效应,促成了近程化、无接触的新常态体验。 围绕新趋势,“VR 沉迷式体验背地的低提早通信技术”、“边缘云的技术挑战和利用翻新”将成为热点。随着海量物联网设施接入,智能硬件的互联网通信需要将被进一步激发;随着寰球布局的节点老本高居不下,边缘计算对于互联网通信继续稳固倒退极为重要,技术大咖们分享这些的新趋势,有助于拓展开发者们的综合思维。 围绕新体验,具备代表性的头部企业将在近程会议、在线教育、电商等畛域与开发者们分享技术冲破带来的全新体验,其中融云“基于 WebRTC 超大规模音视频会议实际”将间接解码音视频的核心技术,这对于整个行业而言,都是不可多得的学习机会。 围绕新架构,“全球化下的跨国实时音视频直播架构建设”,“亿级终端数据体量下的高并发架构解析”,也是技术赋能场景所应关注的。这些来自虎牙直播、奇虎360、融云的技术大咖的内容分享,使开发者能够获知不同行业的最佳实际架构,从而避开技术开发中的“坑”。 科技兴邦:通信云技术可全面反对国产化 WICC 2020 的召开,正值中国通信新力量崛起之时。通信云技术开源翻新,拥抱寰球产业变动,更要反对国产化零碎及平台的合作伙伴。随着国产芯片、操作系统、中间件、数据库的体系化倒退,以“国产化对立操作系统架构的多边技术交融之路”为代表的技术分享,将深度分析通信技术在业务架构中的演进与变动。 科技兴邦义不容辞,在保持国产化自暴自弃的路线上,华为如此,融云亦是如此。融云自有研发的通信云技术已可全面反对国产支流厂商,在积极响应国家层面的推动国产代替计划的要求上,融云将为开发者们带来更多前瞻性思考和贵重地实际。 结语 WICC 2020 对于关注通信云将来发展趋势的开发者而言,是一场不容错过的盛宴。融云再次以互联网通信云行业领导者的身份主办这次大会,体现出其引领行业倒退和洞见将来的责任担当。在现场,融云还为开发者们筹备了多种互动体验,以及多种形式的深度交换,期待每一年的寰球互联网通信云大会,都可能让开发者们的思维碰撞指引将来的技术创新。

October 10, 2020 · 1 min · jiezi

关于im:亿级消息系统的核心存储Tablestore发布Timeline-20模型转载

亿级音讯零碎的外围存储:Tablestore公布Timeline 2.0模型:https://blog.csdn.net/weixin_...

October 4, 2020 · 1 min · jiezi

关于im:一个海量在线用户即时通讯系统IM的完整设计转载

一个海量在线用户即时通讯零碎(IM)的残缺设计(转载)http://www.yunliaoim.com/im/1...

October 3, 2020 · 1 min · jiezi

关于im:京东到家基于Netty的WebSocket应用实践分享转载

京东到家基于Netty的WebSocket利用实际分享http://www.yunliaoim.com/im/2...

October 3, 2020 · 1 min · jiezi

关于im:IM开发快速入门二什么是IM系统的实时性

本文在编写时参考了博客作者“鹿呦呦”和在线课程“即时消息技术分析与实战”的相干材料,一并表示感谢。 1、引言随着挪动互联网络的倒退,IM技术的利用曾经不仅限于聊天利用自身,它早已融入各种利用状态中,比方:直播中的主播互动、联网游戏中的玩家互动、外卖/打车利用中的实时地位共享、在线教育利用中的互动白板等。 在这些格调迥异的利用场景下,IM技术所出现进去的性能状态虽有不同,但“实时性”这个技术特色并无区别。 那么,对于技术门外汉来说,到底什么是IM的“实时性”?该如何了解它?这就是本文想要探讨的主题。 区别于弱小的原生利用,Web端的IM零碎,在很长一段时间内想实现真正的“实时性”,是十分艰难的,因为无奈间接应用UDP、TCP通信协议,在HTML5中的WebSocket呈现之前,Web端简直没有真正意义上的“双向实时通信”这种技术存在。 正因为如此,了解Web端即时通信技术的演进,也就自然而然能循序渐进地领会到IM零碎中的“实时性”了。所以本文将围绕Web端即时通讯技术,为你开展IM“实时性”这个话题。 情谊提醒:本系列文章侧重于实践概念的讲述,篇幅无限,点到即止,如需零碎、深刻、具体地学习IM技术的方方面面,请从此文动手:《新手入门一篇就够:从零开发挪动端IM》(史诗级文章,适宜从入门到放弃)。 学习交换: 即时通讯/推送技术开发交换5群:215477170 [举荐]挪动端IM开发入门文章:《新手入门一篇就够:从零开发挪动端IM》开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(本文同步公布于:http://www.52im.net/thread-3143-1-1.html) 2、系列文章目录《IM开发疾速入门(一):什么是IM零碎?》《IM开发疾速入门(二):什么是IM零碎的实时性?》(* 本文)《IM开发疾速入门(三):什么是IM零碎的可靠性? (稍后公布)》《IM开发疾速入门(四):什么是IM零碎的一致性? (稍后公布)》《IM开发疾速入门(五):什么是IM零碎的安全性? (稍后公布)》《IM开发疾速入门(六):什么是IM零碎的的心跳机制? (稍后公布)》《IM开发疾速入门(七):如何了解并实现IM零碎音讯未读数? (稍后公布)》《IM开发疾速入门(八):如何了解并实现IM零碎的多端音讯漫游? (稍后公布)》3、短轮询技术在晚期的Web时代,技术的创造者们无奈预感现在各种选进的技术利用模式,他们认为数据只是用来“看”的,也数据的获取根本就是“申请 -> 响应”这种一问一答模式。包含咱们平时浏览的各种门户网站都是采纳的“申请响应”模式。 这种依赖于用户“被动”申请的数据获取模式,如果想实现IM零碎,是无奈即时取得最新的聊天音讯的,因为用户并不知道新音讯什么时候到来,而服务端也没有方法被动告诉用户。 在这个期间,尽管技术和思路都受过后技术水平的限度,但IM总不能不做吧。 于是,一种被称为“短轮询”的数据获取模式呈现了。在“短轮询”模式下,IM客户端定时轮询服务端,以便让用户晓得是否有新的聊天音讯存在。 这种模式下,服务端收到申请后,即刻查问是否存在新音讯,有就返回给客户端,没有则返回空并立刻敞开连贯。 相较于后面用户须要“手动”去刷新页面的形式,这种模式只是将用户的“手动”变为“主动”而已,技术实质并没有产生任何实质性扭转。 短轮询这种模式,就好比旧时代一个期待重要邮件的人,他须要每天自已跑到邮局,被动去问是否有本人的函件,有就拿回家,如果没有,则第二天持续去问。一来一去,十分低效。 技术原理总结如下图所示: 短轮询这种模式有益处,也有害处。 益处是: 1)技术简略,容易实现;2)可维护性强,因为它没什么简单的。害处是: 1)因为无奈预知数据是否存在,所以少数申请是无用的,节约计算资源;2)为了晋升实时性,高频率的申请会加大服务端的性能负载。总结一下就是,短轮询这种模式对于IM技术大拿来说,显的十分low,因为技术实现切实是简略粗犷。 4、长轮询技术正如你所见,用短轮询技术来保障IM的实时性,的确难说优雅。不过,这难不倒无所不能的程序员,一种被称为“长轮询”的数据获取模式呈现了。 从技术上来说,长轮询实现的IM相较于短轮询最大的改良在于:短轮询状况下,服务端不论有没有新音讯,申请完结就会立刻断开连接。而长轮询时,如果本次申请没有新音讯产生,糨不会马上断开连接并返回,而是会将本次连贯“挂起”一段时间,如果在这段“挂起”工夫内有新的聊天音讯呈现,就能马上读取并立刻返回给客户端,接着完结本次连贯。一段时间后又会再次发动申请,如此周而复始。 长轮询这种模式,拿上节期待邮件的这个例子来说,就好比收信的人每天到邮局去问是否有函件,如果没有,他不马上回家,而是在邮局待上一段时间,如果这段时间过来了,还是没有,就先回家,接着第二天再来。 技术原理总结如下图所示: 长轮询的长处是: 1)相较于短连询,肯定水平升高了服务端申请负载;2)相较于短连询,实时性有晋升,因为它是被动“等”音讯。长轮询的毛病是: 1)长论询模式下,连贯“挂起”的这段时间内,服务端须要配合开启独自的音讯查问线程,依然存在无用功;2)相较于短连询模式,在一次长轮询完结、下次轮询发动前的窗口期内,依然存在“实时性”盲区。实际上,在Web端即时通讯技术里,长轮询有个业余的术语叫“Comet”,有趣味能够具体学习《Comet技术详解:基于HTTP长连贯的Web端实时通信技术》。 5、轮询无奈实现真正的“实时性”对于Web端即时通讯技术来说,下面提到的无论是短轮询,还是长轮询,它们都存在“实时性”盲区。 咱们回到上两节介绍的短轮询和长轮询技术原理图。 先看看短轮询这张图: 很显著,短轮询在每次轮询完结和下次轮询开始的间隔期内,是无奈感知到新音讯的,这也便造成了“实时性盲区”。换句话说,短轮询技术在“实时性盲区”内,无奈做到“实时”。 再来看看长轮询: 跟短轮询情理一样,长轮询在每次轮询完结和下次轮询开始的间隔期,仍然会造成“实时性盲区”。 要了解纠结轮询技术的实时性缺点,就得理解它们背地的技术——HTTP协定了。 HTTP协定设计的目标,就是为了实现“申请--响应”这种模式的数据交互,也就是众所周之的“短连贯”设计。而无论是短轮询还是长轮询,都跳不出HTTP的先天技术逻辑(申请--响应--断开)。 所以,归根到底,想要基于HTTP协定来实现IM,要达到真正的“实时性”,是相当勉强的。因为HTTP设计的目标,就是用“短连贯”来简化传统TCP长连贯通信带来的复杂性,而IM的实时性恰好要用到的又是TCP的长连贯个性,所以这就是个悖论。 要真正实现Web端的IM“实时性”,必定不能强行HTTP上做文章了,咱们须要新的技术。 6、WebSocket让Web端IM真正的“实时性”变成可能好消息是,HTML5中带来了WebSocket技术。WebSocket是真正的全双式双向通信技术(详见:《WebSocket从入门到精通,半小时就够!》)。 下图上新式轮询技术跟WebSocket的比照图: 从上图能够看出: 1)轮询技术一问一答,在下一个申请发动之前,存在“实时性”盲区;2)WebSocket一旦建设连贯后,数据能够随时双向通信(即客户端能够随时向服务端发消息,服务端也能够随时告诉客户端有新音讯)。举个例子就是:轮询技术相当于传统的邮件传递办法(你得自已去邮局问有没有新邮件),而WebSocket相当于古代的电话零碎,只有你拨通后,随时能够实时收听到对方的声音,对方也能随时收听到你的声音。完满! 总结一下WebSocket 的长处是: 1)真正的实时性:反对客户端与服务端真正的双向实时通信;2)大幅升高负载:少了轮询技术中高频率无用的申请,可大大降低服务端QPS压力;3)网络开销升高:一次连贯,随时应用,再也不必轮询技术中每次发动HTTP申请(随之而来的是每次HTTP的大量冗余协定头信息等)。7、本文小结本文以Web端即时通讯技术的演进为例,从短轮询到长轮询,再到WebSocket,实践联系实际地解说了Web端IM“实时性”的技术变迁,从而帮忙读者了解IM中“实时性”这个最为要害的技术特色。 附录:更多Web端即时通讯材料《新手入门贴:史上最全Web端即时通讯技术原理详解》《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》《SSE技术详解:一种全新的HTML5服务器推送事件技术》《Comet技术详解:基于HTTP长连贯的Web端实时通信技术》《老手疾速入门:WebSocket扼要教程》《WebSocket详解(一):初步意识WebSocket技术》《WebSocket详解(二):技术原理、代码演示和利用案例》《WebSocket详解(三):深刻WebSocket通信协议细节》《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》《WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)》《WebSocket详解(六):刨根问底WebSocket与Socket的关系》《socket.io实现音讯推送的一点实际及思路》《LinkedIn的Web端即时通讯实际:实现单机几十万条长连贯》《Web端即时通讯技术的倒退与WebSocket、Socket.io的技术实际》《Web端即时通讯平安:跨站点WebSocket劫持破绽详解(含示例代码)》《开源框架Pomelo实际:搭建Web端高性能分布式IM聊天服务器》《应用WebSocket和SSE技术实现Web端音讯推送》《详解Web端通信形式的演进:从Ajax、JSONP 到 SSE、Websocket》《MobileIMSDK-Web的网络层框架为何应用的是Socket.io而不是Netty?》《实践联系实际:从零了解WebSocket的通信原理、协定格局、安全性》《微信小程序中如何应用WebSocket实现长连贯(含残缺源码)》《八问WebSocket协定:为你疾速解答WebSocket热门疑难》《疾速理解Electron:新一代基于Web的跨平台桌面技术》《一文读懂前端技术演进:盘点Web前端20年的技术变迁史》《Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!》《Web端即时通讯实际干货:如何让你的WebSocket断网重连更疾速?》《WebSocket从入门到精通,半小时就够!》  更多同类文章 ……本文已同步公布于“即时通讯技术圈”公众号: ▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3143-1-1.html

September 18, 2020 · 1 min · jiezi

关于im:融云分析聊天室海量消息分发之消息丢弃策略

1 背景 随着直播、聊天室等 APP 的宽泛遍及利用,聊天室性能越来越被器重。比方往年十分火、下载量飙升的『直播带货』类 APP,在其直播中的用户聊天、弹幕、礼物、点赞、禁言、零碎告诉等场景都基于聊天室实现;如果将聊天室中产生的海量音讯全量散发至客户端,那么客户端可能会呈现卡顿,且此类刷屏音讯人眼无奈查看也会影响用户体验,因而聊天室音讯散发的抛弃策略应运而生。 2 海量音讯散发的挑战 咱们以一个百万人观看的直播聊天室模型进行举例: 1、在直播中会有一波一波的音讯顶峰,比方直播中的“刷屏”音讯,即大量用户在同一时段发送的海量音讯,个别状况下此类“刷屏”音讯的音讯内容基本相同。如果将所有音讯全副展现在客户端,则客户端很可能呈现卡顿、音讯提早等问题,重大影响用户体验。 2、海量音讯的状况下,如果服务端每条音讯都长期存储将导致服务缓存使用量激增,使得内存、存储成为性能瓶颈。 3、在另外一些场景下,比方聊天室的房间管理员进行操作后的告诉音讯或者零碎告诉,个别状况下这类音讯是较为重要的,须要优先保障它的达到率。 基于这些挑战,咱们的服务须要做一个基于业务场景的优化来应答。 3 聊天室架构模型 融云聊天室服务通过多年迭代更新,目前已十分稳固。以下简要介绍融云的聊天室架构,架构示意图如下: 图3-1 简要阐明: 聊天室服务-缓存聊天室的根本信息,包含用户列表,禁言封禁关系,白名单用户等 音讯服务-缓存本节点须要解决的用户关系信息、音讯队列信息等 * 聊天室用户关系同步: 成员被动退出退出时:聊天室服务同步至==> 音讯服务 散发音讯发现用户已离线时:音讯服务同步至==> 聊天室服务 *发送音讯: 聊天室服务通过必要校验通过后将音讯播送至音讯服务 聊天室服务不缓存音讯内容 Zk-各服务实例均注册到 Zk,数据用于服务间流转时的落点计算 聊天室服务:依照聊天室 ID 落点 音讯服务:依照用户 ID 落点 Redis-次要作为二级缓存,以及服务更新(重启)时内存数据的备份 4 聊天室音讯散发解决方案 聊天室服务的音讯散发、拉取计划: 图4-1 散发(图4-1): 用户 A 在聊天室中发送一条音讯,首先由聊天室服务解决 聊天室服务将音讯同步到各音讯服务节点 音讯服务向本节点缓存的所有成员下发告诉拉取 如图『音讯服务-1』将向用户 B 下发告诉 另外,在散发过程中,具备告诉合并机制;上述步骤 3 具体流程为: 将所有成员退出到待告诉队列中(如已存在则更新告诉音讯工夫) 下发线程,轮训获取待告诉队列 向队列中用户下发告诉拉取 通过次流程可保障下发线程一轮只会向同一用户发送一个告诉拉取,即多个音讯会合并为一个告诉拉取,无效晋升了服务端性能且升高了客户端与服务端的网络耗费。 图4-2 拉取(图4-2): 用户 B 收到告诉后将向服务端发送拉取音讯申请 ...

September 17, 2020 · 1 min · jiezi

实战搭建完整的IM即时通讯应用1

介绍即时通讯应用服务,整套蕴含服务端、治理端和客户端 预计3篇分享:这次是第一篇,我的项目的整体介绍和实体关系的梳理 现已部署上线,客户端和治理端,欢送体验 能够注册客户端账号,也能够应用初始默认账号,现有初始账号阐明: 账号明码阐明admin123456治理端账号user123456客户端普通用户账号muteuser123456客户端被禁言用户账号disabled123456客户端被封禁用户账号member1123456客户端普通用户账号member2123456客户端普通用户账号...123456客户端普通用户账号member30123456客户端普通用户账号<img width="300" src="https://i.loli.net/2020/07/15/fO2naUmPluYRsBd.png"> 性能简介注册,登录,集体、群组聊天,个人信息编辑等根底性能申请增加好友和申请入群表情,图片,视频,定位信息反对聊天会话列表记录音讯记录(微信的音讯记录实在一言难尽)反对多点同时登录百度 UNIT 机器人主动回复(todo)治理端,进行角色和权限的治理,群状态治理(我也当一回马化腾)需要简介挪动互联网倒退至今,以微信为首的即时通讯服务曾经融入了咱们生存中的各个角落,在公司的一些业务中也扮演着重要的角色,对于即时通讯咱们公司原来是应用的环信的服务,然而有很多定制化的需要无奈实现,所以起初决定外部开发一个满足定制化需要的即时通讯微服务。 应用socket.io框架是因为过后后端缺人,加上看了一些例子后感觉应用起来真的很不便,而且全平台反对,所以这个微服务就在前端团队进行落地实际,目前成果还不错。 社区目前这方面的内容比拟少或者太简陋(只有一个公共的聊天室这种)。另外就是在业务开发过程中被 PM 搞得很好受,所以想脱离一些特有的业务上的货色,实现一个性能简略五脏俱全的不掺杂公司业务的 IM 利用,蕴含服务端,治理端和客户端。客户端的模拟对象是微信,因为我很相熟,不必在产品下面思考太多,另外就是试用的人很相熟,不须要太多的沟通老本。 框架简介要开发一套残缺的即时通讯服务,须要以下局部: 服务端:用来实现根底的服务接口和数据长久化客户端:实现登录、聊天等根底性能,相似微信治理端:治理群组、用户和角色权限server为企业级框架和利用而生选用阿里的 egg.js 框架做撑持,看中的起因是他们外部大规模的落地和平安方面做得比拟好,没有抉择 nest 的起因是集成 socket.io 比拟麻烦,ORM 选用 sequelize,数据库是 mysql ,之前一起应用过,上手难度小 admin开箱即用的中台前端/设计解决方案抉择 Ant Design Pro 作为模板开发治理端,选用的起因是我对 Vue 全家桶比拟相熟,想借着这个机会相熟下整套 React 生态 的开发流程,感触下目前国内两大开发框架的本质区别和必由之路,Ant Design Pro 曾经公布了好几年了,也确实给中小型企业带来效率的晋升,也正好适宜我这的需要。 client????️ Vue.js 开发的规范工具应用 @vue/cli 搭建 IM 服务的客户端,一个挪动端的 H5 我的项目,UI 框架应用的有赞 vant,集成了我的开源组件vue-page-stack和黄老师的better-scroll,实现 IM 的根底性能 实体关系作为一个前端工程师,大多数的日常工作是不须要思考实体关系的。然而,就我的理论体验来看,懂得实体关系能够帮忙咱们更好的了解业务模型。而对产品和业务了解的晋升对咱们的帮忙是十分大的,能够在需要评审的时候发现很多不合乎逻辑的中央(怎么又要吐槽产品经理了),这时候能提出来就会被动防止咱们在后续的过程中进行重复开发,同时能够和产品侧的同学造成比拟良好的互动(而不是互怼)。上面简略列举下比拟重要的实体关系: <img width="600" src="https://i.loli.net/2020/07/14/Zhz85V2ptOylDcj.png"> 通过上图能够看到 user 是整个关系图中的外围,上面介绍下各个实体之间的关系: user 和 user_info(用户信息) 是一对一的关系user 和 role(角色)是多对多的关系role 和 right(权限)是多对多的关系user 和 apply(申请)是多对多的关系,申请都是波及到两个 user(申请人和被申请人)user 和 group(群组)是多对多的关系group 和 conversation(会话) 是一对一的关系friend 和 conversation(会话) 是一对一的关系conversation 和 message(音讯)是 1 对多的关系friend(好友关系) 和 user 没有间接关系,friend 由两个 user 确定上面具体介绍下会话、角色与权限: ...

July 15, 2020 · 1 min · jiezi

从游击队到正规军二马蜂窝旅游网的IM客户端架构演进和实践总结

一、引言移动互联网技术改变了旅游的世界,这个领域过去沉重的信息分销成本被大大降低。用户与服务供应商之间、用户与用户之间的沟通路径逐渐打通,沟通的场景也在不断扩展。这促使所有的移动应用开发者都要从用户视角出发,更好地满足用户需求。 论坛时代的马蜂窝,用户之间的沟通形式比较单一,主要为单纯的回帖回复等。为了以较小的成本快速满足用户需求,当时采用的是非实时性消息的方案来实现用户之间的消息传递。 随着行业和公司的发展,马蜂窝确立了「内容+交易」的独特商业模式。在用户规模不断增长及业务形态发生变化的背景下,为用户和商家提供稳定可靠的售前和售后技术支持,成为电商移动业务线的当务之急。 本文由马蜂窝电商业务 IM 移动端研发团队分享了马蜂窝电商业务 IM 移动端的架构演进过程,以及在IM技术力量和资源有限的情况下所踩过的坑等。 系列文章: 《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》(* 本文)学习交流: - 即时通讯/推送技术开发交流5群:215477170 [推荐]- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》(本文同步发布于:http://www.52im.net/thread-2796-1-1.html) 二、设计思路与整体架构我们结合 B2C,C2B,C2C 不同的业务场景设计实现了马蜂窝旅游移动端中的私信、用户咨询、用户反馈等即时通讯业务;同时为了更好地为合作商家赋能,在马蜂窝商家移动端中加入与会话相关的咨询用户管理、客服管理、运营资源统计等功能。 目前 IM 涉及到的业务如下: 为了实现马蜂窝旅游 App 及商家 IM 业务逻辑、公共资源的整合复用及 UI 个性化定制,将问题拆解为以下部分来解决: 1)IM 数据通道与异常重连机制:解决不同业务实时消息下发以及稳定性保障; 2)IM 实时消息订阅分发机制:解决消息定向发送、业务订阅消费,避免不必要的请求资源浪费; 3)IM 会话列表 UI 绘制通用解决方案:解决不同消息类型的快速迭代开发和管理复杂问题。 整体实现结构分为 4 个部分进行封装,分别为下图中的数据管理、消息注册分发管理、通用 UI 封装及业务管理。 三、技术原理和实现过程3.1、通用数据通道对于常规业务展示数据的获取,客户端需要主动发起请求,请求和响应的过程是单向的,且对实时性要求不高。但对于 IM 消息来说,需要同时支持接收和发送操作,且对实时性要求高。为支撑这种要求,客户端和服务器之间需要创建一条稳定连接的数据通道,提供客户端和服务端之间的双向数据通信。 3.1.1 数据通道基础交互原理 为了更好地提高数据通道对业务支撑的扩展性,我们将所有通信数据封装为外层结构相同的数据包,使多业务类型数据使用共同的数据通道下发通信,统一分发处理,从而减少通道的创建数量,降低数据通道的维护成本。 常见的客户端与服务端数据交互依赖于 HTTP 请求响应过程,只有客户端主动发起请求才可以得到响应结果。结合马蜂窝的具体业务场景,我们希望建立一种可靠的消息通道来保障服务端主动通知客户端,实现业务数据的传递。目前采用的是 HTTP 长链接轮询的形式实现,各业务数据消息类型只需遵循约定的通用数据结构,即可实现通过数据通道下发给客户端。数据通道不必关心数据的具体内容,只需要关注接收与发送。 3.1.2 客户端数据通道实现原理 客户端数据通道管理的核心是维护一个业务场景请求栈,在不同业务场景切换过程中入栈不同的业务场景参数数据。每次 HTTP 长链接请求使用栈顶请求数据,可以模拟在特定业务场景 (如与不同的用户私信) 的不同处理。数据相关处理都集中封装在数据通道管理中,业务层只需在数据通道管理中注册对应的接收处理即可得到需要的业务消息数据。 3.2、消息订阅与分发在软件系统中,订阅分发本质上是一种消息模式。非直接传递消息的一方被称为「发布者」,接受消息处理称为「订阅者」。发布者将不同的消息进行分类后分发给对应类型的订阅者,完成消息的传递。应用订阅分发机制的优势为便于统一管理,可以添加不同的拦截器来处理消息解析、消息过滤、异常处理机制及数据采集工作。 3.2.1 消息订阅 业务层只专注于消息处理,并不关心消息接收分发的过程。订阅的意义在于更好地将业务处理和数据通道处理解耦,业务层只需要订阅关注的消息类型,被动等待接收消息即可。 ...

October 18, 2019 · 2 min · jiezi

拿起键盘就是干跟我一起徒手开发一套分布式IM系统

1、引言老读者应该还记得我在去年国庆节前分享过一篇《技术干货:从零开始,教你设计一个百万级的消息推送系统》,虽然我在文中有贴一些伪代码,依然有些朋友希望能直接分享一些可以运行的源码。好吧,质疑我穷我无话可说(因为是真穷。。),怀疑我撸码的能力那是绝对不行,所以这次准备拉起键盘大干一场——徒手撸套分布式IM出来!^_^! 本文记录了我开发的一款面向IM学习者的 IM系统——CIM(全称:CROSS-IM),同时提供了一些组件帮助开发者构建一款属于自己可水平扩展的 IM。 通过学习本文和CIM代码,你可以获得以下知识: 1)如何从头开发一套IM(CIM的客户有点弱,见谅见谅);2)如何设计分布式的IM架构;3)如何将你的分布式IM架构用代码和相关技术实现出来。本文配套的CIM源码地址: 主要镜像:https://github.com/crossoverJie/cim备用镜像:https://github.com/52im/cim以下文章与本文类似或相关,同样有助于您的IM开发入门: 《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》《拿起键盘就是干:跟我一起徒手开发一套分布式IM系统》《浅谈IM系统的架构设计》《简述移动端IM开发的那些坑:架构设计、通信协议和客户端》《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》《一套原创分布式即时通讯(IM)系统理论架构方案》《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》* 友情提示:阅读本文和CIM源码,需要您具备一定的网络编程、IM理论等知识等,如果您还不具备这些,请先阅读《新手入门一篇就够:从零开发移动端IM》,完全来的及! 学习交流: - 即时通讯/推送技术开发交流5群:215477170[推荐]- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》(本文同步发布于:http://www.52im.net/thread-2775-1-1.html) 2、关于作者 crossoverJie(陈杰): 90后,毕业于重庆信息工程学院,现供职于重庆猪八戒网络有限公司。 3、运行演示本次特地录了两段视频演示(群聊、私聊),点击下方链接可以查看视频版 Demo。 CIM 私聊视频演示:https://www.bilibili.com/video/av39405821CIM 群聊视频演示:https://www.bilibili.com/video/av394055014、架构设计下面来看看具体的架构设计: 架构说明: 1)CIM 中的各个组件均采用 SpringBoot 构建;2)采用 Netty + Google Protocol Buffer 构建底层通信;3)Redis 存放各个客户端的路由信息、账号信息、在线状态等;4)Zookeeper 用于 IM-server 服务的注册与发现。整体主要由以下模块组成: 1)cim-server——IM 服务端:用于接收 client 连接、消息透传、消息推送等功能。支持集群部署;2)cim-forward-route——消息路由服务器:用于处理消息路由、消息转发、用户登录、用户下线以及一些运营工具(获取在线用户数等);3)cim-client——IM 客户端:给用户使用的消息终端,一个命令即可启动并向其他人发起通讯(群聊、私聊);同时内置了一些常用命令方便使用。5、逻辑流程图整体的流程也比较简单,流程图如下: 流程解释如下: 1)客户端向 route 发起登录;2)登录成功从 Zookeeper 中选择可用 IM-server 返回给客户端,并保存登录、路由信息到 Redis;3)客户端向 IM-server 发起长连接,成功后保持心跳;4)客户端下线时通过 route 清除状态信息。所以当我们自己部署时需要以下步骤: 1)搭建基础中间件 Redis、Zookeeper;2)部署 cim-server,这是真正的 IM 服务器,为了满足性能需求所以支持水平扩展,只需要注册到同一个 Zookeeper 即可;3)部署 cim-forward-route,这是路由服务器,所有的消息都需要经过它。由于它是无状态的,所以也可以利用 Nginx 代理提高可用性;4)cim-client 真正面向用户的客户端;启动之后会自动连接 IM 服务器便可以在控制台收发消息了。更多使用介绍可以参考快速启动。 ...

October 15, 2019 · 2 min · jiezi

谈谈接入各种第三方推送的技术方案和坑点

在移动互联网时代,为了运营好一个APP,消息推送是一个优质廉价的渠道。消息推送的使用场景简单来说,可以包括运营类的消息推送,如活动推广期间的推送等,还包括通知类的消息推送,如社交场景中的新消息提醒等。对于APP来说,消息推送能够起到内容告知、提高日活,甚至召回用户的作用。那么如何接入第三方推送平台呢?本篇文章中,网易云信资深研发工程师将和大家聊聊接入各种第三方推送平台的技术方案,分享接入推送平台的一些实用经验。 如何接入第三方推送1、 推送的一般流程推送是一种服务器主动push消息到设备端的行为,因此依赖于设备端和服务器的长连接。整体的架构和流程如下: 具体如下:1) 设备和推送服务器建立长连接2) 设备会根据某些规则生成或从推送服务器获取到一个DeviceToken,推送服务器可以根据DeviceToken定位到具体的设备3) 设备会上报DeviceToken到应用服务器(由应用自己完成)4) 应用服务器根据需要调用推送的服务端接口发起推送5) 推送服务器收到推送请求,根据请求中的DeviceToken定位到具体的设备,下发推送通知6) 设备收到推送消息,可以进行通知栏弹窗或者其他行为2、 IOS苹果官方提供了APNS推送,有很高的推送送达率。早先的APNS推送提供了一套基于TCP协议的接口,但是该接口使用方式比较复杂,稍有不慎就会导致推送失败,但调用方还误以为推送成功。后来苹果又提供了一套新的基于HTTP2协议的接口,新接口的一个好处是可以追踪到每个推送请求是被APNS服务器拒绝了还是成功了,再也不用去猜请求到底是被苹果服务器给丢了还是接受了。3、 安卓谷歌官方最早提供了GCM推送,后来又推出了FCM推送来代替GCM,但由于国内的环境不适合使用,因此各个手机厂商又相继推出了各自的推送,推送的原理都是类似的,都是依赖于设备和推送服务器的长连接,但是厂商推送的优势在于这样的长连接可以和自己的手机系统绑定到一起,从而可以不同应用共享同一条长连接,节省了心跳的流量消耗,并且这样的系统级长连接可以不用担心应用被杀导致的应用内长连接断连导致消息推送不可达。目前已经推出厂商推送的包括小米、华为、魅族、OPPO等,FCM也可以算安装了谷歌服务的设备的系统级推送。不同于IOS,安卓阵营的推送服务器接口都是HTTPS接口,并且通过SecretKey的方式来进行安全校验。 一点经验1、 DeviceToken的管理我们知道DeviceToken标识了一台具体的设备,但是推送服务本身是不知道应用本身的账号体系的,因此同一个APP,假设注销了A账号,改用B账号登录,此时DeviceToken一般来说是没有变化的,此时应用服务器需要去标识A账号的该设备属于注销状态,不然一条针对A账号的推送消息就会被B账号收到。2、 应用被卸载的情况应用被卸载的时候(这时候登录的A账号),应用本身感知不到,此时针对A账号的该设备的推送还是会发出去,推送服务器收到推送消息,找不到对应的设备,此时没有问题,只是会消耗一些资源。假设此时设备上的应用又重新安装了,然后登录了另一个账号B,假设DeviceToken没有变化,此时针对A账号的推送将会被B账号收到。上面这种情况出现的前提条件是DeviceToken没有发生变化,测试发现华为推送存在这个问题(经过询问华为推送技术支持,2018年3月之后的设备不存在该问题),其他推送没有。为了解决这个问题,服务器必须自己管理DeviceToken-用户账号的映射关系,并在发现有DeviceToken冲突的情况下去把老的账号设置为注销状态。3、 IM场景下推送时机问题IM场景下,应用服务器有自己长连接服务,此时第三方推送服务的作用是利用第三方厂商推送的系统级长连接来提高消息推送的送达率。首先对于IOS端,应用无法常驻后台,我们会在应用切换前后台的时候通过IM长连接发送一个标记位,服务器会在设备离线或者处于后台的情况下触发APNS推送,从而减少设备在前台情况下APNS推送的流量消耗。而对于安卓端,服务器会在设备处于离线的情况下触发第三方推送,否则会走IM长连接下发通知,当设备处于后台但还活着的时候,会在收到消息之后主动弹窗以便提醒用户有新消息。对于安卓端还有一个场景是这样的,安卓端在后台的某个时刻进程死了,此时过来一条需要推送的消息,服务器发现设备处于离线状态,尝试调用第三方推送(可能有也可能没有)。过了一会进程自己活回来了,重新连接到了IM服务器,拿到了未读消息,此时一般的逻辑下,进程会主动弹窗告知有消息到达,造成设备端的通知栏有两条推送。为了解决这个问题,需要IM服务器在设备重连的时候下发未读消息是否需要弹窗的信息。 以上就是网易云信对于第三方推送平台技术方案的介绍和经验分享。想要阅读更多技术干货文章,欢迎关注网易云信博客。了解网易云信,来自网易核心架构的通信与视频云服务。__网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。

June 28, 2019 · 1 min · jiezi

IM系统设计实践(一)

IM系统设计实践

April 6, 2019 · 1 min · jiezi

【融云分析】如何实现分布式场景下唯一 ID 生成?

◀背景▶对于一套分布式部署的 IM 系统,要求每条消息的 ID 要保证在集群中全局唯一且按生成时间有序排列。如何快速高效的生成消息数据的唯一 ID ,是影响系统吞吐量的关键因素。那么,融云是如何做到生成全局唯一消息 ID 的呢?首先需要明确下 ID 生成的核心需求:全局唯一有序◀设计▶融云消息数据的唯一 ID 长度采用 80 Bit 。每 5 个 Bit ,进行一次 32 进制编码,转换为一个字符,字符取值范围是,( 2 ~ 9 ) 和 ( A ~ B ),其中,已经去掉容易造成肉眼混淆的,数字 0 和 1 ,及字母 O 和 I 。这样,80 Bit 可以转换为 16 个字符,再加上 3 个分隔符( - ),将 16 个字符分为 4 组,最终得到一个 19 字符的唯一 ID 。 这样设计,即可以保证生成的 ID 是有序的,也能方便阅读。如上图所示,80 Bit 被分为 4 段:第一段 42 Bit ,用于存放时间戳,最长可表示到 2109 年,足够开发者当前使用了。时间戳数据放在高位,可以保证生成的唯一 ID 是按时间有序的,这个是消息 ID 必须要满足的条件。第二段 12 Bit ,用于存放自旋转 ID 。我们知道,时间戳的精度是到毫秒的,对于一套亿级 IM 系统来说,同一毫秒内产生多条消息太正常不过了,这个自旋 ID 就是在给落到同一毫秒内的消息进行自增编号。12 Bit 则意味着,同一毫秒内,单台主机中最多可以标识 4096( 2 的 12 次方)条消息。第三段 4 Bit ,用于标识会话类型。4 Bit ,最多可以标识 16 中会话,足够涵盖单聊、群聊、系统消息、聊天室、客服及公众号等常用会话类型。第四段 22 Bit ,会话 ID 。如群聊中的群 ID ,聊天室中的聊天室 ID 等。与第三段会话类型组合在一起,可以唯一标识一个会话。其他的一些 ID 生成算法,会预留两段,分别用来标识数据中心编号和主机编号(如 SnowFlake 算法),我们并没有这样做,而是将这两段用来标识会话。这样,ID 生成可以直接融入到业务服务中,且不必关心服务所在的主机,做到无状态扩缩容。◀实现过程▶消息 ID 共占 80 Bit ,计算时我们分为两部分,高 64 Bit (记为 highBits )和低 16 Bit (记为 lowBits )。获取当前系统的时间戳,并赋值给消息 ID 的高 64 Bit ;获取一个自旋 ID , highBits 左移 12 位,并将自旋 ID 拼接到低 12 位中;其中,自旋 ID 是一个从 0 到 4095 范围内,顺序递增的数,生成规则如下:上步的 highBits 左移 4 位,将会话类型拼接到低 4 位;取会话 ID 哈希值的低 22 位,记为 sessionIdInt ;highBits 左移 6 位,并将 sessionIdInt 的高 6 位拼接到 highBits 的低 6 位中;取会话 ID 的低 16 位作为 lowBits ;highBits 与 lowBits 拼接,得到 80 Bit 的消息 ID 。对其进行 32 进制编码,即可得到唯一消息 ID 。编码规则如下:从左至右,每 5 个 Bit 转换为一个整数,以这个整数作为下标,即可在下表中找到对应的字符。总结:这种 ID 生成的方式,需要注意保证自旋 ID 的生成是线程安全的。避免在并发情况下,生成出同样的 ID 。另外,此 ID 生成算法,强烈依赖系统时间,如果系统时间被改小,也可能造成 ID 生成重复。 ...

March 27, 2019 · 1 min · jiezi

一致性 Hash 算法的实际应用

前言记得一年前分享过一篇《一致性 Hash 算法分析》,当时只是分析了这个算法的实现原理、解决了什么问题等。但没有实际实现一个这样的算法,毕竟要加深印象还得自己撸一遍,于是本次就当前的一个路由需求来着手实现一次。背景看过《为自己搭建一个分布式 IM(即时通讯) 系统》的朋友应该对其中的登录逻辑有所印象。先给新来的朋友简单介绍下 cim 是干啥的:其中有一个场景是在客户端登录成功后需要从可用的服务端列表中选择一台服务节点返回给客户端使用。而这个选择的过程就是一个负载策略的过程;第一版本做的比较简单,默认只支持轮询的方式。虽然够用,但不够优雅????。因此我的规划是内置多种路由策略供使用者根据自己的场景选择,同时提供简单的 API 供用户自定义自己的路由策略。先来看看一致性 Hash 算法的一些特点:构造一个 0 ~ 2^32-1 大小的环。服务节点经过 hash 之后将自身存放到环中的下标中。客户端根据自身的某些数据 hash 之后也定位到这个环中。通过顺时针找到离他最近的一个节点,也就是这次路由的服务节点。考虑到服务节点的个数以及 hash 算法的问题导致环中的数据分布不均匀时引入了虚拟节点。自定义有序 Map根据这些客观条件我们很容易想到通过自定义一个有序数组来模拟这个环。这样我们的流程如下:初始化一个长度为 N 的数组。将服务节点通过 hash 算法得到的正整数,同时将节点自身的数据(hashcode、ip、端口等)存放在这里。完成节点存放后将整个数组进行排序(排序算法有多种)。客户端获取路由节点时,将自身进行 hash 也得到一个正整数;遍历这个数组直到找到一个数据大于等于当前客户端的 hash 值,就将当前节点作为该客户端所路由的节点。如果没有发现比客户端大的数据就返回第一个节点(满足环的特性)。先不考虑排序所消耗的时间,单看这个路由的时间复杂度:最好是第一次就找到,时间复杂度为O(1)。最差为遍历完数组后才找到,时间复杂度为O(N)。理论讲完了来看看具体实践。我自定义了一个类:SortArrayMap他的使用方法及结果如下:可见最终会按照 key 的大小进行排序,同时传入 hashcode = 101 时会按照顺时针找到 hashcode = 1000 这个节点进行返回。下面来看看具体的实现。成员变量和构造函数如下:其中最核心的就是一个 Node 数组,用它来存放服务节点的 hashcode 以及 value 值。其中的内部类 Node 结构如下:写入数据的方法如下:相信看过 ArrayList 的源码应该有印象,这里的写入逻辑和它很像。写入之前判断是否需要扩容,如果需要则复制原来大小的 1.5 倍数组来存放数据。之后就写入数组,同时数组大小 +1。但是存放时是按照写入顺序存放的,遍历时自然不会有序;因此提供了一个 Sort 方法,可以把其中的数据按照 key 其实也就是 hashcode 进行排序。排序也比较简单,使用了 Arrays 这个数组工具进行排序,它其实是使用了一个 TimSort 的排序算法,效率还是比较高的。最后则需要按照一致性 Hash 的标准顺时针查找对应的节点:代码还是比较简单清晰的;遍历数组如果找到比当前 key 大的就返回,没有查到就取第一个。这样就基本实现了一致性 Hash 的要求。ps:这里并不包含具体的 hash 方法以及虚拟节点等功能(具体实现请看下文),这个可以由使用者来定,SortArrayMap 可作为一个底层的数据结构,提供有序 Map 的能力,使用场景也不局限于一致性 Hash 算法中。TreeMap 实现SortArrayMap 虽说是实现了一致性 hash 的功能,但效率还不够高,主要体现在 sort 排序处。下图是目前主流排序算法的时间复杂度:最好的也就是 O(N) 了。这里完全可以换一个思路,不用对数据进行排序;而是在写入的时候就排好顺序,只是这样会降低写入的效率。比如二叉查找树,这样的数据结构 jdk 里有现成的实现;比如 TreeMap 就是使用红黑树来实现的,默认情况下它会对 key 进行自然排序。来看看使用 TreeMap 如何来达到同样的效果。运行结果:127.0.0.1000效果和上文使用 SortArrayMap 是一致的。只使用了 TreeMap 的一些 API:写入数据候,TreeMap 可以保证 key 的自然排序。tailMap 可以获取比当前 key 大的部分数据。当这个方法有数据返回时取第一个就是顺时针中的第一个节点了。如果没有返回那就直接取整个 Map 的第一个节点,同样也实现了环形结构。ps:这里同样也没有 hash 方法以及虚拟节点(具体实现请看下文),因为 TreeMap 和 SortArrayMap 一样都是作为基础数据结构来使用的。性能对比为了方便大家选择哪一个数据结构,我用 TreeMap 和 SortArrayMap 分别写入了一百万条数据来对比。先是 SortArrayMap:耗时 2237 毫秒。TreeMap:耗时 1316毫秒。结果是快了将近一倍,所以还是推荐使用 TreeMap 来进行实现,毕竟它不需要额外的排序损耗。cim 中的实际应用下面来看看在 cim 这个应用中是如何具体使用的,其中也包括上文提到的虚拟节点以及 hash 算法。模板方法在应用的时候考虑到就算是一致性 hash 算法都有多种实现,为了方便其使用者扩展自己的一致性 hash 算法因此我定义了一个抽象类;其中定义了一些模板方法,这样大家只需要在子类中进行不同的实现即可完成自己的算法。AbstractConsistentHash,这个抽象类的主要方法如下:add 方法自然是写入数据的。sort 方法用于排序,但子类也不一定需要重写,比如 TreeMap 这样自带排序的容器就不用。getFirstNodeValue 获取节点。process 则是面向客户端的,最终只需要调用这个方法即可返回一个节点。下面我们来看看利用 SortArrayMap 以及 AbstractConsistentHash 是如何实现的。就是实现了几个抽象方法,逻辑和上文是一样的,只是抽取到了不同的方法中。只是在 add 方法中新增了几个虚拟节点,相信大家也看得明白。把虚拟节点的控制放到子类而没有放到抽象类中也是为了灵活性考虑,可能不同的实现对虚拟节点的数量要求也不一样,所以不如自定义的好。但是 hash 方法确是放到了抽象类中,子类不用重写;因为这是一个基本功能,只需要有一个公共算法可以保证他散列地足够均匀即可。因此在 AbstractConsistentHash 中定义了 hash 方法。这里的算法摘抄自 xxl_job,网上也有其他不同的实现,比如 FNV1_32_HASH 等;实现不同但是目的都一样。这样对于使用者来说就非常简单了:他只需要构建一个服务列表,然后把当前的客户端信息传入 process 方法中即可获得一个一致性 hash 算法的返回。同样的对于想通过 TreeMap 来实现也是一样的套路:他这里不需要重写 sort 方法,因为自身写入时已经排好序了。而在使用时对于客户端来说只需求修改一个实现类,其他的啥都不用改就可以了。运行的效果也是一样的。这样大家想自定义自己的算法时只需要继承 AbstractConsistentHash 重写相关方法即可,客户端代码无须改动。路由算法扩展性但其实对于 cim 来说真正的扩展性是对路由算法来说的,比如它需要支持轮询、hash、一致性hash、随机、LRU等。只是一致性 hash 也有多种实现,他们的关系就如下图:应用还需要满足对这一类路由策略的灵活支持,比如我也想自定义一个随机的策略。因此定义了一个接口:RouteHandlepublic interface RouteHandle { /** * 再一批服务器里进行路由 * @param values * @param key * @return */ String routeServer(List<String> values,String key) ;}其中只有一个方法,也就是路由方法;入参分别是服务列表以及客户端信息即可。而对于一致性 hash 算法来说也是只需要实现这个接口,同时在这个接口中选择使用 SortArrayMapConsistentHash 还是 TreeMapConsistentHash 即可。这里还有一个 setHash 的方法,入参是 AbstractConsistentHash;这就是用于客户端指定需要使用具体的那种数据结构。而对于之前就存在的轮询策略来说也是同样的实现 RouteHandle 接口。这里我只是把之前的代码搬过来了而已。接下来看看客户端到底是如何使用以及如何选择使用哪种算法。为了使客户端代码几乎不动,我将这个选择的过程放入了配置文件。如果想使用原有的轮询策略,就配置实现了 RouteHandle 接口的轮询策略的全限定名。如果想使用一致性 hash 的策略,也只需要配置实现了 RouteHandle 接口的一致性 hash 算法的全限定名。当然目前的一致性 hash 也有多种实现,所以一旦配置为一致性 hash 后就需要再加一个配置用于决定使用 SortArrayMapConsistentHash 还是 TreeMapConsistentHash 或是自定义的其他方案。同样的也是需要配置继承了 AbstractConsistentHash 的全限定名。不管这里的策略如何改变,在使用处依然保持不变。只需要注入 RouteHandle,调用它的 routeServer 方法。@Autowiredprivate RouteHandle routeHandle ;String server = routeHandle.routeServer(serverCache.getAll(),String.valueOf(loginReqVO.getUserId()));既然使用了注入,那其实这个策略切换的过程就在创建 RouteHandle bean 的时候完成的。也比较简单,需要读取之前的配置文件来动态生成具体的实现类,主要是利用反射完成的。这样处理之后就比较灵活了,比如想新建一个随机的路由策略也是同样的套路;到时候只需要修改配置即可。感兴趣的朋友也可提交 PR 来新增更多的路由策略。总结希望看到这里的朋友能对这个算法有所理解,同时对一些设计模式在实际的使用也能有所帮助。相信在金三银四的面试过程中还是能让面试官眼前一亮的,毕竟根据我这段时间的面试过程来看听过这个名词的都在少数????(可能也是和候选人都在 1~3 年这个层级有关)。以上所有源码:https://github.com/crossoverJie/cim如果本文对你有所帮助还请不吝转发。 ...

March 1, 2019 · 2 min · jiezi

Android开发需要了解的 IM 知识

引言即便在通讯如此发达的今天,IM 也依然是诸多场景下非常重要的基础能力。因此做为 一名 Android 开发,不可避免的会遇到一些IM 相关的需求或问题。本文以一个Android开发的角度来讲述IM 开发相关的基础知识。想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客。了解网易云信,来自网易核心架构的通信与视频云服务。IM开发需要面对的问题网络问题,如何高效快速的传输数据?协议问题,消息如何封装?及时性问题,如何进行进程保活?网络问题 TCP 的三次握手建立连接是一个非常耗时的过程。在 IM 场景下,数据的传输将会非常的频繁,如果每次传输都建立一个 TCP 连接,那么这个效率是不能接受的,并且频繁的建立连接可能会发生socket错误,所以我们需要 “复用”TCP连接,也就是平时所说的TCP长连接。TCP 长连接短连接在建立后,当数据传输完毕时会立即关闭,下次需要传输数据时需要重新建立连接,在日常的业务场景非常常见,比如通过 http/https 请求获取Server 数据。而长连接在传输完数据后并不会关闭,这样下次需要传输数据时就可以直接使用已经建立好的连接,这中间省去了连接建立的时间。但是建立一个 TCP 长连接却并不是“建立后不关闭”那么简单,因为 TCP 长连接会“被动”关闭。网络地址转换 (NAT) IPv4的容量是有限的,随着接入Internet的计算机数量的不断猛增,IP地址资源也就愈加显得捉襟见肘,于是也就产生了 NAT技术。简单来说,NAT就是在局域网内部网络中使用内部地址,而当内部节点要与外部网络进行通讯时,就在网关(可以理解为出口,打个比方就像院子的门一样)处,将内部地址替换成公用地址,从而在外部公网(Internet)上正常使用,NAT可以使多台计算机共享Internet连接,这一功能很好地解决了公共 IP地址紧缺的问题。通过这种方法,可以只申请一个合法IP地址,就把整个局域网中的计算机接入Internet中。 而我们就处于运营商(移动/联通/电信。。。)的局域网内。当我们接入运营商的网络后,会分配到一个运营商的内部 IP地址,于是我们就可以使用这个IP地址建立连接向外传输数据了。但是当这个IP闲置了一段时间(NAT超时时间)后,运营商为了节约资源,会把分配给我们IP回收掉。此时如果我们还继续使用之前那个未关闭的连接去传输数据,那么毫无疑问会失败的。下面是一些运营商的 NAT超时时间。网络NAT超时时间中国移动3G/2G5 min中国联通2G5 min中国电信3G大于 28 min 要想长连接一直有效,那么闲置时间就不能太长,所以在闲置时我们需要向外(Server)传输一些数据包,这也就是常说的“心跳包”,用于告诉运营商这个 IP 还在被使用,告诉Server 客户端还在线。心跳策略 心跳策略一般分为两种: 1. 固定心跳 2. 动态心跳这里讲一下固定心跳,动态心跳可以参考 微信心跳 。固定心跳其实就是间隔固定时间发送一个心跳包。心跳间隔 X 的值需要参考运营商的 NAT 超时时间确定,不能大于最小的 NAT超时时间,也不能太小,要不Server 的负担非常重。一般取一个比较接近最小的NAT超时时间,比如4分钟。协议问题 协议决定是消息以什么样的形式传输,即发送时如果对消息进行封装,接收时如何解析。比如可以将消息体以 XML 的形式进行处理,这也就是 XMPP 协议,参考下面一个消息示意:隔壁老王:你儿子长的比你帅多了。老李:嘿嘿,谢谢夸奖!<message><from>隔壁老王</from><to>老李</to><context>你儿子长的比你帅多了。</context><type>text</type></message><message><from>老李</from><to>隔壁老王</to><context>嘿嘿,谢谢夸奖!</context><type>text</type></message> 从上面的消息示意,我们可以发现一条消息的内容可以拆分成很多属性,而协议就是把这些属性组合起来。以 XML 的形式传输消息,最大的一个问题,就是冗余数据太多了,特别是当消息的属性比较多时。 那么有没什么格式能尽可能有减小冗余数据? 其实无论消息如何封装,最终传输的肯定是二进制流,那么完全可以直接用二进制的形式对消息进行封装,这也就是二进制协议。下面是一个简单二进制协议的实现示意。 一条消息由from + to + context + type这几个属性组成,那么我们完全可以按顺序存储在二进制中,由于内容长度不确定,所以每个属性的开头我们可以使用固定字节数来记录这个属性的内容长度。当然,这里只是展示了一个二进制协议的例子,实际的消息会比这复杂多了,但是核心思路就是这么简单,最终无非是设计与实现形式上的差距。及时性问题 IM的作为即时通讯,如果无法保证消息及时触达,那么意义就大打折扣。要保证消息及时触达,最关键要做到以下两点: 1. App 进程要尽量存活,也就是进程保活 ; 2. 在 App进程挂掉后,能够唤醒起来;进程保活 进程保活其实是属于 Android 平台的一个话题,相信大家日常开发也遇到过,细节就不在这长篇大论了,简单的说下几个原则: 1. 优化内存,减小内存的占用,会大大的减小被 kill 的机率; 2. 多进程,将 UI 进程与 IM 进程独立出来,这样 IM 的进程负担就会小很多;进程唤醒 严格意义上来说进程唤醒是属于进程保活的一个分支,这里单独列出来,是因为进程唤醒关注的是进程挂掉之后的动作。对于进程唤醒,这里也只列些原则,详细的可以去查阅相关资料。 1. 静态注册监听广播(<7.0) 2. Alarm定时任务,定时去检查进程是否存活。 3. JobScheduler定时任务,定时去检查进程是否存活( >5.0 ) 4. 接入厂商推送网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。 ...

January 21, 2019 · 1 min · jiezi

从0到1构建网易云信IM私有化

本文来源于MOT技术管理课堂杭州站演讲实录,全文 2410 字,阅读约需 5分钟。网易云信资深研发工程师张翱从私有化面临的问题及需求说起,分享了网易云信IM私有化的解决方案和具体实践。想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客。了解网易云信,来自网易核心架构的通信与视频云服务。私有化的源起在做公有云平台的过程中,我们接触到很多客户,有许多客户和我们反馈:“你们的云平台服务很好、线上也很稳定,但我们希望能把云平台搬到自己的环境里部署起来”。在进一步了解情况后,我们也得到了客户要求私有化的几个诉求点:私密性要求一些企业出于数据保密及安全方面的顾虑,希望能把关键数据安放在自建机房或者数据中心,对网络访问进行严格控制;另外像银行金融机构以及政府部门会受到监管合规等方面的限制,私密性甚至是一个硬指标。自主性要求希望能够自主掌控IM系统,这类客户一般自身便具有较强的开发和运维团队。数据资产化要求公有云上的客户需要依赖我们的数据开放能力,而在私有化部署后客户能够一手掌握存储的原始数据以及使用过程产生的所有日志信息,使数据真正转变为企业资产,满足灵活多样的数据分析需求进而增值。本地化应用要求一些企业对应用时延有较高要求,公有云平台无法满足,从而需要进行本地私有化部署。另外和现有企业内部信息系统整合,构建沟通交流协作大平台的需求成为企业选择私有化部署的一种考量。由于私有化的呼声持续增长,我们决定启动私有化项目,那么从服务提供者的角度我们需要什么样的私有化呢?复用业务代码复用公有云代码,不重复造轮子,减少与公有云代码版本的差异性,使私有化系统最大限度继承公有云上的能力,降低测试开发维护成本。适配不同环境具有私有化需求的客户来自各行各业,私有化部署环境也会各不相同,系统除了能在网易蜂巢、阿里云、华为云等主流云平台上跑起来,也需要能够适配企业自建数据中心的虚拟机以及物理机环境。部署高效可复制部署流程标准化自动化。我们所追求的私有化,不是耗费1-2个月的工作量为企业部署一套定制化系统,而是高效可复制的。另外针对目前企业中IM相关的企业办公等场景的部署规模等实际情况,标准化部署我们走的是相对轻量化的路线,有效降低企业的部署成本。服务稳定可靠这其实是一个分布式系统的基本要求,内部各个组件高可用可扩展,消除单点。私有化面临的问题明确了需求后我们再来看看所面临的一些问题。公有云的主体架构如下,主要由客户端层、网关接入和路由层、业务层、中间件、数据存储层以及监控系统组成,其中接入层根据不同的连接方式以及应用场景拆分为多个服务,业务层根据不同业务逻辑划分出漫游、推送、历史消息以及抄送等服务。在这些服务里面会涉及到JAVA、C、Golang等多种技术栈,当这些服务混合部署到各种环境中,如何解决依赖管理以及可能发生的底层库冲突,怎么通过技术让这个过程变得简单高效,是我们面临的主要问题。下面我们来看如何解决落地。私有化解决方案概括起来主要是围绕Docker技术从主机、容器、镜像和编排四个层面来解决主机主机上除了标准的操作系统,初始化只需要安装Docker引擎、Supervisor和MetricBeat。其中,Supervisor起到管理容器实例的作用,当容器出现状况时起到一定的故障恢复的作用;MetricBeat是ELK技术栈中的监控agent,能够向监控系统上报主机的资源使用情况以及各个容器的健康状况。最小化依赖组件,就降低了出现依赖冲突的可能性,达到兼容更多云主机、虚拟机以及物理机环境的目的。镜像主机上最小化依赖项,那么每个服务依赖管理的任务就落到了镜像这个层面上。每个镜像对应一种服务并且自我管理依赖,多个镜像对应的容器之间相互隔离。比如服务A依赖jdk7,而服务B必须跑在jdk8版本上,如果这两个服务没有容器化而是跑在同一主机环境下,我们就需要显式指定所使用的JAVA路径,增加额外的复杂度。更坏的情况,如果出现底层库的版本冲突,可能会导致不同服务无法部署在一个主机上,这显然是我们不想看到的。但通过Docker与生俱来的隔离特性,我们能很好地规避这个问题。容器镜像实例化后我们便得到了运行中的容器,不同于单进程容器的是,我们使用Supervisor作为容器入口,再由Supervisor来管理容器中的多个进程,这些进程有主次之分,主进程对外提供服务,次进程一般包括MetricBeat和FileBeat,前者起到主进程监控和业务监控的功能,后者是ELK生态中的日志采集组件。编排将多个同类容器组成集群,将非同类容器进行配置并连接可达,是编排的基本功能。往往说起Docker容器编排,大家首先想到的是kubernetes(以下简称k8s),不同于容器云等场景,在企业IM场景中用户数普遍为几十万左右,对应的集群主机数量一般不超过10台,在这种规模下将k8s整合进去代价较大。于是我们考虑轻量的方式,就是使用Ansible。对于Ansible,做过运维的同学应该比较熟悉,它基于SSH采用无agent架构,是集群管理的有力工具。虽然丢失了k8s中容器动态管理以及故障自我恢复等高级功能,但通过前面提到的在主机和容器层面引入Supervisor管理的方式,在一定程度上保留了容器管理和故障恢复的能力。Ansible虽然轻量,但通过丰富的功能模块、角色定义,能够具备强大的脚本表达能力,我们在此基础上编写主机初始化流程,各个服务的高可用集群如基于keepalived虚拟ip的MySQL主从或者双主集群以及基于OpenResty负载均衡双主集群。除了技术架构上的四个层面,在ansible部署脚本之上我们封装了http接口并开发了可视化的安装向导。此外我们还提供管理平台和运维平台kibana,这些可视化平台能够方便交付工程师和运维工程师在安装部署、集群管理、应用管理以及运维监控等方面提高效率,真正做到从部署到交付后运维的全流程高效可复制。最后,我们把验收工作比作交付的最后一公里,由于PaaS产品并不像SaaS那样能够做到开箱即用的效果,我们提供了demo程序进行测试,覆盖iOS/AOS/Web/PC等主流客户端,具备单聊、群聊、聊天室、双人及多人音视频通话等场景便于客户在场景中验证核心能力。Demo程序开放源代码,便于后续接入集成。云信实战经验总结那么,从IM私有化实践中我们可以得到什么经验?总结为以下五点:1) 标准OS提供计算资源,兼容异构环境2) Docker实现程序包封装和运行时资源隔离3) Ansible实现分布式集群高可用部署4) 可视化平台使部署及管理高效可复制5) 多端demo验证突破交付最后一公里以上就是网易云信IM私有化实践的分享,期待和大家共同探讨、交流。网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。

January 21, 2019 · 1 min · jiezi

SSL加密与分布式IM系统-InChat1.1.3版本试用说明

本文首发于本博客 猫叔的博客,转载请申明出处2019年1月15号-InChat发布V1.1.3版本InChat一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架v1.1.3版本使用说明v1.1.0-alpha版本使用说明InChatV1.1.2版本使用说明历史更新说明1.1.2版本捕获未注册链接断开异常,完善异常处理修改项目启动流程,仿Selector启动模式添加HTTP接口三个:发送通知消息,获取在线用户数,获取在线用户列表,暂不支持用户自定义HTTP接口(对于传统web API我们希望用户用自己的框架与流程)服务端发送通知接口改为enum处理1.1.3版本添加SSL加密,实现https与wss功能接受用户自定义证书(浏览器信任与不信任均可以访问)InChat配置可改为分布式或着单机版引入Redis,处理集群信息与消息互通如果有生产需要或者个别需求,发现BUG,欢迎留言,项目会更新新的API关于InChat的Maven依赖fastjson 》 1.2.53gson 》 2.8.5netty 》 4.1.32.Finalcommons-lang 》 3.5slf4j-log4j12 》 1.7.25jedis 》 3.0.1创建项目创建一个空的Maven项目,并引入InChatMaven包,(注意,请不要使用与本项目相同的包目录)。可能你只需要这样的Maven依赖即可<dependencies> <dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.3</version> </dependency></dependencies>对接InChat的接口与实现InChat启动参数可以自配置你只需要继承InChat的默认配置类InitNetty即可,如下public class MyInit extends InitNetty { /** 自定义启动监听端口 / @Override public int getWebport() { return 8090; } /* 是否启动分布式 true-启动、false-不启动 / @Override public Boolean getDistributed() { return true; } /* 是否启动加密功能 */ @Override public boolean isSsl() { return true; }}请注意,分布式为测试版,所以暂不支持SSL加密,如果启动分布式请关闭SSL加密功能如何自定义证书?#keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -dnam e “CN=in-chat.cn” -keypass 123456 -storepass 123456 -keystore inchat.jkskeytool为JDK提供的生成证书工具keysize 2048 密钥长度2048位(这个长度的密钥目前可认为无法被暴力破解)validity 365 证书有效期365天keyalg RSA 使用RSA非对称加密算法dname “CN=gornix.com” 设置Common Name为gornix.com,这是我的域名keypass 654321 密钥的访问密码为123456storepass 123456 密钥库的访问密码为123456(其实这两个密码也可以设置一样,通常都设置一样,方便记)keystore gornix.jks 指定生成的密钥库文件为inchat.jks如果你试着自己创建了自己的证书,那么你需要去重写InitNetty中的几个信息:jksFile,jksStorePassword,jksCertificatePassword。你的jks文件只需要放到resources目录下就好,两个密码就是你之前设定相同的密码。本项目已经提供了默认的inchat.jks,请用户在Maven包中复制并粘贴到自己的项目中的resources文件夹中即可。获取聊天消息数据此接口与原先一样,仅修改了方法名public class DataBaseServiceImpl implements InChatToDataBaseService { @Override public Boolean writeMessage(InChatMessage message) { System.out.println(message.toString()); return true; }}登录校验与群聊消息此接口没有做过多的修改public class VerifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { return true; } @Override public JSONArray getArrayByGroupId(String groupId) { JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}服务端发送通知消息枚举类此接口具有Demo模板,用户需要继承InChat框架的FromServerService接口,同时该接口注释也有实例demo,我们需要实现一个自定义的枚举,你可以这样写:public enum FromServerServiceImpl implements FromServerService { //你可以自定义自己的系统消息,请以Integer-String的形式 TYPE1(1,"【系统通知】您的账号存在异常,请注意安全保密信息。"), TYPE2(2,"【系统通知】恭喜您连续登录超过5天,奖励5积分。"); private Integer code; private String message; FromServerServiceImpl(Integer code, String message){ this.code = code; this.message = message; } public Integer getCode() { return code; } //实现接口的方法,遍历本枚举的code,获取对应的消息,作为系统消息发送 public String findByCode(Object code) { Integer codes = (Integer)code; for (FromServerServiceImpl item: FromServerServiceImpl.values()) { if (item.code == codes){ return item.message; } } return null; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }}启动项目1.1.3版本的启动项目变得异常的简单,你只需要配置启动的配置工厂即可。但是如果我们启动了分布式的话,我们还需要配置redis信息。public class application { public static void main(String[] args) { //配置你的自定义配置 ConfigFactory.initNetty = new MyInit(); //配置校验类 ConfigFactory.inChatVerifyService = new VerifyServiceImpl(); //配置消息接收处理类 ConfigFactory.inChatToDataBaseService = new DataBaseServiceImpl(); //配置服务端系统消息枚举,这里的值无所谓 TYPE1或者TYPE2或者TYPEN均可以 ConfigFactory.fromServerService = FromServerServiceImpl.TYPE1; //配置分布式Redis地址,端口目前默认的,下一版可以支持修改。 ConfigFactory.RedisIP = “192.168.192.132”; //启动InChat InitServer.open(); }}项目效果启动成功DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8090】 INFO - [RedisConfig.getJedis]:连接成功,测试连接PING->PONG如果你开通了分布式,那么你可以试着启动两个应用程序。DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8070】 INFO - [RedisConfig.getJedis]:连接成功,测试连接PING->PONG读者可以到项目中使用原来的两个前端页面。分别登录两个用户在两个应用程序,并进行互相通信即可。启动分布式请关闭SSL,分布式为测试版,暂不支持SSL目前,分布式版本接通了点对点与群聊的功能,大家可以试试。下一版本会添加一个分布式的组件用来统一数据与接口功能。关于加密的,请提前让电脑认同信任证书关于分布式的操作效果关于HTTP接口的,目前与分布式无关原先的自我发送,点对点发送,群聊,异常处理,HTTP接口均与原来一样原先的接口说明可以看上一版本: InChatV1.1.2版本使用说明前端相关这里你可以来到InChat的Front-End-Testing文档夹中的chat.html。你可以直接使用,你进需要修改对应的对接IP即可。如果你开了SSL加密,你的IP开头记得改为:wss://192.168.1.121:8090/ws !!!前端可以看原来的版本: InChatV1.1.2版本使用说明关于数据库设计当前一版不会固定大家的数据库设计,大家可以自己自由设计,同时搭上自己的项目,构建一个附带IM的自项目。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

January 15, 2019 · 2 min · jiezi

为自己搭建一个分布式 IM 系统二【从查找算法聊起】

前言最近这段时间确实有点忙,这篇的目录还是在飞机上敲出来了的。言归正传,上周更新了 cim 第一版;没想到反响热烈,最高时上了 GitHub Trending Java 版块的首位,一天收到了 300+ 的 star。现在总共也有 1.3K+ 的 star,有几十个朋友参加了测试,非常感谢大家的支持。在这过程中也收到一些 bug 反馈,feature 建议;因此这段时间我把一些影响较大的 bug 以及需求比较迫切的 feature 调整了,本次更新的 v1.0.1 版本:客户端超时自动下线。新增 AI 模式。聊天记录查询。在线用户前缀模糊匹配。下面谈下几个比较重点的功能。客户端超时自动下线 这个功能涉及到客户端和服务端的心跳设计,比较有意思,也踩了几个坑;所以准备留到下次单独来聊。AI 模式大家应该还记得这个之前刷爆朋友圈的 估值两个一个亿的 AI 核心代码。和我这里的场景再合适不过了。于是我新增了一个命令用于一键开启 AI 模式,使用情况大概如下。欢迎大家更新源码体验,融资的请私聊我????。聊天记录聊天记录也是一个比较迫切的功能。使用命令 :q 关键字 即可查询与个人相关的聊天记录。这个功能其实比较简单,只需要在消息发送及接收消息时保存即可。但要考虑的一点是,这个保存消息是 IO 操作,不可避免的会有耗时;需要尽量避免对消息发送、接收产生影响。异步写入消息因此我把消息写入的过程异步完成,可以不影响真正的业务。实现起来也挺简单,就是一个典型的生产者消费者模式。主线程收到消息之后直接写入队列,另外再有一个线程一直源源不断的从队列中取出数据后保存聊天记录。大概的代码如下:写入消息的同时会把消费消息的线程打开:而最终存放消息记录的策略,考虑后还是以最简单的方式存放在客户端,可以降低复杂度。简单来说就是根据当前日期+用户名写入到磁盘里。当客户端关闭时利用线程中断的方式停止了消费队列的线程。这点的设计其实和 logback 写日志的方式比较类似,感兴趣的可以去翻翻 logback 的源码,更加详细。回调接口至于收到其他客户端发来的消息时则是利用之前预留的消息回调接口来写入日志。收到消息后会执行自定义的回调接口。于是在这个回调方法中实现写入逻辑即可,当后续还有其他的消息处理逻辑时也能在这里直接添加。当处理逻辑增多时最好是改为责任链模式,更加清晰易维护。查找算法接下来是本文着重要讨论的一个查找算法,准确的说是一个前缀模糊匹配的算法。实现的效果如下:使用命令 :qu prefix 可以按照前缀的方式搜索用户信息。当然在命令行中其实意义不大,但是在移动端中确是比较有用的。类似于微信按照用户名匹配:因为后期打算出一个移动端 APP,所以就先把这个功能实现了。从效果也看得出来:就是按照输入的前缀匹配字符串(目前只支持英文)。在没有任何限制的条件下最快、最简单的实现方式可以直接把所有的字符串存放在一个容器中 (List、Set),查询时则挨个遍历;利用 String.startsWith(“prefix”) 进行匹配。但这样会有几个问题:存储资源比较浪费,不管是 list 还是 Set 都会有额外的损耗。查询效率较低,需要遍历集合后再遍历字符串的 char 数组(String.startsWith 的实现方式)。字典树基于以上的问题我们可以考虑下:假设我需要存放 java,javascript,jsp,php 这些字符串时在 ArrayList 中会怎么存放?很明显,会是这样完整的存放在一个数组中;同时这个数组还可能存在浪费,没有全部使用完。但其实仔细观察这些数据会发现有一些共同特点,比如 java,javascript 有共同的前缀 java;和 jsp 有共同的前缀 j。那是否可以把这些前缀利用起来呢?这样就可以少存储一份。比如写入 java,javascript 这两个字符串时存放的结构如下:当再存入一个 jsp 时:最后再存入 jsf 时:相信大家应该已经看明白了,按照这样的存储方式可以节省很多内存,同时查询效率也比较高。比如查询以 jav 开头的数据,只需要从头结点 j 开始往下查询,最后会查询到 ava 以及 script 这两个个结点,所以整个查询路径所经历的字符拼起来就是查询到的结果java+javascript。如果以 b 开头进行查询,那第一步就会直接返回,这样比在 list 中的效率高很多。但这个图还不完善,因为不知道查询到啥时候算是匹配到了一个之前写入的字符串。比如在上图中怎么知道 j+ava 是一个我们之前写入的 java 这个字符呢。因此我们需要对这种是一个完整字符串的数据打上一个标记:比如这样,我们将 ava、script、p、f 这几个节点都换一个颜色表示。表明查询到这个字符时就算是匹配到了一个结果。而查到 s 这个字符颜色不对,代表还需要继续往下查。比如输入关键字 js 进行匹配时,当它的查询路径走到 s 这里时判断到 s 的颜色不对,所以不会把 js 作为一个匹配结果。而是继续往下查,发现有两个子节点 p、f 颜色都正确,于是把查询的路径 jsp 和 jsf 都作为一个匹配结果。而只输入 j,则会把下面所有有色的字符拼起来作为结果集合。这其实就一个典型的字典树。具体实现下面则是具体的代码实现,其实算法不像是实现一个业务功能这样好用文字分析;具体还是看源码多调试就明白了。谈下几个重点的地方吧:字典树的节点实现,其中的 isEnd 相当于图中的上色。利用一个 Node[] children 来存放子节点。为了可以区分大小写查询,所以子节点的长度相当于是 26*2。写入数据这里以一个单测为例,写入了三个字符串,那最终形成的数据结构如下:图中有与上图有几点不同:每个节点都是一个字符,这样树的高度最高为52。每个节点的子节点都是长度为 52 的数组;所以可以利用数组的下标表示他代表的字符值。比如 0 就是大 A,26 则是小 a,以此类推。有点类似于之前提到的布隆过滤器,可以节省内存。debug 时也能看出符合上图的数据结构:所以真正的写入步骤如下:把字符串拆分为 char 数组,并判断大小写计算它所存放在数组中的位置 index。将当前节点的子节点数组的 index 处新增一个节点。如果是最后一个字符就将新增的节点置为最后一个节点,也就是上文的改变节点颜色。最后将当前节点指向下一个节点方便继续写入。查询总的来说要麻烦一些,其实就是对树进行深度遍历;最终的思想看图就能明白。所以在 cim 中进行模糊匹配时就用到了这个结构。字典树的源码在此处:https://github.com/crossoverJie/cim/blob/master/cim-common/src/main/java/com/crossoverjie/cim/common/data/construct/TrieTree.java其实利用这个结构还能实现判断某个前缀的单词是否在某堆数据里、某个前缀的单词出现的次数等。总结目前 cim 还在火热内测中(虽然群里只有20几人),感兴趣的朋友可以私聊我拉你入伙☺️ 再没有新的 BUG 产生前会着重把这些功能完成了,不出意外下周更新 cim 的心跳重连等机制。完整源码:https://github.com/crossoverJie/cim如果这篇对你有所帮助还请不吝转发。 ...

January 14, 2019 · 1 min · jiezi

用Java构建一个简单的WebSocket聊天项目之新增HTTP接口调度

本文首发公众号与个人博客:Java猫说 & 猫叔的博客 | MySelf,转载请申明出处。前言大家可以看看上一篇:用Java构建一个简单的WebSocket聊天室在上一篇文章中我们已经实现了:自我对话、好友交流、群聊、离线消息等的功能。而本篇,我们的框架升级了,并且开通了几个新的HTTP接口功能,同时也把原先框架的一些异常做了处理。我们将使用更少的代码完成功能更加完善的聊天项目!采用框架我们整个Demo基本不需要大家花费太多时间,就可以实现以下的功能。用户token登录校验自我聊天点对点聊天群聊获取在线用户数与用户标签列表发送系统通知首先,我们需要介绍一下我们今天打算采用的框架,InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,采用这个框架,我们基本上只需要两三个类就可以实现我们今天需要的功能了。先看看效果需要了解SSM & SpringBoot 吗?InChat ,本身不依赖于任何的底层框架,所以大家只要会基本的Java语言就可以实现一套自己的WebSocket聊天室。框架使用手册(新版V1.1.2刚刚发布)关于详细的手册说明,大家可以看看官网的介绍:V1.1.2版本使用说明V1.1.2版本视频教学<dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.2</version></dependency>开始Demo搭建构建一个空的Maven项目我们不需要依赖其他的Maven包,只要本文提及的框架即可。<dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.2</version></dependency>InChat启动参数可以自配置你只需要继承InChat的默认配置类InitNetty即可,如下public class MyInit extends InitNetty { /** 自定义启动监听端口 */ @Override public int getWebport() { return 8090; }}获取聊天消息数据此接口与原先一样,仅修改了方法名public class DataBaseServiceImpl implements InChatToDataBaseService { @Override public Boolean writeMessage(InChatMessage message) { System.out.println(message.toString()); return true; }}登录校验与群聊消息此接口没有做过多的修改public class VerifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { return true; } @Override public JSONArray getArrayByGroupId(String groupId) { JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}服务端发送通知消息枚举类此接口具有Demo模板,用户需要继承InChat框架的FromServerService接口,同时该接口注释也有实例demo,我们需要实现一个自定义的枚举,你可以这样写:public enum FromServerServiceImpl implements FromServerService { //你可以自定义自己的系统消息,请以Integer-String的形式 TYPE1(1,"【系统通知】您的账号存在异常,请注意安全保密信息。"), TYPE2(2,"【系统通知】恭喜您连续登录超过5天,奖励5积分。"); private Integer code; private String message; FromServerServiceImpl(Integer code, String message){ this.code = code; this.message = message; } public Integer getCode() { return code; } //实现接口的方法,遍历本枚举的code,获取对应的消息,作为系统消息发送 public String findByCode(Object code) { Integer codes = (Integer)code; for (FromServerServiceImpl item: FromServerServiceImpl.values()) { if (item.code == codes){ return item.message; } } return null; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }}启动项目1.1.2版本的启动项目变得异常的简单,你只需要配置启动的配置工厂即可。public class application { public static void main(String[] args) { //配置你的自定义配置 ConfigFactory.initNetty = new MyInit(); //配置校验类 ConfigFactory.inChatVerifyService = new VerifyServiceImpl(); //配置消息接收处理类 ConfigFactory.inChatToDataBaseService = new DataBaseServiceImpl(); //配置服务端系统消息枚举,这里的值无所谓 TYPE1或者TYPE2或者TYPEN均可以 ConfigFactory.fromServerService = FromServerServiceImpl.TYPE1; //启动InChat InitServer.open(); }}项目效果启动成功DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8090】当聊天连接未注册情况下,客户端自动断开后,服务会自动包对应的异常 INFO - [Handler:channelInactive]/192.168.56.1:8090关闭成功ERROR - [捕获异常:NotFindLoginChannlException]-[Handler:channelInactive] 关闭未正常注册链接!原先的自我发送,点对点发送,群聊均与原来一样原先的接口说明可以看上一版本: v1.1.0-alpha版本使用说明新功能添加 HTTP新增HTTP接口三个,在你启动Inchat的时候,默认启动,对于你的其他web API并无任何影响,它是一个IM的辅助作用。本版本不支持用户自定义相关的InChat HTTP接口获取在线用户数地址:[ip:端口]/get_size GET返回值{ “code”: 200, “data”: { “online”: 1,//当前在线数 “time”: “Jan 3, 2019 10:06:45 PM”//查询时间 }}获取在线用户标识地址:[ip:端口]/get_list GET返回值{ “code”: 200, “data”: { //返回在线用户列表 “tokens”: [ “1111” ] }}根据用户标签,发送系统指定消息地址:[ip:端口]/send_from_server POST参数:token(你可以从get_list中得到在线用户标签)、value(你在系统中添加枚举的code值,这里不接受字符串)返回值{ “code”: 400, “data”: { “message”: “通知发送成功” }}(有个小BUG,返回值code应该是200)关于前端InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,大家可以直接来这个项目下获取前端页面,或者直接访问这个地址:https://github.com/UncleCatMy…对于这个前端页面,我们需要更改一下IP地址。运行调试项目接下来直接启动后端项目,当我们看到以下的信息,则项目启动成功。 INFO - 服务端启动成功【192.168.1.121:8090】这里的IP需要更换以下读者启动后的IP地址。接着直接用浏览器打开chat.html的页面即可,关于js的方法,大家可以看看InChatV1.1.0版本使用说明。运行效果已经提前展示啦!公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

January 4, 2019 · 2 min · jiezi

为自己搭建一个分布式 IM(即时通讯) 系统

前言大家新年快乐!新的一年第一篇技术文章希望开个好头,所以元旦三天我也没怎么闲着,希望给大家带来一篇比较感兴趣的干货内容。老读者应该还记得我在去年国庆节前分享过一篇《设计一个百万级的消息推送系统》;虽然我在文中有贴一些伪代码,依然有些朋友希望能直接分享一些可以运行的源码;这么久了是时候把坑填上了。目录结构:本文较长,高能预警;带好瓜子板凳。于是在之前的基础上我完善了一些内容,先来看看这个项目的介绍吧:CIM(CROSS-IM) 一款面向开发者的 IM(即时通讯)系统;同时提供了一些组件帮助开发者构建一款属于自己可水平扩展的 IM 。借助 CIM 你可以实现以下需求:IM 即时通讯系统。适用于 APP 的消息推送中间件。IOT 海量连接场景中的消息透传中间件。完整源码托管在 GitHub : https://github.com/crossoverJie/cim演示本次主要涉及到 IM 即时通讯,所以特地录了两段视频演示(群聊、私聊)。点击下方链接可以查看视频版 Demo。YouTubeBilibili群聊 私聊群聊 私聊也在公网部署了一套演示环境,想要试一试的可以联系我加入内测群获取账号一起尬聊????。架构设计下面来看看具体的架构设计。CIM 中的各个组件均采用 SpringBoot 构建。采用 Netty + Google Protocol Buffer 构建底层通信。Redis 存放各个客户端的路由信息、账号信息、在线状态等。Zookeeper 用于 IM-server 服务的注册与发现。整体主要由以下模块组成:cim-serverIM 服务端;用于接收 client 连接、消息透传、消息推送等功能。支持集群部署。cim-forward-route消息路由服务器;用于处理消息路由、消息转发、用户登录、用户下线以及一些运营工具(获取在线用户数等)。cim-clientIM 客户端;给用户使用的消息终端,一个命令即可启动并向其他人发起通讯(群聊、私聊);同时内置了一些常用命令方便使用。流程图整体的流程也比较简单,流程图如下:客户端向 route 发起登录。登录成功从 Zookeeper 中选择可用 IM-server 返回给客户端,并保存登录、路由信息到 Redis。客户端向 IM-server 发起长连接,成功后保持心跳。客户端下线时通过 route 清除状态信息。所以当我们自己部署时需要以下步骤:搭建基础中间件 Redis、Zookeeper。部署 cim-server,这是真正的 IM 服务器,为了满足性能需求所以支持水平扩展,只需要注册到同一个 Zookeeper 即可。部署 cim-forward-route,这是路由服务器,所有的消息都需要经过它。由于它是无状态的,所以也可以利用 Nginx 代理提高可用性。cim-client 真正面向用户的客户端;启动之后会自动连接 IM 服务器便可以在控制台收发消息了。更多使用介绍可以参考快速启动。详细设计接下来重点看看具体的实现,比如群聊、私聊消息如何流转;IM 服务端负载均衡;服务如何注册发现等等。IM 服务端先来看看服务端;主要是实现客户端上下线、消息下发等功能。首先是服务启动:由于是在 SpringBoot 中搭建的,所以在应用启动时需要启动 Netty 服务。从 pipline 中可以看出使用了 Protobuf 的编解码(具体报文在客户端中分析)。注册发现需要满足 IM 服务端的水平扩展需求,所以 cim-server 是需要将自身数据发布到注册中心的。这里参考之前分享的《搞定服务注册与发现》有具体介绍。所以在应用启动成功后需要将自身数据注册到 Zookeeper 中。最主要的目的就是将当前应用的 ip + cim-server-port+ http-port 注册上去。上图是我在演示环境中注册的两个 cim-server 实例(由于在一台服务器,所以只是端口不同)。这样在客户端(监听这个 Zookeeper 节点)就能实时的知道目前可用的服务信息。登录当客户端请求 cim-forward-route 中的登录接口(详见下文)做完业务验证(就相当于日常登录其他网站一样)之后,客户端会向服务端发起一个长连接,如之前的流程所示:这时客户端会发送一个特殊报文,表明当前是登录信息。服务端收到后就需要将该客户端的 userID 和当前 Channel 通道关系保存起来。同时也缓存了用户的信息,也就是 userID 和 用户名。离线当客户端断线后也需要将刚才缓存的信息清除掉。同时也需要调用 route 接口清除相关信息(具体接口看下文)。IM 路由从架构图中可以看出,路由层是非常重要的一环;它提供了一系列的 HTTP 服务承接了客户端和服务端。目前主要是以下几个接口。注册接口由于每一个客户端都是需要登录才能使用的,所以第一步自然是注册。这里就设计的比较简单,直接利用 Redis 来存储用户信息;用户信息也只有 ID 和 userName 而已。只是为了方便查询在 Redis 中的 KV 又反过来存储了一份 VK,这样 ID 和 userName 都必须唯一。登录接口这里的登录和 cim-server 中的登录不一样,具有业务性质,登录成功之后需要判断是否是重复登录(一个用户只能运行一个客户端)。登录成功后需要从 Zookeeper 中获取服务列表(cim-server)并根据某种算法选择一台服务返回给客户端。登录成功之后还需要保存路由信息,也就是当前用户分配的服务实例保存到 Redis 中。为了实现只能一个用户登录,使用了 Redis 中的 set 来保存登录信息;利用 userID 作为 key ,重复的登录就会写入失败。类似于 Java 中的 HashSet,只能去重保存。获取一台可用的路由实例也比较简单:先从 Zookeeper 获取所有的服务实例做一个内部缓存。轮询选择一台服务器(目前只有这一种算法,后续会新增)。当然要获取 Zookeeper 中的服务实例前自然是需要监听 cim-server 之前注册上去的那个节点。具体代码如下:也是在应用启动之后监听 Zookeeper 中的路由节点,一旦发生变化就会更新内部缓存。这里使用的是 Guava 的 cache,它基于 ConcurrentHashMap,所以可以保证清除、新增缓存的原子性。群聊接口这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其余所有客户端都能收到!流程肯定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 SessionSocketHolder 中遍历所有 Channel(通道)然后下发消息即可。服务端是单机倒也可以,但现在是集群设计。所以所有的客户端会根据之前的轮询算法分配到不同的 cim-server 实例中。因此就需要路由层来发挥作用了。路由接口收到消息后首先遍历出所有的客户端和服务实例的关系。路由关系在 Redis 中的存放如下:由于 Redis 单线程的特质,当数据量大时;一旦使用 keys 匹配所有 cim-route:* 数据,会导致 Redis 不能处理其他请求。所以这里改为使用 scan 命令来遍历所有的 cim-route:*。接着会挨个调用每个客户端所在的服务端的 HTTP 接口用于推送消息。在 cim-server 中的实现如下:cim-server 收到消息后会在内部缓存中查询该 userID 的通道,接着只需要发消息即可。在线用户接口这是一个辅助接口,可以查询出当前在线用户信息。实现也很简单,也就是查询之前保存 ”用户登录状态的那个去重 set “即可。私聊接口之所以说获取在线用户是一个辅助接口,其实就是用于辅助私聊使用的。一般我们使用私聊的前提肯定得知道当前哪些用户在线,接着你才会知道你要和谁进行私聊。类似于这样:在我们这个场景中,私聊的前提就是需要获得在线用户的 userID。所以私聊接口在收到消息后需要查询到接收者所在的 cim-server 实例信息,后续的步骤就和群聊一致了。调用接收者所在实例的 HTTP 接口下发信息。只是群聊是遍历所有的在线用户,私聊只发送一个的区别。下线接口一旦客户端下线,我们就需要将之前存放在 Redis 中的一些信息删除掉(路由信息、登录状态)。IM 客户端客户端中的一些逻辑其实在上文已经谈到一些了。登录第一步也就是登录,需要在启动时调用 route 的登录接口,获得 cim-server 信息再创建连接。登录过程中 route 接口会判断是否为重复登录,重复登录则会直接退出程序。接下来是利用 route 接口返回的 cim-server 实例信息(ip+port)创建连接。最后一步就是发送一个登录标志的信息到服务端,让它保持客户端和 Channel 的关系。自定义协议上文提到的一些登录报文、真正的消息报文这些其实都是在我们自定义协议中可以区别出来的。由于是使用 Google Protocol Buffer 编解码,所以先看看原始格式。其实这个协议中目前一共就三个字段:requestId 可以理解为 userId。reqMsg 就是真正的消息。type 也就是上文提到的消息类别。目前主要是三种类型,分别对应不同的业务:心跳为了保持客户端和服务端的连接,每隔一段时间没有发送消息都需要自动的发送心跳。目前的策略是每隔一分钟就是发送一个心跳包到服务端:这样服务端每隔一分钟没有收到业务消息时就会收到 ping 的心跳包:内置命令客户端也内置了一些基本命令来方便使用。命令描述:q退出客户端:olu获取所有在线用户信息:all获取所有命令:更多命令正在开发中。。比如输入 :q 就会退出客户端,同时会关闭一些系统资源。当输入 :olu(onlineUser 的简写)就会去调用 route 的获取所有在线用户接口。群聊群聊的使用非常简单,只需要在控制台输入消息回车即可。这时会去调用 route 的群聊接口。私聊私聊也是同理,但前提是需要触发关键字;使用 userId;;消息内容 这样的格式才会给某个用户发送消息,所以一般都需要先使用 :olu 命令获取所以在线用户才方便使用。消息回调为了满足一些定制需求,比如消息需要保存之类的。所以在客户端收到消息之后会回调一个接口,在这个接口中可以自定义实现。因此先创建了一个 caller 的 bean,这个 bean 中包含了一个 CustomMsgHandleListener 接口,需要自行处理只需要实现此接口即可。自定义界面由于我自己不怎么会写界面,但保不准有其他大牛会写。所以客户端中的群聊、私聊、获取在线用户、消息回调等业务(以及之后的业务)都是以接口形式提供。也方便后面做页面集成,只需要调这些接口就行了;具体实现不用怎么关心。总结cim 目前只是第一版,BUG 多,功能少(只拉了几个群友做了测试);不过后续还会接着完善,至少这一版会给那些没有相关经验的朋友带来一些思路。后续计划:完整源码:https://github.com/crossoverJie/cim如果这篇对你有所帮助还请不吝转发。 ...

January 2, 2019 · 2 min · jiezi

纯golang im即时通讯系统(支持分布式)

简介纯go实现的im即时通讯系统,各层可单独部署,之间通过rpc通讯,支持集群,github地址 https://github.com/Terry-Ye/im, 学习于goim, 总分三层,comet(用户连接层),可以直接部署多个节点,每个节点保证serverId 唯一,在配置文件comet.tomllogic(业务逻辑层),无状态,各层通过rpc通讯,容易扩展,支持http接口来接收消息job(任务推送层)通过redsi 订阅发布功能进行推送到comet层。时序图以下Comet 层,Logic 层,Job层都可以灵活扩展机器特性分布式,可拓扑的架构支持单个,房间推送心跳支持(gorilla/websocket内置)基于redis 做消息推送轻量级持续迭代…部署安装go get -u github.com/Terry-Ye/immv $GOPATH/src/github.com/Terry-Ye/im $GOPATH/src/imcd $GOPATH/src/imgo get ./…golang.org 包拉不下来的情况,例package golang.org/x/net/ipv4: unrecognized import path “golang.org/x/net/ipv4” (https fetch: Get https://golang.org/x/net/ipv4?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)从github 拉下来,再移动位置git clone https://github.com/golang/net.gitmkdir -p golang.org/x/mv net $GOPATH/src/golang.org/x/部署im安装comet、logic、job模块cd $GOPATH/src/im/cometgo installcd ../logic/go installcd ../jobgo installnohup $GOPATH/bin/logic -d $GOPATH/src/im/logic/ 2>&1 > /data/log/im/logic.log &nohup $GOPATH/bin/comet -d $GOPATH/src/im/comet/ 2>&1 > /data/log/im/comet.log &nohup $GOPATH/bin/job -d $GOPATH/src/im/job/ 2>&1 > /data/log/im/job.log &im_api 是im系统中使用的接口,需要像demo那样整体跑起来需要完整的部署部署注意事项部署服务器注意防火墙是否开放对应的端口(本地不需要,具体需要的端口在各层的配置文件)demo聊天室:http://www.texixi.com:1999/使用的包log: github.com/sirupsen/logrusrpc: github.com/smallnest/rpcxwebsocket: github.com/gorilla/websocket配置文件:github.com/spf13/viper后续计划在线列表支持wss聊天机器人

December 19, 2018 · 1 min · jiezi

InChat一版,仅仅两个接口实现自己的IM系统(可兼容)

InChat 一个IM通讯框架一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架。(核心底层Netty)Github:InChat版本目标:完成基本的消息通讯(仅支持文本消息),离线消息存储,历史消息查询,一对一聊天、自我聊天、群聊等。你可以使用InChat,快速搭建一个基于SpringBoot的IM项目,而且没有任何硬性要求,你完全可以兼容自己原有的项目。v1.0.0版本使用说明关于InChat的Maven依赖fastjson 》 1.2.53gson 》 2.8.5netty 》 4.1.32.Finalcommons-lang 》 3.5aspectj 》 1.9.2lombok 》 1.18.4spring-boot 》 2.0.2.RELEASEspring-boot-starter-websocket关于一版依旧使用SpringBoot的环境,同时为应用注入了web环境,引入InChat依赖包后,对于SpringBoot相关的web可以无需引入,同时请注意相关版本的兼容性。引入InChat默认可以自动运行web环境。创建项目创建一个空的Maven项目,并引入InChatMaven包,(注意,请不要使用与本项目相同的包目录)。可能你只需要这样的Maven依赖即可<dependencies> <dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.0-alpha</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>注入InChat的项目到自身项目中你可能需要在你的项目上进行报扫描@SpringBootApplication@ComponentScan({“com.inchat”}) //你的demo包目录@ComponentScan({“com.github.unclecatmyself”}) //InChat的包目录 –请将InChat的放到最下面public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}对接InChat的接口与实现这次你仅需写两个实现接口即可啦!!!@Servicepublic class ToDataBaseServiceImpl implements InChatToDataBaseService{ @Override public Boolean writeMapToDB(Map<String, Object> maps) { //异步写入数据库 System.out.println(maps.toString()); return true; }}这个接口是每个人通讯的信息,InChat自带实现了异步的数据外抛得接口InChatToDataBaseService,目前一版只有一个方法,就是上面得writeMapToDB,你仅需要map的内容转为对应的对象(一版还没提供对应的转换类,下一版对提供),并将数据存入自己喜欢的数据库中。如果数据并发大,也可以先放到MQ中,再写入数据库。@Servicepublic class verifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { //登录校验 return true; } @Override public JSONArray getArrayByGroupId(String groupId) { //根据群聊id获取对应的群聊人员ID JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}这个接口是InChat的校验层实现,对于Token的校验就是,verifyToken,websocket链接的时候,你将在初次做登录校验,你可以将从InChat拿到的websocket传过来的Token,你可以与自己的用户登录的token做校验,返回true,则用户成功链接InChat。关于getArrayByGroupId,目前是否应该放在这个接口中还有待确定,不过目前一版暂时这样,你可以去数据库中查询对应的群聊id所对应的人员ID(或Token),并返回对应的JSONArray即可啦。自定义配置InChat参数这个你可以直接在application中按照自己的意思配置,不过你最好先了解netty启动项目接着启动项目即可啦当你看到这个日志就标志着Inchat搭建成功了!!!2018-12-14 10:29:09.269 INFO 4920 — [ BOSS_1] c.g.u.bootstrap.NettyBootstrapServer : 服务端启动成功【192.168.1.121:8090】关于前端这里你可以来到InChat的Front-End-Testing文档夹中的chat.html。你可以直接使用,你进需要修改对应的对接IP即可。关于前端的js暂时还是模板关于登录你会看到chat.html中的登录按钮对应的jsfunction send(value) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { var message = { type: “login”, //与InChat对应的 不可修改 token: “1111” } socket.send(JSON.stringify(message)); } else { alert(“连接没有开启.”); }}本demo,默认登录的Token是“1111”,关于用户校验则直接返回true即可。登录成功,返回以下内容。(不需要显示给用户看){“success”:“true”,“type”:“login”}InChat不会有登录记录发送给自己你会看到chat.html中的登录按钮对应的jsfunction sendToMe(value) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { var message = { type: “sendMe”, //与InChat对应的 不可修改 value: value, //发送的内容 token: “1111” //发送用户的token } socket.send(JSON.stringify(message)); } else { alert(“连接没有开启.”); }}发送成功,InChat返回内容.(你仅需将value显示到前端即可){“type”:“sendMe”,“value”:“发送给自己的内容”}InChat消息记录,你将在异步消息中接受到InChat传递给你的用户通讯消息,你可以进行对应的入库操作{“time”:“2018-12-14 10:56:24”,“type”:“sendMe”,“value”:“发送给自己的内容”,“token”:“1111”}发送给某人你会看到chat.html中的登录按钮对应的jsfunction sendToOne(value) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { var message = { type : “sendTo”, //与InChat对应的 不可修改 token : “1111”, //发送用户Token value: value, //发送内容 one: “2222”, //接受用户Token(唯一标识) } socket.send(JSON.stringify(message)); } else { alert(“连接没有开启.”); }}发送成功,接受的用户是否登录,你都能接受到返回信息。(value应用于自己界面展示){“one”:“2222”,“type”:“sendTo”,“value”:“发送给朋友的内容”}但是用户那边就不一样了。登录正常在线。{“from”:“1111”,“type”:“sendTo”,“value”:“发送给朋友的内容”}离线接受不到信息InChat异步消息推送,你可以看到两种在线: {“one”:“2222”,“time”:“2018-12-14 11:01:36”,“type”:“sendTo”,“value”:“发送给朋友的内容”,“token”:“1111”}离线: {“one”:“2222”,“time”:“2018-12-14 10:59:04”,“on_online”:“2222”,“type”:“sendTo”,“value”:“发送给朋友的内容”,“token”:“1111”}如果出现用户发送给用户的状态是离线的,则会在消息多出on_online的字段,该字段的内容就是离线用户的Token,你可以针对性的数据入库,并在用户上线的时候,读写信息的时候,有一个未读消息的状态。发送群聊你会看到chat.html中的登录按钮对应的jsfunction sendGroup(value) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { var message = { type: “sendGroup”, //与InChat对应的 不可修改 groupId: “2”, //群聊ID token: “1111”, //发送用户的Token value: value //发送的消息 } socket.send(JSON.stringify(message)); } else { alert(“连接没有开启.”); }}发送成功,本人将接受到消息{“groupId”:“2”,“from”:“1111”,“type”:“sendGroup”,“value”:“大家明天一起去唱K吧”}群组中有些人在线接受、离线不接受在线:{“groupId”:“2”,“from”:“1111”,“type”:“sendGroup”,“value”:“大家明天一起去唱K吧”}InChat异步消息入库,群组只会异步给你一个消息,你可以看到on_online中,3333用户是没有接受到信息的,所以你可以在他上线发送未读消息。{“groupId”:“2”,“time”:“2018-12-14 11:09:17”,“on_online”:[“3333”],“type”:“sendGroup”,“value”:“大家明天一起去唱K吧”,“token”:“1111”}关于数据库设计当前一版不会固定大家的数据库设计,大家可以自己自由设计,同时搭上自己的项目,构建一个附带IM的自项目。前端效果发送人接收人 ...

December 14, 2018 · 2 min · jiezi