通讯协议与即时通讯杂谈

IM实现方式

第一种方式,使用第三方IM服务

国内IM的第三方服务商有很多,类似云信、环信、融云、LeanCloud

第三方服务商IM底层协议基本上都是TCP。他们的IM方案很成熟,有了它们,我们甚至不需要自己去搭建IM后台,什么都不需要去考虑。
如果你足够懒,甚至连UI都不需要自己做,这些第三方有各自一套IM的UI,拿来就可以直接用。真可谓3分钟集成…

但是缺点也很明显,定制化程度太高,很多东西我们不可控。当然还有一个最最重要的一点,就是太贵了…作为真正社交为主打的APP,仅此一点,就足以让我们望而却步。

另外一种方式,我们自己去实现

我们自己去实现也有很多选择:

  • 1)首先面临的就是传输协议的选择,TCP还是UDP?

    结论吧:对于小公司或者技术不那么成熟的公司,IM一定要用TCP来实现,因为如果你要用UDP的话,需要做的事太多。当然QQ就是用的UDP协议,当然不仅仅是UDP,腾讯还用了自己的私有协议,来保证了传输的可靠性,杜绝了UDP下各种数据丢包乱序等等一系列问题。

  • 2)其次是我们需要去选择使用哪种聊天协议:

    基于Scoket或者WebScoket或者其他的私有协议、MQTT、还是广为人诟病的XMPP?

    基于Scoket原生:代表框架 CocoaAsyncSocket。
    基于WebScoket:代表框架 SocketRocket。
    基于MQTT:代表框架 MQTTKit。
    基于XMPP:代表框架 XMPPFramework。

    其中MQTTXMPP聊天协议,它们是最上层的协议,而WebScoket传输通讯协议,它是基于Socket封装的一个协议。而通常我们所说的腾讯IM的私有协议,就是基于WebScoket或者Scoket原生进行封装的一个聊天协议。

    说到底,iOS要做一个真正的IM产品,一般都是基于Scoket或者WebScoket等,再之上加上一些私有协议来保证的

    扫描二维码关注公众号,回复: 14220616 查看本文章
  • 3)我们是自己去基于OS底层Socket进行封装还是在第三方框架的基础上进行封装?

  • 4)传输数据的格式,我们是用Json、还是XML、还是谷歌推出的ProtocolBuffer

    使用 ProtocolBuffer 减少 Payload 。 携程之前分享过,说是采用新的Protocol Buffer数据格式+Gzip压缩后的Payload大小降低了15%-45%。数据序列化耗时下降了80%-90%。

  • 5)我们还有一些细节问题需要考虑,例如TCP的长连接如何保持心跳机制Qos机制重连机制等等…当然,除此之外,我们还有一些安全问题需要考虑。

IM聊天协议

1.XMPP

XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能。

XMPP的优点是:协议成熟强大可扩展性强,并且有成熟的开源方案。
XMPP的缺点是:信息冗余量大(信息的格式是 XML),因而费流量,费电。

2.MQTT

MQTT全称叫做Message Queuing Telemetry Transport,意为消息队列遥测传输,是IBM开发的一个即时通讯协议。由于其维护一个长连接以轻量级低消耗著称,所以常用于移动端消息推送服务开发。

MQTT的优点是:协议简洁轻巧数据冗余量低。并且支持的设备从智能硬件到智能手机无所不包。
MQTT的缺点是:服务器端实现难度大,虽然已经有了C++版本的服务端组件,但是并不开源。而且在推送数量较大时如何处理并发是十分考验后台人员的技术水平的。

MQTT具有如下特性:

使用发布/订阅消息模式,提供一对多消息发布;
对负载内容屏蔽的消息传输;
使用TCP/IP进行网络连接;
主流的MQTT是基于TCP进行连接的,同样也有UDP版本的MQTT,但是不太常用,叫做MQTT-SN。

具有三种消息发布服务质量选项:

1.“至多一次”,通常app的推送使用的就是这种模式。也就是说,如果移动设备在消息推送的时候没有联网,那么再次联网就不会收到通知了;
2.“至少一次”,可以确保消息收到,但消息可能会重复;
3.“只有一次”,确保消息到达一次,比如计费系统, 如果出现消息重复或者丢失会导致系统结果不正确的问题。

小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量;
这就是为什么MQTT能以轻量级低消耗著称,所以MQTT特别适用于低开销、低宽带占用的即时通讯场景。
通知有关各方客户端异常中断的机制。

