文档章节

android socket 发送心跳包

猴亮屏
 猴亮屏
发布于 2017/07/14 09:33
字数 1586
阅读 57
收藏 0
点赞 0
评论 0

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

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中。此时应该使用字节读取。

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

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

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

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

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

本文转载自:http://blog.csdn.net/androidforwell/article/details/55102185

共有 人打赏支持
猴亮屏

猴亮屏

粉丝 29
博文 505
码字总数 52840
作品 2
北京
Android工程师
关于使用AlarmManager的注意事项

最近在做一个需求:客户端按照规定的时间间隔向服务端发送定位。一看到这个需求就想到了使用 来实现。 经常被用来执行定时任务,比如设置闹铃、发送心跳包等。也许有人会有疑问:为什么不能使...

hejunbinlan ⋅ 06/15 ⋅ 0

Android上保持Socket长连接

0.Thanks 性能优化十六之WakeLock唤醒锁以及JobScheduler使用 安卓 java 判断socket断开 android保持服务不休眠(持续运行)以及唤醒屏幕的方法 1.概述 前阵子接到一个用户反馈说,公司的一款...

栗子酱油饼 ⋅ 01/07 ⋅ 0

android socket长连接资源释放

android socket长连接向服务器发送心跳包,发送一段时间后,在服务端没有接收到客户端发送的心跳包,通过抓包软件没有抓到客户端发送的请求,麻烦看下是什么原因导致的。 android客户端心跳包...

恶里怪 ⋅ 2016/04/29 ⋅ 1

在Android上面如何使用带有心跳检测的Socket

由于移动设备的网络的复杂性,经常会出现网络断开,如果没有心跳包的检测,客户端只会在需要发送数据的时候才知道自己已经断线,会延误,甚至丢失服务器发送过来的数据。一下简单建立一个带有...

fneg ⋅ 2013/11/21 ⋅ 8

android -------- 蓝牙通信

前面介绍了蓝牙的一些知识,今天来聊一聊蓝牙之间的通信,实现两个设备通信。 我用两部手机实现相互发消息的功能 无论是BluetoothSocket,还是BluetoothServerSocket,都需要一个UUID(全局唯...

切切歆语 ⋅ 04/17 ⋅ 0

Android 3G/4G流量上网原理简析

手机一般会提供两种上网方式:Wifi或者3G/4G上网,Wifi上网其实就是利用网卡通过以太网上网;3G/4G则是通过基带,利用蜂窝网络进行上网,之前已经简单的阐述了Wifi上网跟3G上网的区别,本文主...

看书的小蜗牛 ⋅ 05/10 ⋅ 0

AndroidThings之基础二 设计理念

转载自:https://blog.csdn.net/tangxiaoyin/article/details/75273491 (PS:目前AndroidThings已经走向消费级别,发布正式版本1.0版,开发板推荐树莓派3B+) 前言 2012 年 6 月,由 IoT-GSI(...

qq_28831197 ⋅ 05/09 ⋅ 0

android 两个客户端间的通信(Socket通信)

客户端之间通信: 实际上两个客户端不能直接通信,要借助服务器来做为中转站,才能实现双方通信。 一个客户端发送数据到服务器,服务器将数据发送给(所有连接上服务器的)客户端,这样客户端...

新根 ⋅ 2015/09/20 ⋅ 0

聊一聊WebSocket

聊一聊WebSocket 会飞的污熊2017-12-261 阅读 webwebsocket WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向...

会飞的污熊 ⋅ 2017/12/26 ⋅ 0

Android系统源码分析团体项目BeesAndroid正式上线啦

嗨,BeesAndroid开源技术小组正式成立啦,Bees,即蜜蜂,取义分享、合作与奉献的意思,这也是BeesAndroid小组的宗旨,我们第一个团体项目BeesAndroid也于2018年3月6日同步上线,该项目的前 ...

郭孝星 ⋅ 03/08 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

看东方明珠新媒体如何基于阿里视频云,构建完整的视频OTT平台SaaS服务

摘要: 东方明珠新媒体如何基于阿里云,搭建了面向第三方的视频SaaS服务?6月8日,上海云栖大会视频专场中,东方明珠新媒体股份有限公司云计算中心的副总周少毅带来了《东方明珠视频云》为题...

阿里云云栖社区 ⋅ 19分钟前 ⋅ 0

C#调用WebService实例和开发 VS2013

简单的理解就是:webservice就是放在服务器上的函数,所有人都可以调用,然后返回信息。 Web Service的主要目标是跨平台的可互操作性。为了实现这一目标,Web Service 完全基于XML(可扩展标...

布衣大侠 ⋅ 22分钟前 ⋅ 0

基于FlumeNG+Kafka+ElasticSearch+Kibana的日志系统

环境准备 1.服务器概览 hostname ip 操作系统 说明 安装内容 node1.fek 192.168.2.161 centos 7 node1节点 nginx,jdk1.8, flumeNG, elasticsearch slave1 node2.fek 192.168.2.162 centos ......

张shieppp ⋅ 22分钟前 ⋅ 0

问答网站已成过去,深度问答社区才是当下

曾几何时,各类问答网站数不胜数,从百度知道这类综合型问答网站到各种垂直细分的问答网站,都有不少,但到了移动互联网时代,很明显的一大趋势是,网站整体的流量都在下滑,随着移动智能设备...

ThinkSNS账号 ⋅ 24分钟前 ⋅ 0

Android平台架构(ART)

Android平台架构(ART) 本文目的:准确表述Android平台架构 本文转载自[Android官网] 本文定位:学习笔记 学习过程记录,加深理解。也希望能给学习的同学一些灵感 本文更新时间:2018.06.22(...

lichuangnk ⋅ 27分钟前 ⋅ 0

看东方明珠新媒体如何基于阿里视频云,构建完整的视频OTT平台SaaS服务

摘要: 东方明珠新媒体如何基于阿里云,搭建了面向第三方的视频SaaS服务?6月8日,上海云栖大会视频专场中,东方明珠新媒体股份有限公司云计算中心的副总周少毅带来了《东方明珠视频云》为题...

猫耳m ⋅ 28分钟前 ⋅ 0

Java 动态代理 原理解析

概要 AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标...

轨迹_ ⋅ 30分钟前 ⋅ 0

js 获取当前时间

var myDate = new Date();myDate.getYear(); //获取当前年份(2位)myDate.getFullYear(); //获取完整的年份(4位,1970-????)myDate.getMonth(); //获取当前月份(0-11,0代表1月)myDate...

夜醒者 ⋅ 36分钟前 ⋅ 0

windows删除或修改本地Git保存的账号密码

在win10或者win7都是一样的步骤: (一)进入控制面板(二)选择用户账户(三)选择管理你的凭据(四)选择Windows凭据(五)选择git保存的用户信息(六)选择编辑或者进...

果树啊 ⋅ 36分钟前 ⋅ 0

8个基本的Docker容器管理命令

前言: 在这篇文章中,我们将带你学习 8 个基本的 Docker 容器命令,它们操控着 Docker 容器的基本活动,例如 运行run、 列举list、 停止stop、 查看历史纪录logs、 删除delete 等等。文末福...

java高级架构牛人 ⋅ 38分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部