场景
要做的产品 --- 炒股app
- 数据量大:5000多支股票,每支股票有分时,分笔数据,画一条k线,可能要500条分时数据,甚至更多。
- 实时性:股价每分每秒都在变化,一分钟产生很多条数据,用户要看到最新的信息,真的是一秒钟上下几十万啊。
- 服务器主动推:股票预警等等一些重要消息,需要服务器实时推送,保证客户端100%收到,收不到可能造成客户资金损失。
由于这些场景因素,股票的数据不能通过HTTP协议来传输了,只能走TCP协议了,当然一些个人信息什么的还是走HTTP的。
技术细节实现
1.数据量比较大,一次请求可能返回几百条数据到移动端,对数据要进行压缩,这里采用了 google 的 Protocol Buffer 数据传输格式,因为它对象序列化速度快,压缩率高 (ps: http 一般用 json, xml)。
2.实时性与主动推:服务器与客户端之间维护一条 tcp 长连接,避免每次3次握手,4次挥手,和产生一堆 time_wait 状态的 socket 占用资源。走tcp协议需要自己维护一些状态
掉线重连,移动端网络情况复杂,有3G,4G,wifi,socket 经常断开,它断了要自动连上,并且还要对应用层透明!不能让应用层感知到!连上后数据接着之前断开的地方发送,不能有影响。
自动登录,这个是掉线重连后要做的一个操作,不登录拿不到股票数据。登录后接着做一个消息同步,看看有没有新消息。
3.客户端100%收到(可靠性):除了 tcp 超时重传和3次ack回应保证了可靠传输之外,我们在app里也实现了一套应用层的ack机制,服务器保存了一组消息,每个消息有个seq号,一个消息推给客户端后,客户端拿到消息的seq,要发ack请求回应服务器这个消息,然后下次服务器才不会推给你,不然的话,下次服务器还是会推给你的。掉线重连,重登录后会同步一次你订阅过的消息,把上次没收到的,或者丢包的消息再同步过来。
4.客户端的负载均衡,这个一般在服务器做,但后台那边说防止ip封锁,还有某些服务器不能在全国访问到,所以把负载均衡放在客户端做了,app启动拉取服务器列表,然后多线程并发发送测速包,得到每个服务器延迟和负载,然后选个最优的服务器连上即可,延迟可以理解为路有多长,负载可以理解为路上拥堵情况,显然最短的路不一定是最快的,它可能很堵。
根据以上因素,我设计了一套TCP层的网络请求逻辑,应用层只管发请求就好了,我只告诉你有没有结果,上面的细节全部屏蔽在底层,对应用层透明!
// 应用层发一个 tcp 请求的代码例子,支持多线程循环发:
[[TCPAPI instance] requestStockInfoWithcompletion:^(id response, NSString *error) {
if (error == nil) {
NSLog(@"成功:%@", (NSArray *)response);
} else {
NSLog(@"失败:%@", error);
}
}];
测试:我用一条线程以每隔 0.1 秒的速度循环发送请求,然后模拟断网,网络差,被服务器踢下线等等情况,程序工作正常,一旦有网络,数据就会成功返回。
整个app网络层的请求逻辑图:
我有个 tcp 请求同步队列接收应用层来的请求,有句话叫做"we cannot send directly, so queue it",然后一个请求走完图中的逻辑,下一个请求接着走,每一个请求有一个定时器,超时就回调一个失败结果给应用层,有张映射表保存请求和回调之间的映射。
然后还一个socket发送队列,真正去发送数据的队列,socket接收队列就不用说了吧,把接收到的数据通过pb协议反序列化得到pb对象的,这里有个 tcp 分片机制导致的 tcp 粘包问题,就是 socket 返回一段二进制数据不一定刚好就是一个对象的全部数据,可能少可能多,要自己判断,拆包的时候注意下就好了。
得到 pb 对象,把这个结果回调给应用层,整个流程就走完了。
图片可能太大,可以到这里下载来看。