MQTT协议实现方式

1.在MQTT协议中有三种身份:

发布者(Publish)。发布者其实是客户端,可以进行发布消息;
代理(Broker)。代理指的是服务器,比较有名的是eqmtt,当前,你也可以用其他成熟的框架去搭建MQTT服务;
订阅者(Subscribe)。一般指的是客户端,不过,发布者同时也可以是订阅者。

2.MQTT客户端

一般来说,客户端可以实现一下功能:

给其他客户端发布订阅的信息;
订阅其他客户端发布的信息;
退订和订阅主题;
断开服务器连接。

3.MQTT服务端

MQTT服务端也称为消息代理,经常你会听到broker这个词。它可以实现一下功能:

接收来自客户端的网络连接;
接受客户发布的应用信息;
处理来自客户端主题订阅和退订请求;
向订阅的客户端转发应用程序消息。

4.MQTT协议中的方法

MQTT和HTTP一样,也定义了一些动作,来表示对确定资源进行操作。

Connect,等待于服务器建立连接;
Disconnect,等待客户端完成所做的工作,并与服务器断开TCP/IP会话;
Subscribe,主题订阅;
UnSubscribe,主题取消订阅;
Publish,发送消息。

Socket编程

我们先不使用任何框架,直接用OS底层Socket来实现一个简单的IM。

我们客户端的实现思路也是很简单,创建Socket,和服务器的Socket对接上,然后开始传输数据就可以了。

Socket是什么呢,简单的来说,就是我们使用TCP/IP 或者UDP/IP协议的一组编程接口。Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以较为方便的编写网络上数据的传递。

1.socket与进程的关系

  • 1)socket与进程间的关系:socket 用来让一个进程和其他的进程互通信息(IPC),而Socket接口是TCP/IP网络的API接口函数。
  • 2)进程间通信(本机内)
    进程间通信(不同计算机,要联网)

2、socket与文件的关系——如何理解socket是种特殊的I/O?

  • 1)Socket最先应用于Unix操作系统,如果了解Unix系统的I/O的话,就很容易了解Socket了,因为Socket数据传输其实就是一种特殊的I/O。
  • 2)可对其进行文件操作
  • 3)有文件描述符。而文件描述符的本质是一个非负整数。只是用于区分。类似的还有进程ID。

首先我们不基于任何框架,直接去调用OS底层-基于C的BSD Socket去实现,它提供了这样一组接口:

//socket 创建并初始化 socket,返回该 socket 的文件描述符,如果描述符为 -1 表示创建失败。
int socket(int addressFamily, int type,int protocol)
//关闭socket连接
int close(int socketFileDescriptor)
//将 socket 与特定主机地址与端口号绑定,成功绑定返回0,失败返回 -1。
int bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)
//接受客户端连接请求并将客户端的网络地址信息保存到 clientAddress 中。
int accept(int socketFileDescriptor,sockaddr *clientAddress, int clientAddressStructLength)
//客户端向特定网络地址的服务器发送连接请求,连接成功返回0,失败返回 -1。
int connect(int socketFileDescriptor,sockaddr *serverAddress, int serverAddressLength)
//使用 DNS 查找特定主机名字对应的 IP 地址。如果找不到对应的 IP 地址则返回 NULL。
hostent* gethostbyname(char *hostname)
//通过 socket 发送数据,发送成功返回成功发送的字节数,否则返回 -1。
int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags)
//从 socket 中读取数据,读取成功返回成功读取的字节数,否则返回 -1。
int receive(int socketFileDescriptor,char *buffer, int bufferLength, int flags)
//通过UDP socket 发送数据到特定的网络地址,发送成功返回成功发送的字节数,否则返回 -1。
int sendto(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)
//从UDP socket 中读取数据,并保存发送者的网络地址信息,读取成功返回成功读取的字节数,否则返回 -1 。
int recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength)

让我们可以对socket进行各种操作,首先我们来用它写个客户端。总结一下,简单的IM客户端需要做如下4件事:

客户端调用 socket(…) 创建socket;
客户端调用 connect(…) 向服务器发起连接请求以建立连接;
客户端与服务器建立连接之后,就可以通过send(…)/receive(…)向客户端发送或从客户端接收数据;
客户端调用 close 关闭 socket;

服务端需要做的如下5件事:

