android客户端socket轮询读取服务器数据,Android socket与服务器通讯及心跳链接的实现...

在项目中,有以下需求:Android客户端向服务器发送数据,收到服务器返回的数据发送成功标识后,客户端即与服务器创建数据一来一往的心跳链接,若服务器端断开时,客户端接收到通知,关闭Service中止发送数据;代码以下:java

public class BackService extends Service {

private static final String TAG = "BackService";

/** 心跳检测时间 */

private static final long HEART_BEAT_RATE = 3* 1000;

/** 主机IP地址 */

private static String HOST = "192.168.1.30";

/** 端口号 */

public static final int PORT =10801;

/** 消息广播 */

public static final String MESSAGE_ACTION = "org.feng.message_ACTION";

/** 心跳广播 */

public static final String HEART_BEAT_ACTION = "org.feng.heart_beat_ACTION";

private long sendTime = 0L;

private Socket socket;

private ReadThread mReadThread;

private InputStream is;//输入流

private int count;//读取的字节长度

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 (IBinder) iBackService;

}

@Override

public void onCreate() {

super.onCreate();

HOST= (String) Utils.getShare(this, ConfigUrl.SERVER_IP,"ip");

Log.i(TAG,"ip-->"+HOST);

Log.i(TAG,"port-->"+PORT);

new InitSocketThread().start();

}

// 发送心跳包

private Handler mHandler = new Handler();

private Runnable heartBeatRunnable = new Runnable() {

@Override

public void run() {

ReadThread thread=new ReadThread(socket);

thread.start();

}

};

public boolean sendMsg(String msg) {

if (null == socket) {

return false;

}

try {

if (!socket.isClosed() && !socket.isOutputShutdown()) {

OutputStream os = socket.getOutputStream();

os.write(msg.getBytes());

os.flush();

sendTime = System.currentTimeMillis();// 每次发送成功数据,就改一下最后成功发送的时间,节省心跳间隔时间

Log.i(TAG, "发送成功的时间:" + sendTime+" 内容-->"+msg);

} else {

return false;

}

} catch (IOException e) {

e.printStackTrace();

Intent intent = new Intent(HEART_BEAT_ACTION);

intent.putExtra("message", "exit");

sendBroadcast(intent);

Log.i(TAG,"send-->"+e.getMessage());

return false;

}

return true;

}

// 初始化socket

private void initSocket() throws UnknownHostException, IOException {

socket = new Socket(HOST, PORT);

socket.setSoTimeout(13000);//?

if (socket.isConnected()){//链接成功

if (sendMsg("mdc_"+Utils.getShare(this,ConfigUrl.DEVICE_ID,"手机设备id"))){//发送成功

//接收服务器返回的信息

mReadThread = new ReadThread(socket);

mReadThread.start();

}

}

}

// 释放socket

private void releaseLastSocket(Socket mSocket) {

try {

if (null != mSocket) {

if (!mSocket.isClosed()) {

is.close();

mSocket.close();

}

mSocket = null;

}

} catch (IOException e) {

e.printStackTrace();

}

}

class InitSocketThread extends Thread {

@Override

public void run() {

super.run();

try {

initSocket();

} catch (UnknownHostException e) {

e.printStackTrace();

Log.i(TAG,"socket-->"+e.getMessage());

Log.i(TAG,"链接失败");

Intent intent = new Intent(HEART_BEAT_ACTION);

intent.putExtra("message", "fail");

sendBroadcast(intent);

} catch (IOException e) {

e.printStackTrace();

Log.i(TAG,"socket-->"+e.getMessage());

Intent intent = new Intent(HEART_BEAT_ACTION);

intent.putExtra("message", "fail");

sendBroadcast(intent);

}

}

}

