推送服務(wù)一般有三種實(shí)現(xiàn)方式:
1.輪詢方式
客戶端不斷的查詢服務(wù)器,檢索新內(nèi)容。這種方式的缺點(diǎn)十分明顯,如果輪詢頻率過(guò)快,會(huì)大量消耗網(wǎng)絡(luò)帶寬和電池;
2.長(zhǎng)連接方式
客戶端和服務(wù)端維持一條TCP/IP長(zhǎng)連接,服務(wù)端向客戶端push數(shù)據(jù)。這種方式可以避免輪詢方式帶來(lái)的性能問(wèn)題,但是長(zhǎng)連接依然會(huì)帶來(lái)耗能問(wèn)題。目前蘋果的APNS和谷歌的GCM都是基于此方案來(lái)實(shí)現(xiàn)推送服務(wù)的;
3.SMS方式
當(dāng)服務(wù)端有新內(nèi)容的時(shí)候,會(huì)發(fā)送一條類似短信的指令傳給客戶端,客戶端收到后從服務(wù)端下載新內(nèi)容。由于運(yùn)營(yíng)商并沒(méi)有免費(fèi)開放這種指令,使用需要向運(yùn)營(yíng)商繳納部分費(fèi)用,所以并沒(méi)有大量運(yùn)用起來(lái),但是這種方式非常的高效和及時(shí)。
主流推送方案應(yīng)用比較
1.APNS(Apple Push Notification Service)和GCM(Google Cloud Messaging)
APNS和GCM是iOS和Android兩大陣營(yíng)提出的官方推送方案,這兩者的技術(shù)架構(gòu)較為相似。都是由系統(tǒng)來(lái)統(tǒng)一的維護(hù)一個(gè)長(zhǎng)連接,所有的APP統(tǒng)一發(fā)送心跳和接收推送。
APNS使用的方便性毋庸置疑,但是GCM卻在國(guó)內(nèi)舉步維艱,具體原因有以下三個(gè):
1)Google與我國(guó)政府交惡,導(dǎo)致GMS(Google Mobile Service)在國(guó)內(nèi)無(wú)法正常使用,而GCM是依賴于GMS的,所以無(wú)法順利使用。
2)由于國(guó)內(nèi)2G和移動(dòng)3G的NAT超時(shí)時(shí)間都小于GCM心跳時(shí)間(28分鐘),TCP長(zhǎng)連接必然無(wú)法保活,每次都要等28分鐘心跳失敗重連后才能收到Push。
3)某些運(yùn)營(yíng)商可能限制了5228端口,移動(dòng)3G/2G下,發(fā)現(xiàn)幾乎無(wú)法連接上GCM服務(wù)器,也就無(wú)法獲得GCM通知,WhatsApp放后臺(tái)10分鐘后,經(jīng)常很長(zhǎng)時(shí)間都收不到Push消息。
2.XMPP
XMPP是一種基于標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言的子集XML的協(xié)議,它繼承了在XML環(huán)境中靈活的發(fā)展性。因此,基于XMPP的應(yīng)用具有超強(qiáng)的可擴(kuò)展性。經(jīng)過(guò)擴(kuò)展以后的XMPP可以通過(guò)發(fā)送擴(kuò)展的信息來(lái)處理用戶的需求,以及在XMPP的頂端建立如內(nèi)容發(fā)布系統(tǒng)和基于地址的服務(wù)等應(yīng)用程序。而且,XMPP包含了針對(duì)服務(wù)器端的軟件協(xié)議,使之能與另一個(gè)進(jìn)行通話,這使得開發(fā)者更容易建立客戶應(yīng)用程序或給一個(gè)配好系統(tǒng)添加功能。
XMPP的優(yōu)點(diǎn)是:協(xié)議成熟,強(qiáng)大,可擴(kuò)展性強(qiáng),并且有成熟的開源方案。
XMPP的缺點(diǎn)是:信息冗余量大(信息的格式是 XML),因而費(fèi)流量,費(fèi)電。
3.MQTT
MQTT全稱叫做Message Queuing Telemetry Transport,意為消息隊(duì)列遙測(cè)傳輸,是IBM開發(fā)的一個(gè)即時(shí)通訊協(xié)議。由于其維護(hù)一個(gè)長(zhǎng)連接以輕量級(jí)低消耗著稱,所以常用于移動(dòng)端消息推送服務(wù)開發(fā)。
MQTT的優(yōu)點(diǎn)是:協(xié)議簡(jiǎn)潔輕巧,數(shù)據(jù)冗余量低。并且支持的設(shè)備從智能硬件到智能手機(jī)無(wú)所不包。
MQTT的缺點(diǎn)是:服務(wù)器端實(shí)現(xiàn)難度大,雖然已經(jīng)有了C++版本的服務(wù)端組件,但是并不開源。而且在推送數(shù)量較大時(shí)如何處理并發(fā)是十分考驗(yàn)后臺(tái)人員的技術(shù)水平的。
MQTT具有如下特性:
- 使用發(fā)布/訂閱消息模式,提供一對(duì)多消息發(fā)布;
- 對(duì)負(fù)載內(nèi)容屏蔽的消息傳輸;
- 使用TCP/IP進(jìn)行網(wǎng)絡(luò)連接;
主流的MQTT是基于TCP進(jìn)行連接的,同樣也有UDP版本的MQTT,但是不太常用,叫做MQTT-SN。
- 具有三種消息發(fā)布服務(wù)質(zhì)量選項(xiàng);
1.“至多一次”,通常app的推送使用的就是這種模式。也就是說(shuō),如果移動(dòng)設(shè)備在消息推送的時(shí)候沒(méi)有聯(lián)網(wǎng),那么再次聯(lián)網(wǎng)就不會(huì)收到通知了;
2.“至少一次”,可以確保消息收到,但消息可能會(huì)重復(fù);
3.“只有一次”,確保消息到達(dá)一次,比如計(jì)費(fèi)系統(tǒng), 如果出現(xiàn)消息重復(fù)或者丟失會(huì)導(dǎo)致系統(tǒng)結(jié)果不正確的問(wèn)題。 - 小型傳輸,開銷很?。ü潭ㄩL(zhǎng)度的頭部是2字節(jié)),協(xié)議交換最小化,以降低網(wǎng)絡(luò)流量;
這就是為什么MQTT能以輕量級(jí)低消耗著稱,所以MQTT特別適用于低開銷、低寬帶占用的即時(shí)通訊場(chǎng)景。 - 通知有關(guān)各方客戶端異常中斷的機(jī)制。
MQTT協(xié)議實(shí)現(xiàn)方式