服务器调用 socket(…) 创建socket;
服务器调用 listen(…) 设置缓冲区;
服务器通过 accept(…)接受客户端请求建立连接;
服务器与客户端建立连接之后,就可以通过 send(…)/receive(…)向客户端发送或从客户端接收数据;
服务器调用 close 关闭 socket;

IM常用机制

心跳机制

心跳就是用来检测TCP连接的双方是否可用。那又会有人要问了,TCP不是本身就自带一个KeepAlive机制吗?

这里我们需要说明的是TCPKeepAlive机制只能保证连接的存在,但是并不能保证客户端以及服务端的可用性.比如会有以下一种情况:

某台服务器因为某些原因导致负载超高,CPU 100%,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态,这就是典型的连接活着但业务提供方已死的状态。

这个时候心跳机制就起到作用了:

  • 我们客户端发起心跳Ping(一般都是客户端),假如设置在10秒后如果没有收到回调,那么说明服务器或者客户端某一方出现问题,这时候我们需要主动断开连接。

  • 服务端也是一样,会维护一个socket的心跳间隔,当约定时间内,没有收到客户端发来的心跳,我们会知道该连接已经失效,然后主动断开连接。

PingPong机制

我们在这心跳间隔的3-5分钟如果连接假在线(例如在地铁电梯这种环境下)。那么我们岂不是无法保证消息的即时性么?这显然是我们无法接受的,所以业内的解决方案是采用双向的PingPong机制

  • 服务端发出一个Ping,客户端没有在约定的时间内返回响应的ack,则认为客户端已经不在线,这时我们Server端会主动断开Scoket连接,并且改由APNS推送的方式发送消息。

  • 同样的是,当客户端去发送一个消息,因为我们迟迟无法收到服务端的响应ack包,则表明客户端或者服务端已不在线,我们也会显示消息发送失败,并且断开Scoket连接。

还记得我们之前CocoaSyncSockt的例子所讲的获取消息超时就断开吗?其实它就是一个PingPong机制的客户端实现。我们每次可以在发送消息成功后,调用这个超时读取的方法,如果一段时间没收到服务器的响应,那么说明连接不可用,则断开Scoket连接

重连机制

理论上,我们自己主动去断开的Scoket连接(例如退出账号,APP退出到后台等等),不需要重连。其他的连接断开,我们都需要进行断线重连。

一般解决方案是尝试重连几次,如果仍旧无法重连成功,那么不再进行重连。

接下来的WebScoket的例子,我会封装一个重连时间指数级增长的一个重连方式,可以作为一个参考。

WebScoket最具代表性的一个第三方框架SocketRocket

//重连机制
 (void)reConnect
{
    
    
    [self disConnect]; // 断开连接
    //超过一分钟就不再重连 所以只会重连5次 2^5 = 64
    if (reConnectTime > 64) {
    
    
        return;
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
        webSocket = nil;
        [self initSocket];
    });
    //重连时间2的指数级增长
    if (reConnectTime == 0) {
    
    
        reConnectTime = 2;
    }else{
    
    
        reConnectTime *= 2;
    }
}

IM一些其它问题

1.IM的可靠性

我们之前穿插在例子中提到过:

心跳机制PingPong机制断线重连机制、还有我们后面所说的QOS机制。这些被用来保证连接的可用,消息的即时与准确的送达等等。

上述内容保证了我们IM服务时的可靠性,其实我们能做的还有很多:比如我们在大文件传输的时候使用分片上传断点续传秒传技术等来保证文件的传输。

2.安全性

我们通常还需要一些安全机制来保证我们IM通信安全。

例如:防止 DNS 污染帐号安全第三方服务器鉴权单点登录等等

3.一些其他的优化

类似微信,服务器不做聊天记录的存储,只在本机进行缓存,这样可以减少对服务端数据的请求,一方面减轻了服务器的压力,另一方面减少客户端流量的消耗。

我们进行http连接的时候尽量采用上层API,类似NSURLSession。而网络框架尽量使用AFNetWorking3。因为这些上层网络请求都用的是HTTP/2 ,我们请求的时候可以复用这些连接。

音视频通话

IM应用中的实时音视频技术,几乎是IM开发中的最后一道高墙。原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的。

实时音视频技术上的实现内容主要包括:音视频的采集编码网络传输解码播放等环节。

猜你喜欢

转载自blog.csdn.net/zhanglei5415/article/details/124989394