Andoird TCP通讯

前言

最近在写一个即时通讯的项目,有一些心得,写出来给大家分享指正一下。

简单描述一下这个项目:
  • 实时查询车辆运行状态的项目,走TCP通迅。
  • 接口采用GZIP压缩。
  • 后台是通过Apache的Mina框架
  • 每隔30秒需要发一个心跳包来维持在线状态,如果服务器长时间收不到心跳包,会主动断开链接。
  • 客户端发送命令消息均采用Protobuff3.0协议进行封装。

关于Protobuff3.0不太懂的,可以看一下我上一篇简书Proto3 语言指南

关于此项目会遇到的难点
  • APP的保活
  • 目前越来越多国产手机Rom大多都有像小米那样的神隐模式,长连接就根本无法持续。
  • 耗电(在android中耗电主要是因为占用cpu时间长和一些感应器的使用,而长连接基本上分分秒秒都在跑着两个线程:一个接收一个发送数据)
  • TCP粘包问题(就是发送方发送的多个数据包,到接收方后粘连在一起,导致数据包不能完整的体现发送的数据:可能是发送方的原因,也有可能是接受方的原因。)

解决TCP粘包
自定协议,将数据包分为了封包和解包两个过程。在发送方发送数据时,对发送的数据进行封包操作。在接收方接收到数据时对接收的数据包需要进行解包操作。
自定协议时,封包就是为发送的数据增加包头,包头包含数据的大小的信息,数据就跟随在包头之后。当然包头也可以有其他的信息,比如一些做校验的信息。

针对这个项目,我们来分解需要确定的知识点,如下:

关于Android推送的几种方式

推送就像订餐一样,只要留下你的地址, 送餐员就能如期把饭送到你手里。但在目前的网络上,这是办不到的,因为不是每个人都有一个唯一的地址,服务器想要给我们推送一条消息, 必须知道我们的地址,但服务器不知道我们在哪。

关于推送的常用几种方案:

轮询

客户端定期询问服务器有没有新的消息,这样服务器不用管客户端的地址是什么,客户端来问,直接告诉它就行。

这种方案最简单,对于一些不追求实时性的客户端来说,很适合,只需要把时间间隔设定成几个小时取一次, 就能很方便的解决问题。

但对于即时通讯产品来说, 这种方案完全不能用。 假设即时通讯软件在网络畅通的情况下发送的消息要求对方10s内就能收到, 如果用轮询,那么客户端要每隔5s连一次服务器, 如果在移动端, 手机的电量和流量很快就会被消耗殆尽。

定时广播机制实现

我们可以设定广播时间然后再广播接收器中发送心跳包,这个心跳包我们可以直接发送不适用线程,对于发送心跳来说比较频繁,使用线程还是会耗电,第二,我们心跳其实不需要一天到晚得发送,我们可以在用户使用完或者锁屏后25分钟就暂停发送,然后再过25分钟唤醒连接看看有没有消息有就接收,没有继续断开,如果用户打开应用到停止使用有等待25分钟断开然后再连接查看离线消息,这一个循环又能保证新消息的接收又不会一直占用CPU。

长连接

这大概是目前情况下最佳的方案了,,客户端主动和服务器建立TCP长连接之后, 客户端定期向服务器发送心跳包,有消息的时候,服务器直接通过这个已经建立好的TCP连接通知客户端。

下面普及一些概念:

  • 短连接:是通讯双方有数据交互时就建立一个连接, 数据发送完成后,则断开此连接
  • 长连接:大家建立连接之后, 不主动断开。 双方互相发送数据,发完了也不主动断开连接, 之后有需要发送的数据就继续通过这个连接发送。TCP连接在默认的情况下就是所谓的长连接。(也就是说连接双方都不主动关闭连接, 这个连接就应该一直存在)

但是一些外在的因素也会将长连接断开,例如或者服务器宕机、客户端网络异、手机网络和WIFI网络切换(IP不一样了)、DHCP的租期等等。

关于手机网络和WIFI网络切换为什么会导致长连接断开?

因为平时我们使用的NAT设备最常见就是路由器。NAT设备会在IP封包通过设备时修改源/目的IP地址:它不仅改IP, 还修改TCP和UDP协议的端口号,这样就能让内网中的设备共用同一个外网IP。 举个例子, NAPT维护一个类似下表的NAT表

NAT设备会根据NAT表对出去和进来的数据做修改,比如将192.168.0.3:8888发出去的封包改成120.132.92.21:9202,外部就认为他们是在和120.132.92.21:9202通信。 同时NAT设备会将120.132.92.21:9202收到的封包的IP和端口改成192.168.0.3:8888, 再发给内网的主机, 这样内部和外部就能双向通信了,但如果其中192.168.0.3:8888 == 120.132.92.21:9202这一映射因为某些原因被NAT设备淘汰了, 那么外部设备就无法直接与192.168.0.3:8888通信了。