在MQTT協(xié)議中有三種身份:
- 發(fā)布者(Publish)。發(fā)布者其實(shí)是客戶端,可以進(jìn)行發(fā)布消息;
- 代理(Broker)。代理指的是服務(wù)器,比較有名的是eqmtt,當(dāng)前,你也可以用其他成熟的框架去搭建MQTT服務(wù);
- 訂閱者(Subscribe)。一般指的是客戶端,不過(guò),發(fā)布者同時(shí)也可以是訂閱者。
MQTT客戶端
一般來(lái)說(shuō),客戶端可以實(shí)現(xiàn)一下功能:
- 給其他客戶端發(fā)布訂閱的信息;
- 訂閱其他客戶端發(fā)布的信息;
- 退訂和訂閱主題;
- 斷開服務(wù)器連接。
MQTT服務(wù)端
MQTT服務(wù)端也稱為消息代理,經(jīng)常你會(huì)聽到broker這個(gè)詞。它可以實(shí)現(xiàn)一下功能:
- 接收來(lái)自客戶端的網(wǎng)絡(luò)連接;
- 接受客戶發(fā)布的應(yīng)用信息;
- 處理來(lái)自客戶端主題訂閱和退訂請(qǐng)求;
- 向訂閱的客戶端轉(zhuǎn)發(fā)應(yīng)用程序消息。
MQTT協(xié)議中的方法
MQTT和HTTP一樣,也定義了一些動(dòng)作,來(lái)表示對(duì)確定資源進(jìn)行操作。
- Connect,等待于服務(wù)器建立連接;
- Disconnect,等待客戶端完成所做的工作,并與服務(wù)器斷開TCP/IP會(huì)話;
- Subscribe,主題訂閱;
- UnSubscribe,主題取消訂閱;
- Publish,發(fā)送消息。
4.HTTP輪詢
HTTP輪詢就是在一個(gè)給定的時(shí)間間隔后,定時(shí)向服務(wù)器發(fā)送請(qǐng)求,查看是否有新的數(shù)據(jù)。
HTTP輪詢的優(yōu)點(diǎn)是:實(shí)現(xiàn)簡(jiǎn)單、可控性強(qiáng),部署硬件成本低。
HTTP輪詢的缺點(diǎn)是:實(shí)時(shí)性差,只有時(shí)間到了才會(huì)向服務(wù)器查看是否有新的數(shù)據(jù)。兩次請(qǐng)求之間的時(shí)間間隔過(guò)大,則失去了即時(shí)推送的意義。但如果設(shè)置的時(shí)間間隔較短的,又會(huì)費(fèi)電費(fèi)流量。
5.第三方推送
在推送這一分支領(lǐng)域有許許多多的第三方推送服務(wù),例如:極光,個(gè)推等。
優(yōu)點(diǎn)是:集成方便。
缺點(diǎn)是:大量推送數(shù)據(jù)后,付費(fèi)服務(wù)是在所難免。而且因?yàn)槭峭ㄓ霉蚕碓疲阅愕姆?wù)質(zhì)量是否有保證,也就不能要求太多了,必竟你一毛錢也沒(méi)出或者也不打算出。
IM實(shí)現(xiàn)
第一種方式,使用第三方IM服務(wù)
國(guó)內(nèi)IM的第三方服務(wù)商有很多,類似云信、環(huán)信、融云、LeanCloud
- 第三方服務(wù)商IM底層協(xié)議基本上都是TCP。他們的IM方案很成熟,有了它們,我們甚至不需要自己去搭建IM后臺(tái),什么都不需要去考慮。
如果你足夠懶,甚至連UI都不需要自己做,這些第三方有各自一套IM的UI,拿來(lái)就可以直接用。真可謂3分鐘集成... - 但是缺點(diǎn)也很明顯,定制化程度太高,很多東西我們不可控。當(dāng)然還有一個(gè)最最重要的一點(diǎn),就是太貴了...作為真正社交為主打的APP,僅此一點(diǎn),就足以讓我們望而卻步。
另外一種方式,我們自己去實(shí)現(xiàn)
我們自己去實(shí)現(xiàn)也有很多選擇:
1)首先面臨的就是傳輸協(xié)議的選擇,TCP還是UDP?
結(jié)論吧:對(duì)于小公司或者技術(shù)不那么成熟的公司,IM一定要用TCP來(lái)實(shí)現(xiàn),因?yàn)槿绻阋肬DP的話,需要做的事太多。當(dāng)然QQ就是用的UDP協(xié)議,當(dāng)然不僅僅是UDP,騰訊還用了自己的私有協(xié)議,來(lái)保證了傳輸?shù)目煽啃?,杜絕了UDP下各種數(shù)據(jù)丟包,亂序等等一系列問(wèn)題。
2)其次是我們需要去選擇使用哪種聊天協(xié)議:
基于Scoket或者WebScoket或者其他的私有協(xié)議、
MQTT
還是廣為人詬病的XMPP?
基于Scoket原生:代表框架 CocoaAsyncSocket。
基于WebScoket:代表框架 SocketRocket。
基于MQTT:代表框架 MQTTKit。
基于XMPP:代表框架 XMPPFramework。
其中MQTT和XMPP為聊天協(xié)議,它們是最上層的協(xié)議,而WebScoket是傳輸通訊協(xié)議,它是基于Socket封裝的一個(gè)協(xié)議。而通常我們所說(shuō)的騰訊IM的私有協(xié)議,就是基于WebScoket或者Scoket原生進(jìn)行封裝的一個(gè)聊天協(xié)議。
說(shuō)到底,iOS要做一個(gè)真正的IM產(chǎn)品,一般都是基于Scoket或者WebScoket等,再之上加上一些私有協(xié)議來(lái)保證的
3)我們是自己去基于OS底層Socket進(jìn)行封裝還是在第三方框架的基礎(chǔ)上進(jìn)行封裝?
4)傳輸數(shù)據(jù)的格式,我們是用Json、還是XML、還是谷歌推出的ProtocolBuffer?
使用 ProtocolBuffer 減少 Payload
滴滴打車40%;
攜程之前分享過(guò),說(shuō)是采用新的Protocol Buffer數(shù)據(jù)格式+Gzip壓縮后的Payload大小降低了15%-45%。數(shù)據(jù)序列化耗時(shí)下降了80%-90%。
采用高效安全的私有協(xié)議,支持長(zhǎng)連接的復(fù)用,穩(wěn)定省電省流量
【高效】提高網(wǎng)絡(luò)請(qǐng)求成功率,消息體越大,失敗幾率隨之增加。
【省流量】流量消耗極少,省流量。一條消息數(shù)據(jù)用Protobuf序列化后的大小是 JSON 的1/10、XML格式的1/20、是二進(jìn)制序列化的1/10。同 XML 相比, Protobuf 性能優(yōu)勢(shì)明顯。它以高效的二進(jìn)制方式存儲(chǔ),比 XML 小 3 到 10 倍,快 20 到 100 倍。
【省電】省電
【高效心跳包】同時(shí)心跳包協(xié)議對(duì)IM的電量和流量影響很大,對(duì)心跳包協(xié)議上進(jìn)行了極簡(jiǎn)設(shè)計(jì):僅 1 Byte 。
【易于使用】開發(fā)人員通過(guò)按照一定的語(yǔ)法定義結(jié)構(gòu)化的消息格式,然后送給命令行工具,工具將自動(dòng)生成相關(guān)的類,可以支持java、c++、python、Objective-C等語(yǔ)言環(huán)境。通過(guò)將這些類包含在項(xiàng)目中,可以很輕松的調(diào)用相關(guān)方法來(lái)完成業(yè)務(wù)消息的序列化與反序列化工作。語(yǔ)言支持:原生支持c++、java、python、Objective-C等多達(dá)10余種語(yǔ)言。 2015-08-27 Protocol Buffers v3.0.0-beta-1中發(fā)布了Objective-C(Alpha)版本, 2016-07-28 3.0 Protocol Buffers v3.0.0正式版發(fā)布,正式支持 Objective-C。
【可靠】微信和手機(jī) QQ 這樣的主流 IM 應(yīng)用也早已在使用它(采用的是改造過(guò)的Protobuf協(xié)議)
5)我們還有一些細(xì)節(jié)問(wèn)題需要考慮,例如TCP的長(zhǎng)連接如何保持,心跳機(jī)制,Qos機(jī)制,重連機(jī)制等等...當(dāng)然,除此之外,我們還有一些安全問(wèn)題需要考慮。
1.我們先不使用任何框架,直接用OS底層Socket來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的IM。
我們客戶端的實(shí)現(xiàn)思路也是很簡(jiǎn)單,創(chuàng)建Socket,和服務(wù)器的Socket對(duì)接上,然后開始傳輸數(shù)據(jù)就可以了。
Socket編程,而Socket是什么呢,簡(jiǎn)單的來(lái)說(shuō),就是我們使用TCP/IP 或者UDP/IP協(xié)議的一組編程接口。Socket是網(wǎng)絡(luò)上運(yùn)行的兩個(gè)程序間雙向通訊的一端,它既可以接受請(qǐng)求,也可以發(fā)送請(qǐng)求,利用它可以較為方便的編寫網(wǎng)絡(luò)上數(shù)據(jù)的傳遞。
1.socket與進(jìn)程的關(guān)系
1).socket與進(jìn)程間的關(guān)系:socket 用來(lái)讓一個(gè)進(jìn)程和其他的進(jìn)程互通信息(IPC),而Socket接口是TCP/IP網(wǎng)絡(luò)的API接口函數(shù)。
2).進(jìn)程間通信(本機(jī)內(nèi))
進(jìn)程間通信(不同計(jì)算機(jī),要聯(lián)網(wǎng))
2、socket與文件的關(guān)系——如何理解socket是種特殊的I/O?
1)Socket最先應(yīng)用于Unix操作系統(tǒng),如果了解Unix系統(tǒng)的I/O的話,就很容易了解Socket了,因?yàn)镾ocket數(shù)據(jù)傳輸其實(shí)就是一種特殊的I/O。
2)可對(duì)其進(jìn)行文件操作
3)有文件描述符。而文件描述符的本質(zhì)是一個(gè)非負(fù)整數(shù)。只是用于區(qū)分。類似的還有進(jìn)程ID。
首先我們不基于任何框架,直接去調(diào)用OS底層-基于C的BSD Socket去實(shí)現(xiàn),它提供了這樣一組接口:
//socket 創(chuàng)建并初始化 socket,返回該 socket 的文件描述符,如果描述符為 -1 表示創(chuàng)建失敗。
int socket(int addressFamily, int type,int protocol)
//關(guān)閉socket連接
int close(int socketFileDescriptor)
//將 socket 與特定主機(jī)地址與端口號(hào)綁定,成功綁定返回0,失敗返回 -1。
int bind(int socketFileDescriptor,sockaddr *addressToBind,int addressStructLength)
//接受客戶端連接請(qǐng)求并將客戶端的網(wǎng)絡(luò)地址信息保存到 clientAddress 中。
int accept(int socketFileDescriptor,sockaddr *clientAddress, int clientAddressStructLength)
//客戶端向特定網(wǎng)絡(luò)地址的服務(wù)器發(fā)送連接請(qǐng)求,連接成功返回0,失敗返回 -1。
int connect(int socketFileDescriptor,sockaddr *serverAddress, int serverAddressLength)
//使用 DNS 查找特定主機(jī)名字對(duì)應(yīng)的 IP 地址。如果找不到對(duì)應(yīng)的 IP 地址則返回 NULL。
hostent* gethostbyname(char *hostname)
//通過(guò) socket 發(fā)送數(shù)據(jù),發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回 -1。
int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags)
//從 socket 中讀取數(shù)據(jù),讀取成功返回成功讀取的字節(jié)數(shù),否則返回 -1。
int receive(int socketFileDescriptor,char *buffer, int bufferLength, int flags)
//通過(guò)UDP socket 發(fā)送數(shù)據(jù)到特定的網(wǎng)絡(luò)地址,發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回 -1。
int sendto(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)
//從UDP socket 中讀取數(shù)據(jù),并保存發(fā)送者的網(wǎng)絡(luò)地址信息,讀取成功返回成功讀取的字節(jié)數(shù),否則返回 -1 。
int recvfrom(int socketFileDescriptor,char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength)
讓我們可以對(duì)socket進(jìn)行各種操作,首先我們來(lái)用它寫個(gè)客戶端。總結(jié)一下,簡(jiǎn)單的IM客戶端需要做如下4件事:
客戶端調(diào)用 socket(...) 創(chuàng)建socket;
客戶端調(diào)用 connect(...) 向服務(wù)器發(fā)起連接請(qǐng)求以建立連接;
客戶端與服務(wù)器建立連接之后,就可以通過(guò)send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);
客戶端調(diào)用 close 關(guān)閉 socket;
服務(wù)端需要做的工作簡(jiǎn)單的總結(jié)下:
服務(wù)器調(diào)用 socket(...) 創(chuàng)建socket;
服務(wù)器調(diào)用 listen(...) 設(shè)置緩沖區(qū);
服務(wù)器通過(guò) accept(...)接受客戶端請(qǐng)求建立連接;
服務(wù)器與客戶端建立連接之后,就可以通過(guò) send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);
服務(wù)器調(diào)用 close 關(guān)閉 socket;
心跳
心跳就是用來(lái)檢測(cè)TCP連接的雙方是否可用。那又會(huì)有人要問(wèn)了,TCP不是本身就自帶一個(gè)KeepAlive機(jī)制嗎?
這里我們需要說(shuō)明的是TCP的KeepAlive機(jī)制只能保證連接的存在,但是并不能保證客戶端以及服務(wù)端的可用性.比如會(huì)有以下一種情況:
某臺(tái)服務(wù)器因?yàn)槟承┰驅(qū)е仑?fù)載超高,CPU 100%,無(wú)法響應(yīng)任何業(yè)務(wù)請(qǐng)求,但是使用 TCP 探針則仍舊能夠確定連接狀態(tài),這就是典型的連接活著但業(yè)務(wù)提供方已死的狀態(tài)。
這個(gè)時(shí)候心跳機(jī)制就起到作用了:
- 我們客戶端發(fā)起心跳Ping(一般都是客戶端),假如設(shè)置在10秒后如果沒(méi)有收到回調(diào),那么說(shuō)明服務(wù)器或者客戶端某一方出現(xiàn)問(wèn)題,這時(shí)候我們需要主動(dòng)斷開連接。
- 服務(wù)端也是一樣,會(huì)維護(hù)一個(gè)socket的心跳間隔,當(dāng)約定時(shí)間內(nèi),沒(méi)有收到客戶端發(fā)來(lái)的心跳,我們會(huì)知道該連接已經(jīng)失效,然后主動(dòng)斷開連接。
NAT超時(shí)
我們真正需要心跳機(jī)制的原因其實(shí)主要是在于國(guó)內(nèi)運(yùn)營(yíng)商N(yùn)AT超時(shí)。
原來(lái)這是因?yàn)镮PV4引起的,我們上網(wǎng)很可能會(huì)處在一個(gè)NAT設(shè)備(無(wú)線路由器之類)之后。
NAT設(shè)備會(huì)在IP封包通過(guò)設(shè)備時(shí)修改源/目的IP地址. 對(duì)于家用路由器來(lái)說(shuō), 使用的是網(wǎng)絡(luò)地址端口轉(zhuǎn)換(NAPT), 它不僅改IP, 還修改TCP和UDP協(xié)議的端口號(hào), 這樣就能讓內(nèi)網(wǎng)中的設(shè)備共用同一個(gè)外網(wǎng)IP.
NAT設(shè)備會(huì)根據(jù)NAT表對(duì)出去和進(jìn)來(lái)的數(shù)據(jù)做修改, 比如將192.168.0.3:8888發(fā)出去的封包改成120.132.92.21:9202, 外部就認(rèn)為他們是在和120.132.92.21:9202通信. 同時(shí)NAT設(shè)備會(huì)將120.132.92.21:9202收到的封包的IP和端口改成192.168.0.3:8888, 再發(fā)給內(nèi)網(wǎng)的主機(jī), 這樣內(nèi)部和外部就能雙向通信了, 但如果其中192.168.0.3:8888 == 120.132.92.21:9202這一映射因?yàn)槟承┰虮籒AT設(shè)備淘汰了, 那么外部設(shè)備就無(wú)法直接與192.168.0.3:8888通信了。
我們的設(shè)備經(jīng)常是處在NAT設(shè)備的后面, 比如在大學(xué)里的校園網(wǎng), 查一下自己分配到的IP, 其實(shí)是內(nèi)網(wǎng)IP, 表明我們?cè)贜AT設(shè)備后面, 如果我們?cè)趯嬍以俳觽€(gè)路由器, 那么我們發(fā)出的數(shù)據(jù)包會(huì)多經(jīng)過(guò)一次NAT.
國(guó)內(nèi)移動(dòng)無(wú)線網(wǎng)絡(luò)運(yùn)營(yíng)商在鏈路上一段時(shí)間內(nèi)沒(méi)有數(shù)據(jù)通訊后, 會(huì)淘汰NAT表中的對(duì)應(yīng)項(xiàng), 造成鏈路中斷。
而國(guó)內(nèi)的運(yùn)營(yíng)商一般NAT超時(shí)的時(shí)間為5分鐘,所以通常我們心跳設(shè)置的時(shí)間間隔為3-5分鐘。
PingPong機(jī)制
我們?cè)谶@心跳間隔的3-5分鐘如果連接假在線(例如在地鐵電梯這種環(huán)境下)。那么我們豈不是無(wú)法保證消息的即時(shí)性么?這顯然是我們無(wú)法接受的,所以業(yè)內(nèi)的解決方案是采用雙向的PingPong機(jī)制。
當(dāng)服務(wù)端發(fā)出一個(gè)Ping,客戶端沒(méi)有在約定的時(shí)間內(nèi)返回響應(yīng)的ack,則認(rèn)為客戶端已經(jīng)不在線,這時(shí)我們Server端會(huì)主動(dòng)斷開Scoket連接,并且改由APNS推送的方式發(fā)送消息。
同樣的是,當(dāng)客戶端去發(fā)送一個(gè)消息,因?yàn)槲覀冞t遲無(wú)法收到服務(wù)端的響應(yīng)ack包,則表明客戶端或者服務(wù)端已不在線,我們也會(huì)顯示消息發(fā)送失敗,并且斷開Scoket連接。
還記得我們之前CocoaSyncSockt的例子所講的獲取消息超時(shí)就斷開嗎?其實(shí)它就是一個(gè)PingPong機(jī)制的客戶端實(shí)現(xiàn)。我們每次可以在發(fā)送消息成功后,調(diào)用這個(gè)超時(shí)讀取的方法,如果一段時(shí)間沒(méi)收到服務(wù)器的響應(yīng),那么說(shuō)明連接不可用,則斷開Scoket連接
重連機(jī)制
理論上,我們自己主動(dòng)去斷開的Scoket連接(例如退出賬號(hào),APP退出到后臺(tái)等等),不需要重連。其他的連接斷開,我們都需要進(jìn)行斷線重連。
一般解決方案是嘗試重連幾次,如果仍舊無(wú)法重連成功,那么不再進(jìn)行重連。
接下來(lái)的WebScoket的例子,我會(huì)封裝一個(gè)重連時(shí)間指數(shù)級(jí)增長(zhǎng)的一個(gè)重連方式,可以作為一個(gè)參考。
WebScoket最具代表性的一個(gè)第三方框架SocketRocket
//重連機(jī)制
- (void)reConnect
{
[self disConnect]; // 斷開連接
//超過(guò)一分鐘就不再重連 所以只會(huì)重連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];
});
//重連時(shí)間2的指數(shù)級(jí)增長(zhǎng)
if (reConnectTime == 0) {
reConnectTime = 2;
}else{
reConnectTime *= 2;
}
}
MQTT:
MQTT是一個(gè)聊天協(xié)議,它比webScoket更上層,屬于應(yīng)用層。
它的基本模式是簡(jiǎn)單的發(fā)布訂閱,也就是說(shuō)當(dāng)一條消息發(fā)出去的時(shí)候,誰(shuí)訂閱了誰(shuí)就會(huì)受到。其實(shí)它并不適合IM的場(chǎng)景,例如用來(lái)實(shí)現(xiàn)有些簡(jiǎn)單IM場(chǎng)景,卻需要很大量的、復(fù)雜的處理。
比較適合它的場(chǎng)景為訂閱發(fā)布這種模式的,例如微信的實(shí)時(shí)共享位置,滴滴的地圖上小車的移動(dòng)、客戶端推送等功能。
首先我們來(lái)看看基于MQTT協(xié)議的框架-MQTTKit:
需要說(shuō)一下的是:
1)當(dāng)我們連接成功了,我們需要去訂閱自己clientID的消息,這樣才能收到發(fā)給自己的消息。
2)其次是這個(gè)框架為我們實(shí)現(xiàn)了一個(gè)QOS機(jī)制,那么什么是QOS呢?
QoS(Quality of Service,服務(wù)質(zhì)量)指一個(gè)網(wǎng)絡(luò)能夠利用各種基礎(chǔ)技術(shù),為指定的網(wǎng)絡(luò)通信提供更好的服務(wù)能力, 是網(wǎng)絡(luò)的一種安全機(jī)制, 是用來(lái)解決網(wǎng)絡(luò)延遲和阻塞等問(wèn)題的一種技術(shù)。
在這里,它提供了三個(gè)選項(xiàng):
typedef enum MQTTQualityOfService : NSUInteger {
AtMostOnce,
AtLeastOnce,
ExactlyOnce
} MQTTQualityOfService;
分別對(duì)應(yīng)最多發(fā)送一次,至少發(fā)送一次,精確只發(fā)送一次。
QOS(0),最多發(fā)送一次:如果消息沒(méi)有發(fā)送過(guò)去,那么就直接丟失。
QOS(1),至少發(fā)送一次:保證消息一定發(fā)送過(guò)去,但是發(fā)幾次不確定。
QOS(2),精確只發(fā)送一次:它內(nèi)部會(huì)有一個(gè)很復(fù)雜的發(fā)送機(jī)制,確保消息送到,而且只發(fā)送一次。
IM一些其它問(wèn)題
1.IM的可靠性:
我們之前穿插在例子中提到過(guò):
心跳機(jī)制、PingPong機(jī)制、斷線重連機(jī)制、還有我們后面所說(shuō)的QOS機(jī)制。這些被用來(lái)保證連接的可用,消息的即時(shí)與準(zhǔn)確的送達(dá)等等。
上述內(nèi)容保證了我們IM服務(wù)時(shí)的可靠性,其實(shí)我們能做的還有很多:比如我們?cè)诖笪募鬏數(shù)臅r(shí)候使用分片上傳、斷點(diǎn)續(xù)傳、秒傳技術(shù)等來(lái)保證文件的傳輸。
2.安全性:
我們通常還需要一些安全機(jī)制來(lái)保證我們IM通信安全。
例如:防止 DNS 污染、帳號(hào)安全、第三方服務(wù)器鑒權(quán)、單點(diǎn)登錄等等
3.一些其他的優(yōu)化:
類似微信,服務(wù)器不做聊天記錄的存儲(chǔ),只在本機(jī)進(jìn)行緩存,這樣可以減少對(duì)服務(wù)端數(shù)據(jù)的請(qǐng)求,一方面減輕了服務(wù)器的壓力,另一方面減少客戶端流量的消耗。
我們進(jìn)行http連接的時(shí)候盡量采用上層API,類似NSUrlSession。而網(wǎng)絡(luò)框架盡量使用AFNetWorking3。因?yàn)檫@些上層網(wǎng)絡(luò)請(qǐng)求都用的是HTTP/2 ,我們請(qǐng)求的時(shí)候可以復(fù)用這些連接。
音視頻通話
IM應(yīng)用中的實(shí)時(shí)音視頻技術(shù),幾乎是IM開發(fā)中的最后一道高墻。原因在于:實(shí)時(shí)音視頻技術(shù) = 音視頻處理技術(shù) + 網(wǎng)絡(luò)傳輸技術(shù) 的橫向技術(shù)應(yīng)用集合體,而公共互聯(lián)網(wǎng)不是為了實(shí)時(shí)通信設(shè)計(jì)的。
實(shí)時(shí)音視頻技術(shù)上的實(shí)現(xiàn)內(nèi)容主要包括:音視頻的采集、編碼、網(wǎng)絡(luò)傳輸、解碼、播放等環(huán)節(jié)。