public class ReadThread extends Thread {

private Socket rSocket;

private boolean isStart = true;

public ReadThread(Socket socket) {

rSocket=socket;

}

public void release() {

isStart = false;

releaseLastSocket(rSocket);

}

@SuppressLint("NewApi")

@Override

public void run() {

super.run();

String line="";

if (null != rSocket) {

while (isStart&&!rSocket.isClosed()&&!rSocket.isInputShutdown()){

try {

Log.i(TAG,"开始读取消息");

is=rSocket.getInputStream();

count=is.available();

byte[] data=new byte[count];

is.read(data);

line=new String(data);

if (line!=null){

Log.i(TAG, "收到服务器发送来的消息:"+line);

if ("mdc_ok".equals(line)||"mdc_exist".equals(line)||"exist".equals(line)){

sendMsg("connect");

mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功后,就准备发送心跳

return;

}else if ("mdc_connect".equals(line)){//服务器发送继续接收的消息

boolean isSuccess=sendMsg("connect");

if (isSuccess){//成功发送,接收回执信息

mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功后,就准备发送心跳

}else {//发送失败,处理善后工做

mHandler.removeCallbacks(heartBeatRunnable);

release();

releaseLastSocket(socket);

}

return;

}else if ("mdc_connectexit".equals(line)||"exit".equals(line)){//断开链接消息

Intent intent = new Intent(HEART_BEAT_ACTION);

intent.putExtra("message", "exit");

sendBroadcast(intent);

return;

}

}else if (line==null){

Log.i(TAG, "服务器发送过来的消息是空的");

}

} catch (IOException e) {

Log.i(TAG,"Read-->"+e.getClass().getName());

e.printStackTrace();

continue;

}

}

}

}

}

@Override

public void onDestroy() {

super.onDestroy();

Log.i(TAG,"onDestroy");

mHandler.removeCallbacks(heartBeatRunnable);

mReadThread.release();

releaseLastSocket(socket);

}

}

以上代码只是Service中的代码亲测可用;在开发过程当中也遇到不少问题,有些已解答,有些仍未解决,在此记录,i但愿有了解的能够告知如下。

1.当 OutputStream os,调用os.close()或者 InputStream os,调用is.close()时,会将socket关闭,开始时调用os.close()致使scoket关闭,在后面读取服务器的消息时不断抛出socket closed异常,经检查后发现此处有问题;若想将输出/入流关闭,可调用 socket.shutdownInput();/socket.shutdownOutput();此时socket不会关闭;服务器

2.在ReadThread线程中,读取服务器消息时开始使用BufferedReader in=new BufferedReader(new InputStreamReader(rSocket.getInputStream()));   String line=in.readLine();而后一直抛出超时异常,使用Debug模式调试后发现,line当中已经读取到正确的内容,可是readLine()方法在读到“\n”标识符后才会完成读取,否则会一直等待读取直到抛出超时异常,而且readLIne()是阻塞线程,未完成读取时程序一直阻塞,没法继续向下进行,直到超时进入catch中。此时应该使用字节读取。socket

3.因为客户端一直在轮询发送并接收服务起的消息,当服务器端socket主动断开时,客户端也要断开,可是在开发中,服务器端的socket断开时,客户端一直在while循环中读取消息,使用字节读取时,调用到is.read(data);方法也不会抛出异常(可是好多文章中都写此时会抛出异常,可是我这里没有抛出,缘由未知),而是一直读取字节长度为0 的空数据却不结束,使用socket的isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等判断socket与服务器的链接状态均无效,缘由在于上这些方法都是访问socket在内存驻留的状态,而非与服务器的实时状态,所以判断是无效的,如下三张截图:ide

android客户端socket轮询读取服务器数据,Android socket与服务器通讯及心跳链接的实现..._第1张图片

此为客户端与服务器刚创建链接,此时未通消息

post

android客户端socket轮询读取服务器数据,Android socket与服务器通讯及心跳链接的实现..._第2张图片

此为客户端向服务器发送消息成功后this

android客户端socket轮询读取服务器数据,Android socket与服务器通讯及心跳链接的实现..._第3张图片

此为服务器socket断开后的状况

线程

从图中能够看到,三种状况下各方法返回的状态均相同,没法做为判断客户端与服务器实时链接状况的方法使用,网上查阅资料有网友提到,可使用socket.sendUrgentData(0xFF);方法,向服务器发送数据,检测服务器是否断开链接,相似于ping,且该方法可往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,就会自动舍弃这个字节,而SO_OOBINLINE属性默认状况下就是关闭的,这样防止向服务器发送无效数据,若服务器断开了,则会在报出客户端报出异常,此时便可得知服务器的socket是否处于链接状态;本觉得找到一个好方法,使用时才发现,即便服务器处于正常的链接状态,也会抛出异常(有好多文章说此方法可行,然而我没有搞通,不知是否是使用方法出错了,有了解相关状况的望告知),最后只有让服务器在断开socket时,发送一个标识符,即在代码中mdc_connectexit及exit(之因此会用这两个是由于服务器在发送数据时,会出现“粘包”的状况)。调试

你可能感兴趣的:(android客户端socket轮询读取服务器数据,Android socket与服务器通讯及心跳链接的实现...)