[转]AndroidTCP长连接⼼跳机制及实现
维护任何⼀个长连接都需要⼼跳机制,客户端发送⼀个⼼跳给服务器,服务器给客户端⼀个⼼跳应答,
这样双⽅都知道他们之间的连接是没有断开。【客户端先发送给服务端】
如果超过⼀个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的⼼跳,
那么对客户端来说则断开与服务器的连接重新建⽴⼀个连接,对服务器来说只要断开这个连接即可。
[背景知识]:
1.智能⼿机上的长连接⼼跳和在Internet上的长连接⼼跳有什么不同?
当⼀台智能⼿机连上移动⽹络时,其实并没有真正连接上Internet,运营商分配给⼿机的IP其实是运营商的内⽹IP,⼿机终端要连接上Internet还必须通过运营商的⽹关进⾏IP地址的转换,这个⽹关简称为NAT(NetWork Address Translation),简单来说就是⼿机终端连接Internet 其实就是移动内⽹IP,端⼝,外⽹IP之间相互映射。相当于在⼿机终端在移动⽆线⽹络这堵墙上打个洞与外⾯的Internet相连。
GGSN(GateWay GPRS Support Note ⽹关GPRS⽀持节点)模块就实现了NAT功能,由于⼤部分的移动
⽆线⽹络运营商为了减少⽹关NAT映射表的负荷,如果⼀个链路有⼀段时间没有通信时就会删除其对应表,造成链路中断,正是这种刻意缩短空闲连接的释放超时,原本是想节省信道资源的作⽤,没想到让互联⽹的应⽤不得以远⾼于正常频率发送⼼跳来维护推送的长连接。
⼿机应⽤发送⼼跳的频率很短,既造成了信道资源的浪费,也造成了⼿机电量的快速消耗。
2.Android系统的推送和iOS的推送有什么区别:
没有长连接,服务端就⽆法主动向客户端推送.
iOS长连接是由系统来维护的,也就是说苹果的iOS系统在系统级别维护了⼀个客户端和苹果服务器的长链接,iOS上的所有应⽤上的推送都是先将消息推送到苹果的服务器然后将苹果服务器通过这个系统级别的长链接推送到⼿机终端上,这样的的⼏个好处为:
* (1).在⼿机终端始终只要维护⼀个长连接即可,⽽且由于这个长链接是系统级别的不会出现被杀死⽽⽆法推送的情况。
* (2).省电,不会出现每个应⽤都各⾃维护⼀个⾃⼰的长连接。
* (3).安全,只有在苹果注册的开发者才能够进⾏推送,等等。
不会被杀死,省电,安全.
由于Google的推送框架C2DM在中国境内不能使⽤,android的长连接是由每个应⽤各⾃维护⼀个长连接,如果都24⼩时在线,这种电量和流量的消耗是可想⽽知的。
3.⼏种推送的实现⽅式:
1)轮询(Pull)⽅式:应⽤程序应当阶段性的与服务器进⾏连接并查询是否有新的消息到达,你必须⾃⼰实现与服务器之间的通信,例如消息排队等。⽽且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会⼤量消耗⽹络带宽和电池。
2)SMS(Push)⽅式:在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,并获取其显⽰内容进⾏处理。这是⼀个不错的想法,我就见过采⽤这个⽅案的应⽤程序。这个⽅案的好处是,可以实现完全的实时操作。但是问题是这个⽅案的成本相对⽐较⾼,我们需要向移动公司缴纳相应的费⽤。我们⽬前很难到免费的短消息发送⽹关来实现这种⽅案。
3)持久连接(Push)⽅式:这个⽅案可以解决由轮询带来的性能问题,但是还是会消耗⼿机的电池。iOS平台的推送服务之所以⼯作的很好,是因为每⼀台⼿机仅仅保持⼀个与服务器之间的连接,事实上C2DM也是这么⼯作的。Android操作系统允许在低内存情况下杀死系统服务,所以我们的推送通知服
务很有可能就被操作系统Kill掉了。我们很难在⼿机上实现⼀个可靠的服务,⽬前也⽆法与iOS平台的推送功能相⽐。
[协议]:
1,XMPP简介:
Google官⽅的C2DM服务器底层也是采⽤XMPP协议进⾏的封装。
XMPP(Extensible Messageing and Presence Protocol:可扩展消息与存在协议)是基于可扩展标记语⾔(XML)的协议,它⽤于即时消息(IM)以及在线探测。
基本⽹络结构:
XMPP中定义了三个⾓⾊,客户端,服务器,⽹关。通信能够在这三者的任意两个之间双向发⽣。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。⽹关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的⽹络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。
XMPP通过TCP传输的是与即时通讯相关的指令。XMPP的核⼼部分就是⼀个在⽹络上分⽚断发送XM
L的流协议。这个流协议是XMPP的即时通讯指令的传递基础,也是⼀个⾮常重要的可以被进⼀步利⽤的⽹络基础协议。所以可以说,XMPP⽤TCP传的是XML流。
C: <stream:stream>
C: <presence/>
C: <iq type="get">
<query xmlns="jabber:iq:roster"/>
</iq>
S: <iq type="result">
<query xmlns="jabber:iq:roster">
<item jid="suke@skh.whu.edu"xs/>
<item jid="gmz@skh.whu.edu"/>
<item jid="beta@skh.whu.edu"/>
</query>
</iq>
C: <message from="suke@skh.whu.edu"
to="beta@skh.whu.edu">
<body>Off with his head!</body>
</message>
S: <message from="lj@skh.whu.edu"
to="cyl@skh.whu.edu ">
<body>You are all pardoned.</body>
</message>
C: <presence type="unavailable"/>
C: </stream:stream>
2, MQTT简介:
MQTT 协议主要解决的是机器与机器之间数据通信,其适⽤于如下但不限于这⼏点:
即时传输的轻量级协议
专门设计⽤于低带宽或者⾼昂的⽹络费⽤
具备三种服务品质层级
MQTT 最引以为豪的就是最⼩的2 byte 头部传输开销.我们看下其他流⾏的协议的message format的设计:
XMPP 消息体xml:
|--------------------| | <stream> | |--------------------| | <presence> | | <show/> | | </presence> | |--------------------| | <message to='foo'> | | <body/> | | </message> | |--------------------| | <iq to='bar'> | | <query/> | | </iq> | |--------------------| | ... | |--------------------| | 1
HTTP
HTTP-message = Request | Response ; HTTP/1.1 messages
1
还有很多协议,就不⼀样细说了,就举两个我⽐较了解的.就⽬前通⽤的协议来看很少有⽐MQTT 还要低的传输开销了.
*第⼀个byte ⽤于说明消息体的信息(Message Type 4bit|DUP flag |QoS level 2bit|RETAIN).
第⼆个byte ⽤于传输我们需要传输的数据(Remaining Length, 8bit).*
3,移动端消息推送 xmpp 和 mqtt 哪个更费电?
使⽤XMPP协议(Openfire + Spark + Smack)
简介:基于XML协议的通讯协议,前⾝是Jabber,⽬前已由IETF国际标准化组织完成了标准化⼯作。
优点:协议成熟、强⼤、可扩展性强、⽬前主要应⽤于许多聊天系统中,且已有开源的Java版的开发实例androidpn。
缺点:协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本⾼。
使⽤MQTT协议
简介:轻量级的、基于代理的“发布/订阅”模式的消息传输协议。
优点:协议简洁、⼩巧、可扩展性强、省流量、省电,⽬前已经应⽤到企业领域,且已有C++版的服务端组件rsmb。
缺点:不够成熟、实现较复杂、服务端组件rsmb不开源,部署硬件成本较⾼。
MQTT相⽐XMPP 有⼏个优势:
⼆进制,⾮常精简,适合做⼤量节点弱⽹络差的场景,⾮常适合现在移动互联⽹的基础设施;MQTT是天然的订阅发布系统,有权限的⼈都可以往⾥头发消息;开源的协议和实
现;扩展⽅便且轻量级。
XMPP不适合移动⽹络有⼏个原因:
协议虽然完整扩展性虽然好,它耗费⽹络流量很⼤,交互次数太多,跑起来⽐MQTT慢很多;另外有
⾼达70%的流量是耗费在XMPP本⾝的标签和编解码上⾯。
MQTT是⼀个由 IBM 开发的传输协议,它被设计⽤于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的⽹络环境中的设备提供可靠的⽹络服务。相⽐于XMPP等传统协
议,MQTT 是专门针对移动互联⽹开发的轻量级传输协议,这种传输协议连接稳定、⼼跳数据包⼩,所以具备耗电量低、耗流量低的优势。推送服务的最佳协议!(纯属粘贴,未
经验证...)
[⼼跳代码实现]:
基于TCP的socket编程有三种(2011年),
流式套接字(SOCK_STREAM),
数据报套接字(SOCK_DGRAM),
原始套接字(SOCK_RAW);
基于TCP的socket编程是采⽤的流式套接字。
服务器端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:绑定套接字到⼀个IP地址和⼀个端⼝上(bind());
3:将套接字设置为监听模式等待连接请求(listen());
4:请求到来后,接受连接请求,返回⼀个新的对应于此次连接的套接字(accept());
5:⽤返回的套接字和客户端进⾏通信(send()/recv());
6:返回,等待另⼀连接请求;
7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:向服务器发出连接请求(connect());
3:和服务器端进⾏通信(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
⼼跳是逻辑应⽤层的东西,需要⾃⼰实现,当socket空闲时,发送⼼跳包,报⽂件格式⾃定义.
⼼跳检测需要以下步骤:
1 客户端每隔⼀个时间间隔发⽣⼀个探测包给服务器
2 客户端发包时启动⼀个超时定时器
3 服务器端接收到检测包,应该回应⼀个包
4 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
[Demo]建⽴⼀个带有⼼跳检测的SocketDemo
此处存⼀个别⼈家的demo
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import f.WeakReference;
import java.Socket;
import java.UnknownHostException;
import java.util.Arrays;
import android.app.Service;
t.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.t.LocalBroadcastManager;
import android.util.Log;
public class BackService extends Service {
private static final String TAG = "BackService";
private static final long HEART_BEAT_RATE = 3 * 1000;
public static final String HOST = "192.168.1.101";// "192.168.1.21";//
public static final int PORT = 9800;
public static final String MESSAGE_ACTION="ssage_ACTION";
public static final String HEART_BEAT_ACTION="org.feng.heart_beat_ACTION";
private ReadThread mReadThread;
private LocalBroadcastManager mLocalBroadcastManager;
private WeakReference<Socket> mSocket;
// For heart Beat
private Handler mHandler = new Handler();
private Runnable heartBeatRunnable = new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {
boolean isSuccess = sendMsg("");//就发送⼀个\r\n过去如果发送失败,就重新初始化⼀个socket
if (!isSuccess) {
releaseLastSocket(mSocket);
new InitSocketThread().start();
}
}
mHandler.postDelayed(this, HEART_BEAT_RATE);
}
};
private long sendTime = 0L;
private IBackService.Stub iBackService = new IBackService.Stub() {
@Override
public boolean sendMessage(String message) throws RemoteException {
return sendMsg(message);
}
};
@Override
public IBinder onBind(Intent arg0) {
return iBackService;
}
@Override
public void onCreate() {
new InitSocketThread().start();
Instance(this);
}
public boolean sendMsg(String msg) {
if (null == mSocket || null == ()) {
return false;
}
Socket soc = ();
try {
if (!soc.isClosed() && !soc.isOutputShutdown()) {
OutputStream os = OutputStream();
String message = msg + "\r\n";
os.Bytes());
os.flush();
sendTime = System.currentTimeMillis();//每次发送成数据,就改⼀下最后成功发送的时间,节省⼼跳间隔时间 } else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
private void initSocket() {//初始化Socket
try {
Socket so = new Socket(HOST, PORT);
mSocket = new WeakReference<Socket>(so);
mReadThread = new ReadThread(so);
mReadThread.start();
mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//初始化成功后,就准备发送⼼跳包
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void releaseLastSocket(WeakReference<Socket> mSocket) {
try {
if (null != mSocket) {
Socket sk = ();
if (!sk.isClosed()) {
sk.close();
}
sk = null;
mSocket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
class InitSocketThread extends Thread {
@Override
public void run() {
super.run();
initSocket();
}
}
// Thread to read content from Socket
class ReadThread extends Thread {
private WeakReference<Socket> mWeakSocket;
private boolean isStart = true;
public ReadThread(Socket socket) {
mWeakSocket = new WeakReference<Socket>(socket);
}
public void release() {
isStart = false;
releaseLastSocket(mWeakSocket);
}
@Override
public void run() {
super.run();
Socket socket = ();
if (null != socket) {
try {
InputStream is = InputStream();
byte[] buffer = new byte[1024 * 4];
int length = 0;
while (!socket.isClosed() && !socket.isInputShutdown()
&& isStart && ((length = is.read(buffer)) != -1)) {
if (length > 0) {
String message = new pyOf(buffer,
length)).trim();
Log.e(TAG, message);
//收到服务器过来的消息,就通过Broadcast发送出去
if(message.equals("ok")){//处理⼼跳回复
Intent intent=new Intent(HEART_BEAT_ACTION);
mLocalBroadcastManager.sendBroadcast(intent);
}else{
/
/其他消息回复
Intent intent=new Intent(MESSAGE_ACTION);
intent.putExtra("message", message);
mLocalBroadcastManager.sendBroadcast(intent);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在Activity中发送以及接收数据:
import f.WeakReference;
import org.feng.sockettest.server.BackService;
import org.feng.sockettest.server.IBackService;
import android.app.Activity;
t.BroadcastReceiver;
t.ComponentName;
t.Context;
t.Intent;
t.IntentFilter;
t.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.t.LocalBroadcastManager;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private IBackService iBackService;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
iBackService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) { iBackService = IBackService.Stub.asInterface(service);
}
};
private TextView mResultText;
private EditText mEditText;
private Intent mServiceIntent;
class MessageBackReciver extends BroadcastReceiver {
private WeakReference<TextView> textView;
public MessageBackReciver(TextView tv) {
textView = new WeakReference<TextView>(tv);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = Action();
TextView tv = ();
if (action.equals(BackService.HEART_BEAT_ACTION)) {
if (null != tv) {
tv.setText("Get a heart heat");
}开源mqtt服务器
} else {
String message = StringExtra("message");
tv.setText(message);
}
};
}
private MessageBackReciver mReciver;
private IntentFilter mIntentFilter;
private LocalBroadcastManager mLocalBroadcastManager;
@Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
mLocalBroadcastManager = Instance(this);
mResultText = (TextView) findViewById(sule_text);
mEditText = (EditText) findViewById(t_edit);
mReciver = new MessageBackReciver(mResultText);
mServiceIntent = new Intent(this, BackService.class);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(BackService.HEART_BEAT_ACTION);
mIntentFilter.addAction(BackService.MESSAGE_ACTION);
}
@Override
protected void onStart() {
bindService(mServiceIntent, conn, BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
unbindService(conn);
mLocalBroadcastManager.unregisterReceiver(mReciver);
}
public void onClick(View view) {
switch (Id()) {
case R.id.send:
String content = Text().toString();
try {
boolean isSend = iBackService.sendMessage(content);//Send Content by socket Toast.makeText(this, isSend ? "success" : "fail",
Toast.LENGTH_SHORT).show();
mEditText.setText("");
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
摘录⾃链接:
再补充⼏篇⽂章:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论