場(chǎng)景
要做的產(chǎn)品 --- 炒股app
- 數(shù)據(jù)量大:5000多支股票,每支股票有分時(shí),分筆數(shù)據(jù),畫一條k線,可能要500條分時(shí)數(shù)據(jù),甚至更多。
- 實(shí)時(shí)性:股價(jià)每分每秒都在變化,一分鐘產(chǎn)生很多條數(shù)據(jù),用戶要看到最新的信息,真的是一秒鐘上下幾十萬(wàn)啊。
- 服務(wù)器主動(dòng)推:股票預(yù)警等等一些重要消息,需要服務(wù)器實(shí)時(shí)推送,保證客戶端100%收到,收不到可能造成客戶資金損失。
由于這些場(chǎng)景因素,股票的數(shù)據(jù)不能通過(guò)HTTP協(xié)議來(lái)傳輸了,只能走TCP協(xié)議了,當(dāng)然一些個(gè)人信息什么的還是走HTTP的。
技術(shù)細(xì)節(jié)實(shí)現(xiàn)
1.數(shù)據(jù)量比較大,一次請(qǐng)求可能返回幾百條數(shù)據(jù)到移動(dòng)端,對(duì)數(shù)據(jù)要進(jìn)行壓縮,這里采用了 google 的 Protocol Buffer 數(shù)據(jù)傳輸格式,因?yàn)樗鼘?duì)象序列化速度快,壓縮率高 (ps: http 一般用 json, xml)。
2.實(shí)時(shí)性與主動(dòng)推:服務(wù)器與客戶端之間維護(hù)一條 tcp 長(zhǎng)連接,避免每次3次握手,4次揮手,和產(chǎn)生一堆 time_wait 狀態(tài)的 socket 占用資源。走tcp協(xié)議需要自己維護(hù)一些狀態(tài)
掉線重連,移動(dòng)端網(wǎng)絡(luò)情況復(fù)雜,有3G,4G,wifi,socket 經(jīng)常斷開(kāi),它斷了要自動(dòng)連上,并且還要對(duì)應(yīng)用層透明!不能讓應(yīng)用層感知到!連上后數(shù)據(jù)接著之前斷開(kāi)的地方發(fā)送,不能有影響。
自動(dòng)登錄,這個(gè)是掉線重連后要做的一個(gè)操作,不登錄拿不到股票數(shù)據(jù)。登錄后接著做一個(gè)消息同步,看看有沒(méi)有新消息。
3.客戶端100%收到(可靠性):除了 tcp 超時(shí)重傳和3次ack回應(yīng)保證了可靠傳輸之外,我們?cè)赼pp里也實(shí)現(xiàn)了一套應(yīng)用層的ack機(jī)制,服務(wù)器保存了一組消息,每個(gè)消息有個(gè)seq號(hào),一個(gè)消息推給客戶端后,客戶端拿到消息的seq,要發(fā)ack請(qǐng)求回應(yīng)服務(wù)器這個(gè)消息,然后下次服務(wù)器才不會(huì)推給你,不然的話,下次服務(wù)器還是會(huì)推給你的。掉線重連,重登錄后會(huì)同步一次你訂閱過(guò)的消息,把上次沒(méi)收到的,或者丟包的消息再同步過(guò)來(lái)。
4.客戶端的負(fù)載均衡,這個(gè)一般在服務(wù)器做,但后臺(tái)那邊說(shuō)防止ip封鎖,還有某些服務(wù)器不能在全國(guó)訪問(wèn)到,所以把負(fù)載均衡放在客戶端做了,app啟動(dòng)拉取服務(wù)器列表,然后多線程并發(fā)發(fā)送測(cè)速包,得到每個(gè)服務(wù)器延遲和負(fù)載,然后選個(gè)最優(yōu)的服務(wù)器連上即可,延遲可以理解為路有多長(zhǎng),負(fù)載可以理解為路上擁堵情況,顯然最短的路不一定是最快的,它可能很堵。
根據(jù)以上因素,我設(shè)計(jì)了一套TCP層的網(wǎng)絡(luò)請(qǐng)求邏輯,應(yīng)用層只管發(fā)請(qǐng)求就好了,我只告訴你有沒(méi)有結(jié)果,上面的細(xì)節(jié)全部屏蔽在底層,對(duì)應(yīng)用層透明!
// 應(yīng)用層發(fā)一個(gè) tcp 請(qǐng)求的代碼例子,支持多線程循環(huán)發(fā):
[[TCPAPI instance] requestStockInfoWithcompletion:^(id response, NSString *error) {
if (error == nil) {
NSLog(@"成功:%@", (NSArray *)response);
} else {
NSLog(@"失?。?@", error);
}
}];
測(cè)試:我用一條線程以每隔 0.1 秒的速度循環(huán)發(fā)送請(qǐng)求,然后模擬斷網(wǎng),網(wǎng)絡(luò)差,被服務(wù)器踢下線等等情況,程序工作正常,一旦有網(wǎng)絡(luò),數(shù)據(jù)就會(huì)成功返回。
整個(gè)app網(wǎng)絡(luò)層的請(qǐng)求邏輯圖:
我有個(gè) tcp 請(qǐng)求同步隊(duì)列接收應(yīng)用層來(lái)的請(qǐng)求,有句話叫做"we cannot send directly, so queue it",然后一個(gè)請(qǐng)求走完圖中的邏輯,下一個(gè)請(qǐng)求接著走,每一個(gè)請(qǐng)求有一個(gè)定時(shí)器,超時(shí)就回調(diào)一個(gè)失敗結(jié)果給應(yīng)用層,有張映射表保存請(qǐng)求和回調(diào)之間的映射。
然后還一個(gè)socket發(fā)送隊(duì)列,真正去發(fā)送數(shù)據(jù)的隊(duì)列,socket接收隊(duì)列就不用說(shuō)了吧,把接收到的數(shù)據(jù)通過(guò)pb協(xié)議反序列化得到pb對(duì)象的,這里有個(gè) tcp 分片機(jī)制導(dǎo)致的 tcp 粘包問(wèn)題,就是 socket 返回一段二進(jìn)制數(shù)據(jù)不一定剛好就是一個(gè)對(duì)象的全部數(shù)據(jù),可能少可能多,要自己判斷,拆包的時(shí)候注意下就好了。
得到 pb 對(duì)象,把這個(gè)結(jié)果回調(diào)給應(yīng)用層,整個(gè)流程就走完了。

圖片可能太大,可以到這里下載來(lái)看。