关于DHCP的租期

目前测试发现安卓系统对DHCP的处理有Bug, DHCP租期到了不会主动续约并且会继续使用过期IP, 这个问题会造成TCP长连接偶然的断连。

TCP长连接本质上不需要心跳包来维持

TCP是有保活定时器的,默认是用保活定时器来维持长连接,保活定时器的周期是两小时。
那为什么要有心跳包呢? 1个服务端会对应很多客户端,如果都用保活定时器心跳,那么这些客户端在两小时内都会维持一个长连接,那么问题来了,很多长链接是可以关闭的,而且服务器带宽有限,所以需要心跳包,来及时把不需要维持的链接关闭掉。所以需要手动发心跳。保活定时器的两小时是在是太长了。

心跳包的时间间隔

发送心跳包势必要先唤醒设备, 然后才能发送, 如果唤醒设备过于频繁, 或者直接导致设备无法休眠, 会大量消耗电量, 而且移动网络下进行网络通信, 比在wifi下耗电得多. 所以这个心跳包的时间间隔应该尽量的长, 最理想的情况就是根本没有NAT超时, 比如刚才我说的两台在同一个wifi下的电脑, 完全不需要心跳包. 这也就是网上常说的长连接, 慢心跳.

根据网上的一些说法, 中移动2/3G下, NAT超时时间为5分钟, 中国电信3G则大于28分钟, 理想的情况下, 客户端应当以略小于NAT超时时间的间隔来发送心跳包。

所以心跳间隔逼近NAT超时的间隔, 同时自动适应NAT超时间隔的变化。

服务器如何处理心跳包

如果客户端心跳间隔是固定的,那么服务器在连接闲置超过这个时间还没收到心跳时, 可以认为对方掉线, 关闭连接。 如果客户端心跳会动态改变, 如上节提到的微信心跳方案, 应当设置一个最大值, 超过这个最大值才认为对方掉线。 还有一种情况就是服务器通过TCP连接主动给客户端发消息出现写超时, 可以直接认为对方掉线。

心跳包和轮询的区别

心跳包和轮询看起来类似, 都是客户端主动联系服务器, 但是区别很大。

  • 轮询是为了获取数据, 而心跳是为了保活TCP连接.
  • 轮询得越频繁, 获取数据就越及时, 心跳的频繁与否和数据是否及时没有直接关系
  • 轮询比心跳能耗更高, 因为一次轮询需要经过TCP三次握手, 四次挥手, 单次心跳不需要建立和拆除TCP连接.
关于Android TCP 耗电

首先Android手机有两个处理器, 一个叫Application Processor(AP), 一个叫Baseband Processor(BP). AP是ARM架构的处理器,用于运行Android系统; BP用于运行实时操作系统(RTOS), 通讯协议栈运行于BP的RTOS之上. 非通话时间, BP的能耗基本上在5mA左右,而AP只要处于非休眠状态, 能耗至少在50mA以上, 执行图形运算时会更高. 另外LCD工作时功耗在100mA左右, WIFI也在100mA左右. 一般手机待机时, AP, LCD, WIFI均进入休眠状态, 这时Android中应用程序的代码也会停止执行。

Android为了确保应用程序中关键代码的正确执行, 提供了Wake Lock的API, 使得应用程序有权限通过代码阻止AP进入休眠状态. 但如果不领会Android设计者的意图而滥用Wake Lock API, 为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态, 就会成为待机电池杀手.

完全没必要担心AP休眠会导致收不到消息推送。通讯协议栈运行于BP,一旦收到数据包, BP会将AP唤醒, 唤醒的时间足够AP执行代码完成对收到的数据包的处理过程. 其它的如Connectivity事件触发时AP同样会被唤醒. 那么唯一的问题就是程序如何执行向服务器发送心跳包的逻辑. 你显然不能靠AP来做心跳计时. Android提供的Alarm Manager就是来解决这个问题的. Alarm应该是BP计时(或其它某个带石英钟的芯片,不太确定,但绝对不是AP), 触发时唤醒AP执行程序代码. 那么Wake Lock API有啥用呢? 比如心跳包从请求到应答, 比如断线重连重新登陆这些关键逻辑的执行过程, 就需要Wake Lock来保护. 而一旦一个关键逻辑执行成功, 就应该立即释放掉Wake Lock了. 两次心跳请求间隔5到10分钟, 基本不会怎么耗电. 除非网络不稳定. 频繁断线重连, 那种情况办法不多.

耗电跟push这块,还有一个心跳对齐的功能。这也是推荐用开源项目的原因。比如说多个app都集成了个推,这些app会公用同一个心跳包,来节省耗电之类的。

关于Mina可能遇到的问题

