环信即时通信服务器除了环信之外,还有推荐的品牌吗

环信CTO:解析即时通讯云平台技术难点
移动互联网时代,用户对于移动应用的各种功能的要求正变得越来越高,而即时通讯就是其中之一。环信长期致力于为用户提供优质的即时通讯云服务,帮助应用开发者提供个性化的即时通讯客户体验,在即时通讯领域拥有丰富的经验与过硬的技术实力。为了进一步了解国内即时通讯服务的发展,以及环信即时通讯云服务的功能与特点,我们特意邀请了环信CTO马晓宇进行了专访。InfoQ:您能否谈谈环信IM服务和客户自行搭建IM服务器的区别在什么地方,而环信的服务相比之下又具有什么样的优势?马晓宇:我认为主要有三个区别:时间、系统容量、以及成本。从时间来说,移动应用领域竞争激烈,产品生命周期即使不是以天为单位,那至少也是以周或以月为单位。用户如果自行研发一个即时通讯平台,平均需要几个月的时间。另外还要考虑到这个产品的发展问题,比如之前比较流行的匿名社交、匿名聊天室、匿名群组,如果用户自行开发的话同样需要几个月的时间,这对于一个移动应用来说显然是得不偿失的。从系统容量上来看,如果起步阶段只有几万、几十万用户的话,那么自建IM服务器还是比较容易实现的,而且其中的一些技术还可以从其他地方借鉴。但是如果当用户数量达到几百万,甚至上千万的时候,自建服务的难度还是比较大的。以QQ为例,QQ的发展从几十万用户,到上百万用户,再到上千万用户的过程当中,每个主要的版本都是彻底推倒重做的,如果一个移动应用真正能成功的话,真正在短时间内能达到几百万日活的话,就一定会对系统容量,对技术造成比较大的挑战,这并不是三、五个人在短时间内就可以完成的工作。第三个区别,就是开发成本。成本又可以分为两个部分,一个是开发成本,包括技术团队成员的人工及工资成本了。当移动应用的用户量增长到比较大的时候,更大一部分的费用就体现在运维成本上了。以每百万长连接为一个基准,整个IM服务的资金成本大概从几万到十几万都有可能,如果用户考虑自建的话,很可能会比环信所提供的专业即时通讯服务的成本还要高很多。
InfoQ:能否从云服务的角度来谈一谈,现在很多现象级的应用可能在一段时间之内,它对于整个系统的负载可能会很严重,但是当用户数逐渐减少之后,又会出现计算资源的浪费的情况。那么这对于自行搭建IM服务器的用户来说,这又意味着什么呢?马晓宇:这个问题首先要从我们服务商的角度来说,环信在云端有着各种各样的监控,如果突然有用户在给系统大量发消息的话,环信会以较快的速度来增加服务器来去完成消息的发送工作。推送也是一样,如果说有些消息给苹果用户推送不过来的话,环信同样也会增加服务器,环信的系统可以在较快的时间内完成动态调整,分钟级的部署现在也已经可以实现。另一方面,不同的时段,用户的活跃度也是不同的。如果用户白天比较活跃,或者是晚间比较活跃的话,那么如果使用云服务,单用户的成本以及整体上的成本就都会降低很多,但如果自建服务器的话,就必须要按照峰值时间的需求进行准备,那么峰值范围以外的时间段整个系统都将闲置,就会造成很大的资源浪费。
InfoQ:能不能谈一谈,建立即时通讯云平台需要跨越的技术障碍是哪些?产品上线之后的运营过程中还会遇到哪些挑战?环信是怎样解决这些问题的?马晓宇:在环信的创业探索过程当中,确实遇到过一些比较大的技术挑战,首先需要解决的就是后台的大数据高并发。环信每天需要处理的消息过亿,还要处理千百万的长连接。环信的具体作法是做系统拆分,将系统分成与用户长连接相关的连接服务器和无状态服务器两种,其中使用了Erlang语言,本身支持高并发,同时还内置有跨节点的分布式数据库,可以进行session同步。对于用户管理、推送等其他无状态服务来说,环信搭建了基于Java的后台服务,允许进行动态调配。另外环信主要是一个实时系统,它将发送用户消息等实时业务与其他非实时的后台业务进行解耦,那些像数据统计等不影响前台用户体验的业务会进入消息队列,即便这些非实时业务出现问题,也不会影响到实时业务的运行。很多应用每秒钟都会有几万甚至几十万的用户同时登录,在登录的过程中还要获取用户的基本信息以及好友列表,而环信为了提升登录的用户体验,缩短登录时间,因此为最近登录的用户存储了大量的后台缓存,而不用每次登录都需要访问数据库。从数据存储角度来说,环信后台主要的数据存储点基本上都是基于Casssandra数据库的,利用NoSQL来支持大规模的数据处理。另外对于移动端,由于应用在移动设备里需要长期运行,所以环信也对内存占用进行了优化,尽量减少内存的占用量。比如用户有很多的消息对话,实际上其中只有最新的那一段对话是加载在内存里的,其他部分都已经做了置换以便节省内存。消息流量也是一样,环信对其进行压缩处理,在节省流量的同时,也优化了设备耗电量。此外,用户的集成体验对于环信来说也是需要不断完善的。目前的解决思路是,为用户提供一个开源的UI模板,里面已经演示了如何调度不同的SDK,如何登录、发消息、创建群组等等,便于用户将环信SDK更容易的集成到应用里,只需要直接添加代码就可以。另外一方面,环信还发现有些用户,可能更希望直接把UI整个拿过去使用,所以环信在开发UI的控件封装,将基本的聊天、单聊群聊做成一个控件,这样用户就不需要再去做一个新的对话窗口。从运维角度上来看,环信同样采取了一些优化手段,比如一个API只能处在的一个合理的调用范围内,这样就可以避免某个用户进行大量频繁的异常调用。再比如某些用户需要群发消息,但对发送速度不做要求,这样的话系统就可以将这些消息放到慢速队列中,以避免影响快速队列以及真个系统的运行速度。实际上,无论准备多么充分,意外还是会不时发生,所以环信也比较看重监控系统。当主要消息队列出现拥堵的时候,监控系统在报警的同时还能检测出问题出在什么地方,将会影响哪些用户的具体哪些操作。同时,环信系统在主要的连接点上都有降级开关,保证基本服务的运行不会受到影响。环信还会每隔一两个月进行一次降级演习,模拟缓存、数据库、登录等故障的出现,以制定应急预案,这样在实际运行时出现相应问题时就容易处理了。
InfoQ:环信提供的是一种PaaS服务,对于云服务的用户来说,最关心的肯定是安全这个话题。那么环信在安全方面,以及保护用户隐私数据方面都做了哪些具体措施呢?马晓宇:首先用户数据之间是隔离的,用户的数据是基于Token访问的,用户不能使用一个应用的身份验证去访问另一个应用里的数据。同样地,在同一个应用里面,环信也设置了两个不同访问权限,一个是管理员权限,而另一个则是普通用户权限。在普通用户权限下,是看不到管理员权限下的一些数据的。从运维操作的角度来说,环信制定了严格的操作规范,后台工程师对数据库或者接口的访问权限是有严格限制的,只有特定的人才拥有相应的权限,所有的服务器登录操作都将记录到日志里。另外,环信考虑过第三方服务的因素。由于环信本身是多租户系统,当与第三方云存储服务商合作的时候,就不能像一些个人云服务那样,所有的数据都是存储在一起,使用一个账号就能访问所有的数据。环信同样要求第三方云存储也要支持多租户系统,环信也会与合作伙伴一起在这方面进行并行开发与集成的工作。简单的数据访问上,环信同样采取了一些保护措施。比如用户上传图片是通过HTTPS通道的。每个图片上传之后,环信会对应生成一个身份验证进行绑定,也就是说要想下载这张图片必须同时拥有正确的URL和身份验证,缺一不可。
InfoQ:一家企业的技术理念同样是企业文化的一部分,同时也会影响企业未来的走向。您能不能谈一谈环信的企业技术文化?马晓宇:因为经历的原因,环信的几位创始人都曾进行过大量的开源项目开发工作。另外环信系统本身在消息对列、数据库等方面都在使用基础的开源服务,因此可以说,环信与环信的技术人员一直对开源文化情有独钟。环信首先是希望做出一个能为用户提供价值的、技术上成功的开源架构软件系统。环信认为,在解决很多技术挑战过程中,肯定能够分析总结出一种通用的技术方案,最后形成一套完备的基本系统,再将这个系统开放给更多人,让更多的应用开发者受益。即使这一过程可能需要耗费环信几年的时间,但这对于热爱开源的技术人来说这并不算什么。在团队管理上,环信也同样希望能采用一个开源软件开发的组织结构,逐步形成一个自管理的技术团队。比如环信的技术人员可以远程办公,自由安排工作时间,或者每年只有有两次到北京来和团队其他人员一起开会,平常直接以邮件等方式沟通,完全以开源软件的方法来维持整个团队的运作。这样的工作方式能够让员工的工作与生活联系的更紧密,同时也能激发员工的创造性思维。
InfoQ:您认为今年移动端云计算市场会出什么样的一些变化?哪些因素推动了这个移动端云计算市场变化的?马晓宇:去年各种针对个人的服务比较多,而今年开始针对企业的服务会不断增加。尤其是企业SaaS服务,现在已经涌现出一批SaaS服务商正在进入市场,涉及的领域包括存储、安全、人力资源管理,甚至是统计表单等等。企业的SaaS服务将成为主流趋势,包括环信即将正式推出的移动客服在内,都是完全针对企业的一种SaaS服务。
InfoQ:从环信现有的用户来看,您认为哪些行业或者类型的应用会出现新的增长点呢?马晓宇:从用户数量的增长速度来看,最热门的基本都是图片类的应用,这些应用不仅仅是用户增长快,而且用户的活跃度也非常高。另外,一些特定的垂直领域应用的用户粘性也比较大,比如动漫类的应用,当它增加了即时通讯功能以后,一天的消息流量就到了上千万条,这也证明此类垂直领域的应用还是比较受欢迎的。
InfoQ:您认为现在国内的即时通讯云市场与国外相比还存在哪些不足?需要在哪些方面进行改进呢?马晓宇:国内现在的相关厂商不少,国外也有像Layer这样的公司。以Layer来举例,它的定价基本上是环信的10倍,这一方面说明可能美国用户在付费上的意愿比较高,而另一方面,也证明国内的市场竞争已经进入了白热化的阶段。但总体上来说,因为市场的规模比较大,所以基本上还是处于良性竞争的状态。在国内与国外厂商的差距上来看,目前还是有一些的,无论是商业环境还是人力资源上,环信以及国内厂商都处于劣势,尽管国外厂商技术人员的总数并不多,但如果每一个人都是精英翘楚的话就会变得很不一样了。因此技术人员的储备,进一步吸引世界级的工程师,也是环信接下来的方向,只有在人才上有所突破,才有可能真正走到国外去竞争更大的市场。
给InfoQ中文站投稿或者参与内容翻译工作,博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。欲了解更多环信资讯,欢迎访问环信专区。
> 本站内容系网友提交或本网编辑转载,其目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除内容!
随着移动互联网的发展,即时通讯.移动客服已经成为了很多移动应用的必备功能,环信作为新晋移动即时通讯PaaS平台服务商,凭借着近期刚刚上线的跨平台移动端客服产品吸引了大量应用开发者的关注.为了进一步了解移动客服产品的发展现状,InfoQ专门对环信联合创始人及CEO刘俊彦进行了专访.InfoQ:环信成立已经有两年的时间,能聊一下这两年环信的整体发展吗?刘俊彦:环 ...
1. 引言 即时通信系统是随着互联网的出现而兴起的新型通信手段.即时通信已经成为继电话.电子邮件之后的第三种现代通信方式.即时通信系统作为一种低成本的.集成多种沟通方式 (如即时消息.视音频,文件传输)为企业进行内外联系.提高工作效率.降低沟通成本.拓展商业机会等方面发挥了巨大的作用. 目前许多企业的工作人员,利用QQ.MSN.GTalk 等即时通信软件,进 ...
#推荐活动# #线下沙龙# 明天下午在IC咖啡 —— &APP邂逅即时通讯云,让你的手机APP聊起来&, /Y8sYo5
由于使用习惯,Linux在中国受欢迎程度远不如windows,相应的软件也比较少,尤其是音视频类的软件,但是,这并不代表就完全没有.下面介绍一款强大的音视频即时通讯平台给大家,它就是--Anychat for Linux SDK. AnyChat是一套跨平台的音.视频即时通讯解决方案,基于先进的H.264视频编码标准.AAC音频编码标准与P2P技术,支持Wi ...
由于即时通讯系统的复杂性和对服务器稳定性的很高要求,一般即时通讯系统开发至少需要1年左右的时间,而这还只是测试版,离&稳定&还有一定距离,而这时匆匆上马的不稳定的系统会让你失去用户,您也不可能召集上万台电脑进行测试,那样的成本本身已经相当高,而无论如何测试,也仍然只是测试系统.而我们的系统,是经过几年的正式使用,经受过实际高用户量稳定运行 ...
最近研究即时通讯,发现自己使用XMPP,用openfire或者enjebber后台,会很复杂,要涉及到二次开发后台,要用java或者enjebber专用语言,实在无法开展. 后来找了下其他资料,发现现在国内的即时通讯云有好几家,功能都挺不错的,很强大,完爆自己后台,还省事,不花钱,挺好的,毫不犹疑的学习起来了他们的SDK. 我综合各方面最后选择了环信的产品. ...
12月19日,ArchSummit北京2014大会在北京国际会议中心拉开帷幕.InfoQ和阿里云合作推出了&云平台技术全景剖析&专场,有幸邀请到来自阿里云的黄湘龙.张献涛.朱照远.占超群等4位技术大咖,跟到场的技术人员分享了云架构背后的核心技术,云产品的发展历程和思考等干货,现场气氛非常火爆.很多听众积极提问,跟到场专家请教技术问题:有些 ...
ArchSummit北京2014大会即将于日~20日在北京国际会议中心召开.云计算是近年IT领域最受关注的技术之一,自然也是ArchSummit北京2014大会重点关注的议题.云平台背后用到了哪些技术?云产品是如何架构的?这是很多架构师都很关心的话题.了解云平台背后的技术和架构,对于更好地应用云产品也很有借鉴意义.为此,我们和阿里云合作, ...3款IM云服务产品对比 即时通讯云产品横向评测
3款IM云服务产品对比 即时通讯云产品横向评测
&&& 2013年开始,各类APP开发者和公司都意识到需顺应应用社交化浪潮,而提供IM即时通讯云服务的工具平台也如雨后春笋般冒了出来。IM云服务可以实现各类APP之间的无缝沟通,为用户提供基于电商、金融、医疗和O2O等各类场景的社交。同时IM技术也很快跨越了文字交流的边界,发展到现在语音,视频,会议,白板等众多功能。终端也从PC、WEB延展到,平板上。作为用户的开发者们该如何选择云服务平台呢?下面笔者就列举网易云信、环信和融云三款产品做一个深度测评供大家参考。
&&& 一、产品功能
支持(200-2000人)
支持(500-2000人)
表情贴纸消息
阅后即焚消息
支持,高清视频
同时支持P2P和Relay
支持,高清语音
支持,噪音较大
支持,噪音较大
文件多重备份
短信验证码
直连运营商短信网关
服务端消息历史记录接口
支持,有分页
支持,有分页
支持,按小时,只保存3天。不能实时拿,要按整点。如当天10点的数据要11点以后才能拿。
服务端聊天室 业务接口
iOS、Android、Windows(PC)、Web
iOS、 Android、 Web
iOS、 Android、 Web
&&& 从功能上看三家大同小异,但网易云信拥有更多的独家优势。网易云信提供了包括安卓保活、教学白板;环信是IM+移动客服等,融云是IM+公众服务等,目前只有云信支持PC端。
&&& 在音频效果方面,除了融云,其他两家都支持实时音视频。经测试发现,网易云信的降噪处理比较好,其他两家在接收方有较为明显的杂音。
&&& 二、稳定性
&&& 稳定性方面通过各家厂商公开的硬件数据和软实力进行了一个对比。
&&& 自建机房意味着拥有专业成熟的运维团队、更强的突发事件应对能力、更高效的突发事件响应速度,同时也意味着能提供更稳定的服务。目前看来只有网易云信拥有自建机房,环信是托管云机房,融云是否自建未知,从品牌优势、技术优势以及稳定性方面,小编愿意为网易云信投上一票。
&&& 三、接入体验(以web端为例)
&&& 开发手册对比:
&&& 开发手册示例代码对比:
&&& 浏览器兼容:
&&& 接入流程对比:
&&& 在用户体验被万分重视的今天,作为开放平台类云计算服务,开发者的接入体验就是根本。从接入流程和必备要素(开发文档、演示代码)的角度来看,三家不存在大的差异。其中环信没有视频教学,要扣分。而网易云信10月13号的发布会里提出了1=216(云信的216位专家称为用户的团队成员)的概念,为开发者提供全程的技术支持,服务已然成了云信的承诺。其他两家在服务上没有特别之处,在网站也没有找到在线客服的入口。
&&& 总结:
&&& 2015年是收获、成长的一年,我们看到了即时通讯云服务行业如井喷式的爆发,也看到了即时通讯云服务也在逐渐的平稳,就像狄更斯说过的“这是最好的时代,这是最坏的时代”,的确,在今天即时通讯云服务市场有不少选择,是脱颖而出,还是继续沉沦,也许就是一线之间。
&&& 从多项评测来看,云信、环信和融云作为目前国内即时通讯云服务的领军者,均有着不错的表现。对于企业用户来说,选择云服务最看重的就是性价比,产品性能稳定和节约成本是共同追求的目标,由此看三家均是不错的选择,在这里,笔者更倾向于推荐网易云信,网易云信作为互联网行业的一线品牌,产品功能更加完善,同时有非常好的稳定性。费用方面,融云和环信均打出了免费招牌招揽用户,云信显然比较冷静,虽然是收费政策,但定价不高,后续也有完善的运维服务,毕竟一分钱一分货,付费服务的含金量毋庸置疑。
编 辑:孔垂帅
余承东:华为荣耀已成为双品牌&未来将走独立品牌道路,6月30..
CCTIME推荐
CCTIME飞象网
CopyRight &
京ICP备号&& 京公网安备号
公司名称: 北京飞象互动文化传媒有限公司
未经书面许可,禁止转载、摘编、复制、镜像集成环信的即时通讯 - 简书
<div class="fixed-btn note-fixed-download" data-toggle="popover" data-placement="left" data-html="true" data-trigger="hover" data-content=''>
写了26363字,被335人关注,获得了408个喜欢
集成环信的即时通讯
现在即时通讯比较火,很多公司会用到即时通讯,所以今天给大家讲讲即时通讯,因为没有服务器,所以使用了环信的SDK写了个即时通讯的demo,由于界面比较多,我不会像之前一样直接上代码,而是手把手一点点给大家说,用最简单的代码完成基本的即时通讯功能,尽量使大家看了这篇博客后都能写出一个简单的即时通讯的demo.
上官网注册账号
首先来到环信的,然后登陆.没有账号先注册一个.进去之后创建应用,如图
创建应用界面
点击确定后,来到这个界面,只需要记住应用标示(APPKey)就行,待会儿会在代码里用到它.
下午7.18.38.png
然后用cocoapods导入环信SDK,大家可以通过来安装cocoapods.
打开终端,输入cd,然后将项目入进去回车,就跳到项目地址,输入命令:pod init,然后会生成一个Podfile,双击这个文件,将里面的东西全删了,然后输入:pod 'EaseMobSDK',然后在终端输入命令:pod install(如果不行可以试试:pod install --verbose --no-repo-update).接下来就等着SDK下载安装到项目里了,大概几分钟后就好了.这时候需要双击.xcworkspace的那个文件进去.SDK集成就完成了(不知道为什么官方文档里面写的集成特别复杂,需要导入各种框架,修改很多东西,其实只要终端一条指令就OK了).
AppDelegate
大家还记得刚才的APPKey吧,在AppDelegate里面需要注册使用.
#import "AppDelegate.h"
#import "ViewController.h"
#import &EaseMob.h&
@interface AppDelegate ()
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
_window.backgroundColor = [UIColor whiteColor];
[_window makeKeyAndVisible];
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[[ViewController alloc]init]];
_window.rootViewController =
//注册环信
[[EaseMob sharedInstance]registerSDKWithAppKey:@"xmh123#cdxmh" apnsCertName:@""];
return YES;
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EaseMob sharedInstance] applicationDidEnterBackground:application];
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EaseMob sharedInstance] applicationWillEnterForeground:application];
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
- (void)applicationWillTerminate:(UIApplication *)application {
[[EaseMob sharedInstance]applicationWillTerminate:application];
看看这部分的代码吧.
#import "ViewController.h"
#import "RegisterViewController.h"
#import "FriendListViewController.h"
#import &EaseMob.h&
@interface ViewController ()
@property(nonatomic, strong)UITextField *userNameTextF//用户名
@property(nonatomic, strong)UITextField *passwordTextF//密码
@property(nonatomic, strong)UIButton *loginB
//登陆按钮
@property(nonatomic, strong)UIButton *registerB
//注册按钮
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.translucent = NO;
self.title = @"登陆界面";
UILabel *usernameLabel = [[UILabel alloc]initWithFrame:CGRectMake(20, 100, 80, 50)];
usernameLabel.text = @"用户名";
usernameLabel.font = [UIFont systemFontOfSize:25];
[self.view addSubview:usernameLabel];
_userNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(usernameLabel.frame.origin.x + usernameLabel.frame.size.width + 10, usernameLabel.frame.origin.y, 250, 50)];
_userNameTextField.borderStyle = 3;
_userNameTextField.placeholder = @"请输入用户名";
[self.view addSubview:_userNameTextField];
UILabel *passwordLabel = [[UILabel alloc]initWithFrame:CGRectMake(usernameLabel.frame.origin.x, usernameLabel.frame.origin.y + usernameLabel.frame.size.height + 10, usernameLabel.frame.size.width, usernameLabel.frame.size.height)];
passwordLabel.text = @"密码";
passwordLabel.font = [UIFont systemFontOfSize:25];
[self.view addSubview:passwordLabel];
_passwordTextField = [[UITextField alloc]initWithFrame:CGRectMake(_userNameTextField.frame.origin.x, passwordLabel.frame.origin.y, _userNameTextField.frame.size.width, _userNameTextField.frame.size.height)];
_passwordTextField.placeholder = @"请输入密码";
_passwordTextField.borderStyle = 3;
[self.view addSubview:_passwordTextField];
_loginButton = [UIButton buttonWithType:UIButtonTypeSystem];
_loginButton.frame = CGRectMake(170, 300, 50, 50);
_loginButton.titleLabel.font = [UIFont systemFontOfSize:25];
[_loginButton setTitle:@"登陆" forState:UIControlStateNormal];
[_loginButton addTarget:self action:@selector(didClickLoginButton) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_loginButton];
_registerButton = [UIButton buttonWithType:UIButtonTypeSystem];
_registerButton.frame = CGRectMake(170, 410, 50, 50);
_registerButton.titleLabel.font = [UIFont systemFontOfSize:25];
[_registerButton setTitle:@"注册" forState:UIControlStateNormal];
[_registerButton addTarget:self action:@selector(jumpToRegister) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_registerButton];
-(void)didClickLoginButton
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:_userNameTextField.text password:_passwordTextField.text completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error) {
//如果验证用户名和密码没有问题就跳转到好友列表界面
[self.navigationController pushViewController:[[FriendListViewController alloc]init] animated:YES];
// 显示错误信息的警告
NSLog(@"%@",error);
} onQueue:dispatch_get_main_queue()];
-(void)jumpToRegister
//跳转到注册界面
RegisterViewController *registerVC = [[RegisterViewController alloc]init];
[self.navigationController presentViewController:registerVC animated:YES completion:nil];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
这个界面很简单,大部分都是界面搭建,当点击登陆时会调用
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:_userNameTextField.text password:_passwordTextField.text completion:^(NSDictionary *loginInfo, EMError *error)
这个方法有2个参数,一个用户名,一个是密码,很容易猜到这就是验证你的用户名和密码是否正确,如果正确就跳转到好友列表界面,如果不对就会在控制台打印相应的错误.点击注册就跳转到注册界面.
由于第一次登陆时没有账号,所以先得注册.先看看代码:
#import "RegisterViewController.h"
#import &EaseMob.h&
@interface RegisterViewController ()
@property(nonatomic, strong)UITextField *userNameTextF//用户名
@property(nonatomic, strong)UITextField *passwordTextF//密码
@property(nonatomic, strong)UIButton *registerB
//注册按钮
@implementation RegisterViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.translucent = NO;
self.title = @"登陆界面";
UILabel *usernameLabel = [[UILabel alloc]initWithFrame:CGRectMake(20, 100, 80, 50)];
usernameLabel.text = @"用户名";
usernameLabel.font = [UIFont systemFontOfSize:25];
[self.view addSubview:usernameLabel];
_userNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(usernameLabel.frame.origin.x + usernameLabel.frame.size.width + 10, usernameLabel.frame.origin.y, 250, 50)];
_userNameTextField.borderStyle = 3;
_userNameTextField.placeholder = @"请输入用户名";
[self.view addSubview:_userNameTextField];
UILabel *passwordLabel = [[UILabel alloc]initWithFrame:CGRectMake(usernameLabel.frame.origin.x, usernameLabel.frame.origin.y + usernameLabel.frame.size.height + 10, usernameLabel.frame.size.width, usernameLabel.frame.size.height)];
passwordLabel.text = @"密码";
passwordLabel.font = [UIFont systemFontOfSize:25];
[self.view addSubview:passwordLabel];
_passwordTextField = [[UITextField alloc]initWithFrame:CGRectMake(_userNameTextField.frame.origin.x, passwordLabel.frame.origin.y, _userNameTextField.frame.size.width, _userNameTextField.frame.size.height)];
_passwordTextField.placeholder = @"请输入密码";
_passwordTextField.borderStyle = 3;
[self.view addSubview:_passwordTextField];
_registerButton = [UIButton buttonWithType:UIButtonTypeSystem];
_registerButton.frame = CGRectMake(170, 330, 50, 50);
_registerButton.titleLabel.font = [UIFont systemFontOfSize:25];
[_registerButton setTitle:@"注册" forState:UIControlStateNormal];
[_registerButton addTarget:self action:@selector(didClickedRegisterButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_registerButton];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeSystem];
backButton.frame = CGRectMake(170, 280, 50, 50);
backButton.titleLabel.font = [UIFont systemFontOfSize:25];
[backButton setTitle:@"返回" forState:UIControlStateNormal];
[backButton addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backButton];
-(void)backAction
[self dismissViewControllerAnimated:YES completion:nil];
-(void)touchesBegan:(NSSet&UITouch *& *)touches withEvent:(UIEvent *)event
//点击屏幕时让键盘回收
[_passwordTextField resignFirstResponder];
[_userNameTextField resignFirstResponder];
-(void)didClickedRegisterButton:(id)sender
登陆和注册有3种方法: 1. 同步方法 2. 通过delegate回调的异步方法。3.block异步方法
//其中官方推荐使用block异步方法,所以我这里就用block异步方法
//开始注册
[[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:_userNameTextField.text password:_passwordTextField.text withCompletion:^(NSString *username, NSString *password, EMError *error) {
if (!error) {
NSLog(@"注册成功");
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(@"%@",error);
} onQueue:dispatch_get_main_queue()];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
这个界面也特别简单,除了界面搭建,就只有这个方法:
[[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:_userNameTextField.text password:_passwordTextField.text withCompletion:^(NSString *username, NSString *password, EMError *error)
一共两个参数,就是用户名和密码,然后判断用户名和密码是否可用,如果可用就返回到登陆界面.
好友列表界面
好友列表界面
这里我只加了一个好友叫xiaomei.好友界面是怎么样的呢,大家先看看代码:
#import "FriendListViewController.h"
#import "AddFriendViewController.h"
#import "ChatViewController.h"
#import &EaseMob.h&
@interface FriendListViewController ()&UITableViewDataSource,UITableViewDelegate,EMChatManagerDelegate,EMChatManagerBuddyDelegate&
@property(nonatomic, strong)NSMutableArray *listA
@property(nonatomic, strong)UITableView *tableV
@implementation FriendListViewController
-(void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
-(void)loadView
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
//左侧注销按钮
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"注销" style:UIBarButtonItemStylePlain target:self action:@selector(didClickedCancelButton)];
self.title = @"好友";
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@", buddyList);
[_listArray removeAllObjects];
[_listArray addObjectsFromArray:buddyList];
[_tableView reloadData];
} onQueue:dispatch_get_main_queue()];
- (void)viewDidLoad {
[super viewDidLoad];
_listArray = [NSMutableArray new];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addbuttonAction)];
_tableView = [[UITableView alloc]initWithFrame:self.view.frame];
_tableView.delegate =
_tableView.dataSource =
_tableView.tableFooterView = [[UIView alloc]init];
[self.view addSubview:_tableView];
[ [EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:dispatch_get_main_queue()];
-(void)didClickedCancelButton
//注销用户
[[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:YES];
[self.navigationController popViewControllerAnimated:YES];
-(void)addbuttonAction
[self.navigationController pushViewController:[[AddFriendViewController alloc]init] animated:YES];
# pragma mark - Table View Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _listArray.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
return 50;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
EMBuddy * buddy = _listArray[indexPath.row];
cell.textLabel.text = buddy.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ChatViewController * chatVC = [[ChatViewController alloc]init];
EMBuddy * buddy = _listArray[indexPath.row];
chatVC.name = buddy.
[self.navigationController pushViewController:chatVC animated:YES];
-(void)didReceiveBuddyRequest:(NSString *)username message:(NSString *)message
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"收到来自%@的请求", username] message:message preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction * acceptAction = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *
// 同意好友请求的方法
if ([[EaseMob sharedInstance].chatManager acceptBuddyRequest:username error:&error] && !error) {
NSLog(@"发送同意成功");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@", buddyList);
[_listArray removeAllObjects];
[_listArray addObjectsFromArray:buddyList];
[_tableView reloadData];
} onQueue:dispatch_get_main_queue()];
UIAlertAction * rejectAction = [UIAlertAction actionWithTitle:@"滚" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
// 拒绝好友请求的方法
if ([[EaseMob sharedInstance].chatManager rejectBuddyRequest:username reason:@"滚, 快滚!" error:&error] && !error) {
NSLog(@"发送拒绝成功");
[alertController addAction:acceptAction];
[alertController addAction:rejectAction];
[self showDetailViewController:alertController sender:nil];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
首先,使用下面这个方法获取到所有的好友,然后将好友放入你的数组,这样好友的信息都在数组里了
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@", buddyList);
[_listArray removeAllObjects];
[_listArray addObjectsFromArray:buddyList];
[_tableView reloadData];
} onQueue:dispatch_get_main_queue()];
tableView没行的信息通过下面这个方法,刚才已经把所有好友的信息方法数组里了,那么就可以通过EMBuddy * buddy = _listArray[indexPath.row]这个方法获取单个好友信息,用
cell.textLabel.text = buddy.username给每个cell的text赋值.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
EMBuddy * buddy = _listArray[indexPath.row];
cell.textLabel.text = buddy.
当别人想加你为好友时,会调用这个方法.先弹出一个提示框,然后根据你的选择而去接受或者拒绝.如果接受,就重新导入一遍数据,然后刷新tableView.如果拒绝就调用拒绝的方法.这并不难理解吧.
-(void)didReceiveBuddyRequest:(NSString *)username message:(NSString *)message
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"收到来自%@的请求", username] message:message preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction * acceptAction = [UIAlertAction actionWithTitle:@"好" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *
// 同意好友请求的方法
if ([[EaseMob sharedInstance].chatManager acceptBuddyRequest:username error:&error] && !error) {
NSLog(@"发送同意成功");
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@", buddyList);
[_listArray removeAllObjects];
[_listArray addObjectsFromArray:buddyList];
[_tableView reloadData];
} onQueue:dispatch_get_main_queue()];
UIAlertAction * rejectAction = [UIAlertAction actionWithTitle:@"滚" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
// 拒绝好友请求的方法
if ([[EaseMob sharedInstance].chatManager rejectBuddyRequest:username reason:@"滚, 快滚!" error:&error] && !error) {
NSLog(@"发送拒绝成功");
[alertController addAction:acceptAction];
[alertController addAction:rejectAction];
[self showDetailViewController:alertController sender:nil];
在看聊天界面之前先需要自定义一个聊天输入框,就是下面那个带一个textfield和button的.
DialogBoxView.m
#import "DialogBoxView.h"
@interface DialogBoxView ()
@property (nonatomic, strong) UITextField * draftTextF
@property (nonatomic, strong) UIButton * sendB
@implementation DialogBoxView
- (instancetype)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if (self) {
[self initView];
- (void)initView{
[self setBackgroundColor:[UIColor colorWithWhite:0.9 alpha:1]];
_draftTextField = [[UITextField alloc] initWithFrame:CGRectMake(5, 5, self.frame.size.width - 100, self.frame.size.height - 10)];
[_draftTextField setBorderStyle:(UITextBorderStyleRoundedRect)];
[_draftTextField setPlaceholder:@"说点什么呢"];
[_draftTextField setFont:[UIFont systemFontOfSize:13]];
[self addSubview:_draftTextField];
_sendButton = [UIButton buttonWithType:(UIButtonTypeCustom)];
[_sendButton setFrame:CGRectMake(self.frame.size.width - 90, 5, 85, self.frame.size.height - 10)];
[_sendButton setBackgroundColor:[UIColor colorWithRed:1 green:0 blue:128 / 255.0 alpha:1]];
[_sendButton setTitle:@"发送" forState:(UIControlStateNormal)];
[_sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_sendButton.titleLabel setFont:[UIFont boldSystemFontOfSize:15]];
[_sendButton.layer setMasksToBounds:YES];
[_sendButton.layer setCornerRadius:4];
[_sendButton addTarget:self action:@selector(didSendButtonClicked:) forControlEvents:(UIControlEventTouchUpInside)];
[self addSubview:_sendButton];
- (void)didSendButtonClicked:(UIButton *)sender {
if (self.buttonClicked) {
self.buttonClicked(_draftTextField.text);
_draftTextField.text = @"";
- (NSString *)draftText {
return _draftTextField.
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
这个简单的自定义view没什么好说的,关键是他的.h文件
DialogBoxView.h
#import &UIKit/UIKit.h&
typedef void(^ButtonClicked)(NSString * draftText);
@interface DialogBoxView : UIView
@property (nonatomic, copy) ButtonClicked buttonC
这里用到一个block,当点击按钮时会调用这个block.
接下看看聊天界面的代码吧.
#import "ChatViewController.h"
#import "DialogBoxView.h"
#import &EaseMob.h&
@interface ChatViewController ()&EMChatManagerDelegate,UITableViewDelegate,UITableViewDataSource&
@property(nonatomic, strong)UITableView *tableV
@property(nonatomic, strong)EMConversation *
@property(nonatomic, strong)DialogBoxView *dialogBoxV
@implementation ChatViewController
-(void)loadView
[super loadView];
self.title = _
self.navigationController.navigationBar.translucent = NO;
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 50)];
_tableView.delegate =
_tableView.dataSource =
_tableView.tableFooterView = [[UIView alloc]init];
[self.view addSubview:_tableView];
- (void)viewDidLoad {
[super viewDidLoad];
[_tableView setAllowsSelection:NO];
[self registerForKeyboardNotifications];
_dialogBoxView = [[DialogBoxView alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height - 114, self.view.frame.size.width, 50)];
__weak typeof(self) weakSelf =
_dialogBoxView.buttonClicked = ^(NSString * draftText){
[weakSelf sendMessageWithDraftText:draftText];
[self.view addSubview:_dialogBoxView];
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:dispatch_get_main_queue()];
[self reloadChatRecords];
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 移除通知中心
[self removeForKeyboardNotifications];
// 移除代理
[[EaseMob sharedInstance].chatManager removeDelegate:self];
# pragma mark - Send Message
使用草稿发送一条信息
@param draftText 草稿
- (void)sendMessageWithDraftText:(NSString *)draftText {
EMChatText * chatText = [[EMChatText alloc] initWithText:draftText];
EMTextMessageBody * body = [[EMTextMessageBody alloc] initWithChatObject:chatText];
// 生成message
EMMessage * message = [[EMMessage alloc] initWithReceiver:self.name bodies:@[body]];
message.messageType = eMessageTypeC // 设置为单聊消息
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil prepare:^(EMMessage *message, EMError *error) {
// 准备发送
} onQueue:dispatch_get_main_queue() completion:^(EMMessage *message, EMError *error) {
[self reloadChatRecords];
// 发送完成
} onQueue:dispatch_get_main_queue()];
# pragma mark - Receive Message
当收到了一条消息时
@param message 消息构造体
- (void)didReceiveMessage:(EMMessage *)message {
[self reloadChatRecords];
# pragma mark - Reload Chat Records
重新加载TableView上面显示的聊天信息, 并移动到最后一行
- (void)reloadChatRecords {
_conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:self.name conversationType:eConversationTypeChat];
[_tableView reloadData];
if ([_conversation loadAllMessages].count & 0) {
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[_conversation loadAllMessages].count - 1 inSection:0] atScrollPosition:(UITableViewScrollPositionBottom) animated:YES];
# pragma mark - Keyboard Method
注册通知中心
- (void)registerForKeyboardNotifications
// 使用NSNotificationCenter 注册观察当键盘要出现时
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
// 使用NSNotificationCenter 注册观察当键盘要隐藏时
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
移除通知中心
- (void)removeForKeyboardNotifications {
[[NSNotificationCenter defaultCenter] removeObserver:self];
键盘将要弹出
@param notification 通知
- (void)didKeyboardWillShow:(NSNotification *)notification {
NSDictionary * info = [notification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].
NSLog(@"%f", keyboardSize.height);
//输入框位置动画加载
[self begainMoveUpAnimation:keyboardSize.height];
键盘将要隐藏
@param notification 通知
- (void)didKeyboardWillHide:(NSNotification *)notification {
[self begainMoveUpAnimation:0];
开始执行键盘改变后对应视图的变化
@param height 键盘的高度
- (void)begainMoveUpAnimation:(CGFloat)height {
[UIView animateWithDuration:0.3 animations:^{
[_dialogBoxView setFrame:CGRectMake(0, self.view.frame.size.height - (height + 40), _dialogBoxView.frame.size.width, _dialogBoxView.frame.size.height)];
[_tableView layoutIfNeeded];
if ([_conversation loadAllMessages].count & 1) {
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_conversation.loadAllMessages.count - 1 inSection:0] atScrollPosition:(UITableViewScrollPositionMiddle) animated:YES];
# pragma mark - Table View Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _conversation.loadAllMessages.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
EMMessage * message = _conversation.loadAllMessages[indexPath.row];
EMTextMessageBody * body = [message.messageBodies lastObject];
//判断发送的人是否是当前聊天的人,左边是对面发过来的,右边是自己发过去的
if ([message.to isEqualToString:self.name]) {
cell.detailTextLabel.text = body.
cell.detailTextLabel.textColor = [UIColor redColor];
cell.textLabel.text = @"";
cell.textLabel.textColor = [UIColor blueColor];
cell.detailTextLabel.text = @"";
cell.textLabel.text = body.
cell.detailTextLabel.textColor = [UIColor redColor];
cell.textLabel.textColor = [UIColor blueColor];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
这里面也没什么难点,也有注释,相信大家能懂.最后看看最后的成果图吧.
好了,今天就到这里,祝大家天天开心
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
专题内容主要包括OC、swift等涉及到iOS开发进阶的内容。
swift可以关注下我的另一个专题:
swift开发...
· 21845人关注
学习从点滴开始 !
(PS: 拒绝部分投稿的文章仅仅是由于专题内已收录相关知识点的文章, 并非是投稿的文章技术含量不够好, 望谅解.)
· 5992人关注
心情不好的时候问自己 :
我为何这么屌
心情好的时候问自己 : 为什么比我屌的这么多
· 3833人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:}

我要回帖

更多关于 即时通信协议 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信