一个是Android端发一个汉字给服务器, 服务器filter崩溃, 发超过一个汉字, 客户端filter崩溃, 写个IoFilter做一下编解码就好了. 另外User Guide里面的代码也有错误. 第二个是IoSessionConfig的写超时设置了完全不起作用。

关于小米手机Socket问题

后来又发现客户端只要在后台超过一定时间,对socket的写操作就会变得非常诡异, 表现为socket把数据吞了, 告知应用数据已经被对方接收,但是服务器什么都没收到,而且服务器发送的消息客户端也收不到。只要让app进到前台,之前消失的数据会一股脑发给服务器, 客户端会收到服务器重传的消息。

这个bug的临时解决方案是:用神隐模式里的自定义配置, 把自己想改的设置好就行。

参考文章
Android微信智能心跳方案
android设备休眠
Optimizing Downloads for Efficient Network Access
关于socket长连接的心跳包
Network address translation
C/C++网络编程中的TCP保活
TCP/IP,http,socket,长连接,短连接——小结。
Android实现推送方式解决方案

[1] 网络编程基础资料:

TCP/IP详解-第11章·UDP:用户数据报协议
TCP/IP详解-第17章·TCP:传输控制协议
TCP/IP详解-第18章·TCP连接的建立与终止
TCP/IP详解-第21章·TCP的超时与重传
理论经典:TCP协议的3次握手与4次挥手过程详解
理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程
计算机网络通讯协议关系图(中文珍藏版)
NAT详解:基本原理、穿越技术(P2P打洞)、端口老化等
UDP中一个包的大小最大能多大?
Java新一代网络编程模型AIO原理及Linux系统AIO介绍
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
更多同类文章 ……

[2] 有关IM/推送的通信格式、协议的选择:

为什么QQ用的是UDP协议而不是TCP协议?
移动端即时通讯协议选择:UDP还是TCP?
如何选择即时通讯应用的数据传输格式
强列建议将Protobuf作为你的即时通讯应用数据传输格式
移动端IM开发需要面对的技术问题(含通信协议选择)
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
理论联系实际:一套典型的IM通信协议设计详解
58到家实时消息系统的协议设计等技术实践分享
更多同类文章 ……

[3] 有关IM/推送的心跳保活处理:

Android进程保活详解:一篇文章解决你的所有疑问
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
为何基于TCP协议的移动端IM仍然需要心跳保活机制?
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
更多同类文章 ……

[4] 有关WEB端即时通讯开发:

新手入门贴:史上最全Web端即时通讯技术原理详解
Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
SSE技术详解:一种全新的HTML5服务器推送事件技术
Comet技术详解:基于HTTP长连接的Web端实时通信技术
WebSocket详解(一):初步认识WebSocket技术
socket.io实现消息推送的一点实践及思路
更多同类文章 ……

[5] 有关IM架构设计:

浅谈IM系统的架构设计
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
一套原创分布式即时通讯(IM)系统理论架构方案
从零到卓越:京东客服即时通讯系统的技术架构演进历程
蘑菇街即时通讯/IM服务器开发之架构选择
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT
微信技术总监谈架构:微信之道——大道至简(演讲全文)
如何解读《微信技术总监谈架构:微信之道——大道至简》
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
17年的实践:腾讯海量产品的技术方法论
更多同类文章 ……

[6] 有关IM安全的文章:

即时通讯安全篇(一):正确地理解和使用Android端加密算法
即时通讯安全篇(二):探讨组合加密算法在IM中的应用
即时通讯安全篇(三):常用加解密算法与通讯安全讲解
即时通讯安全篇(四):实例分析Android中密钥硬编码的风险
传输层安全协议SSL/TLS的Java平台实现简介和Demo演示
理论联系实际:一套典型的IM通信协议设计详解(含安全层设计)
微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解
来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享
更多同类文章 ……

有关实时音视频开发:

即时通讯音视频开发(一):视频编解码之理论概述
即时通讯音视频开发(二):视频编解码之数字视频介绍
即时通讯音视频开发(三):视频编解码之编码基础
即时通讯音视频开发(四):视频编解码之预测技术介绍
即时通讯音视频开发(五):认识主流视频编码技术H.264
即时通讯音视频开发(六):如何开始音频编解码技术的学习
即时通讯音视频开发(七):音频基础及编码原理入门
即时通讯音视频开发(八):常见的实时语音通讯编码标准
即时通讯音视频开发(九):实时语音通讯的回音及回音消除概述
即时通讯音视频开发(十):实时语音通讯的回音消除技术详解
即时通讯音视频开发(十一):实时语音通讯丢包补偿技术详解
即时通讯音视频开发(十二):多人实时音视频聊天架构探讨
即时通讯音视频开发(十三):实时视频编码H.264的特点与优势
即时通讯音视频开发(十四):实时音视频数据传输协议介绍
即时通讯音视频开发(十五):聊聊P2P与实时音视频的应用情况
即时通讯音视频开发(十六):移动端实时音视频开发的几个建议
简述开源实时音视频技术WebRTC的优缺点
良心分享:WebRTC 零基础开发者教程(中文)
更多同类文章 ……

[8] IM开发综合文章:

移动端IM开发需要面对的技术问题
开发IM是自己设计协议用字节流好还是字符流好?
请问有人知道语音留言聊天的主流实现方式吗?
IM系统中如何保证消息的可靠投递(即QoS机制)
谈谈移动端 IM 开发中登录请求的优化
完全自已开发的IM该如何设计“失败重试”机制?
微信对网络影响的技术试验及分析(论文全文)
即时通讯系统的原理、技术和应用(技术论文)
开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀
更多同类文章 ……

[9] 开源移动端IM技术框架资料:

开源移动端IM技术框架MobileIMSDK:快速入门
开源移动端IM技术框架MobileIMSDK:常见问题解答
开源移动端IM技术框架MobileIMSDK:压力测试报告
开源移动端IM技术框架MobileIMSDK:Android版Demo使用帮助
开源移动端IM技术框架MobileIMSDK:Java版Demo使用帮助
开源移动端IM技术框架MobileIMSDK:iOS版Demo使用帮助
开源移动端IM技术框架MobileIMSDK:Android客户端开发指南
开源移动端IM技术框架MobileIMSDK:Java客户端开发指南
开源移动端IM技术框架MobileIMSDK:iOS客户端开发指南
开源移动端IM技术框架MobileIMSDK:Server端开发指南

更多同类文章 ……

[10] 有关推送技术的文章:
iOS的推送服务APNs详解:设计思路、技术原理及缺陷等
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
扫盲贴:认识MQTT通信协议
一个基于MQTT通信协议的完整Android推送Demo
求教android消息推送:GCM、XMPP、MQTT三种方案的优劣
移动端实时消息推送技术浅析
扫盲贴:浅谈iOS和Android后台实时消息推送的原理和区别
绝对干货:基于Netty实现海量接入的推送服务技术要点
移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信)
为何微信、QQ这样的IM工具不使用GCM服务推送消息?
更多同类文章 ……

[11] 更多即时通讯技术好文分类:
http://www.52im.net/forum.php?mod=collection&op=all
附录:有关QQ、微信的文章汇总

[1] 有关QQ、微信的技术文章:
微信后台团队:微信后台异步消息队列的优化升级实践分享
微信团队原创分享:微信客户端SQLite数据库损坏修复实践
腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率
腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇)
腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)
微信Mars:微信内部正在使用的网络层封装库,即将开源
如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]
微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
Android版微信从300KB到30MB的技术演进(PPT讲稿) [附件下载]
微信团队原创分享:Android版微信从300KB到30MB的技术演进
微信技术总监谈架构:微信之道——大道至简(演讲全文)
微信技术总监谈架构:微信之道——大道至简(PPT讲稿) [附件下载]
如何解读《微信技术总监谈架构:微信之道——大道至简》
微信海量用户背后的后台系统存储架构(视频+PPT) [附件下载]
微信异步化改造实践:8亿月活、单机千万连接背后的后台解决方案

微信朋友圈海量技术之道PPT [附件下载]
微信对网络影响的技术试验及分析(论文全文)
一份微信后台技术架构的总结性笔记

架构之道:3个程序员成就微信朋友圈日均10亿发布量[有视频]
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
快速裂变:见证微信强大后台架构从0到1的演进历程(二)
微信团队原创分享:Android内存泄漏监控和优化技巧总结
全面总结iOS版微信升级iOS9遇到的各种“坑”
微信团队原创资源混淆工具:让你的APK立减1M
微信团队原创Android资源混淆工具:AndResGuard [有源码]
Android版微信安装包“减肥”实战记录
iOS版微信安装包“减肥”实战记录
移动端IM实践:iOS版微信界面卡顿监测方案
微信“红包照片”背后的技术难题
移动端IM实践:iOS版微信小视频功能技术方案实录
移动端IM实践:Android版微信如何大幅提升交互性能(一)
移动端IM实践:Android版微信如何大幅提升交互性能(二)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信)
移动端IM实践:iOS版微信的多设备字体适配方案探讨
更多同类文章 ……

[2] 有关QQ、微信的技术故事:
技术往事:创业初期的腾讯——16年前的冬天,谁动了马化腾的代码
技术往事:史上最全QQ图标变迁过程,追寻IM巨人的演进历史
开发往事:深度讲述2010到2015,微信一路风雨的背后
开发往事:微信千年不变的那张闪屏图片的由来
开发往事:记录微信3.0版背后的故事(距微信1.0发布9个月时)
一个微信实习生自述:我眼中的微信开发团队
更多同类文章 ……

推荐阅读更多精彩内容