Android socket与服务器通信及心跳连接的实现

简介:

Android socket与服务器通信及心跳连接的实现

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

[java] view plain copy

  1. public class BackService extends Service {
  2.     private static final String TAG = "BackService";
  3.     /** 心跳检测时间  */
  4.     private static final long HEART_BEAT_RATE = 31000;
  5.     /** 主机IP地址  */
  6.     private static String HOST = "192.168.1.30";
  7.     /** 端口号  */
  8.     public static final int PORT =10801;
  9.     /** 消息广播  */
  10.     public static final String MESSAGE_ACTION = "org.feng.message_ACTION";
  11.     /** 心跳广播  */
  12.     public static final String HEART_BEAT_ACTION = "org.feng.heart_beat_ACTION";
  13.     private long sendTime = 0L;
  14.     private Socket socket;
  15.     private ReadThread mReadThread;
  16.     private InputStream is;//输入流
  17.     private int count;//读取的字节长度
  18.     private IBackService.Stub iBackService = new IBackService.Stub() {
  19.         @Override
  20.         public boolean sendMessage(String message) throws RemoteException {
  21.             return sendMsg(message);
  22.         }
  23.     };
  24.     @Override
  25.     public IBinder onBind(Intent arg0) {
  26.         return (IBinder) iBackService;
  27.     }
  28.     @Override
  29.     public void onCreate() {
  30.         super.onCreate();
  31.         HOST= (String) Utils.getShare(this, ConfigUrl.SERVER_IP,"ip");
  32.         Log.i(TAG,"ip-->"+HOST);
  33.         Log.i(TAG,"port-->"+PORT);
  34.         new InitSocketThread().start();
  35.     }
  36.     // 发送心跳包
  37.     private Handler mHandler = new Handler();
  38.     private Runnable heartBeatRunnable = new Runnable() {
  39.         @Override
  40.         public void run() {
  41.             ReadThread thread=new ReadThread(socket);
  42.             thread.start();
  43.         }
  44.     };
  45.     public boolean sendMsg(String msg) {
  46.         if (null == socket) {
  47.             return false;
  48.         }
  49.         try {
  50.             if (!socket.isClosed() && !socket.isOutputShutdown()) {
  51.                 OutputStream os = socket.getOutputStream();
  52.                 os.write(msg.getBytes());
  53.                 os.flush();
  54.                 sendTime = System.currentTimeMillis();// 每次发送成功数据,就改一下最后成功发送的时间,节省心跳间隔时间
  55.                 Log.i(TAG, "发送成功的时间:" + sendTime+"  内容-->"+msg);
  56.             } else {
  57.                 return false;
  58.             }
  59.         } catch (IOException e) {
  60.             e.printStackTrace();
  61.             Intent intent = new Intent(HEART_BEAT_ACTION);
  62.             intent.putExtra("message""exit");
  63.             sendBroadcast(intent);
  64.             Log.i(TAG,"send-->"+e.getMessage());
  65.             return false;
  66.         }
  67.         return true;
  68.     }
  69.     // 初始化socket
  70.     private void initSocket() throws UnknownHostException, IOException {
  71.         socket = new Socket(HOST, PORT);
  72.         socket.setSoTimeout(13000);//?
  73.         if (socket.isConnected()){//连接成功
  74.             if (sendMsg("mdc_"+Utils.getShare(this,ConfigUrl.DEVICE_ID,"手机设备id"))){//发送成功
  75.                 //接收服务器返回的信息
  76.                 mReadThread = new ReadThread(socket);
  77.                 mReadThread.start();
  78.             }
  79.         }
  80.     }
  81.     // 释放socket
  82.     private void releaseLastSocket(Socket mSocket) {
  83.         try {
  84.             if (null != mSocket) {
  85.                 if (!mSocket.isClosed()) {
  86.                     is.close();
  87.                     mSocket.close();
  88.                 }
  89.                 mSocket = null;
  90.             }
  91.         } catch (IOException e) {
  92.             e.printStackTrace();
  93.         }
  94.     }
  95.     class InitSocketThread extends Thread {
  96.         @Override
  97.         public void run() {
  98.             super.run();
  99.             try {
  100.                 initSocket();
  101.             } catch (UnknownHostException e) {
  102.                 e.printStackTrace();
  103.                 Log.i(TAG,"socket-->"+e.getMessage());
  104.                 Log.i(TAG,"连接失败");
  105.                 Intent intent = new Intent(HEART_BEAT_ACTION);
  106.                 intent.putExtra("message""fail");
  107.                 sendBroadcast(intent);
  108.             } catch (IOException e) {
  109.                 e.printStackTrace();
  110.                 Log.i(TAG,"socket-->"+e.getMessage());
  111.                 Intent intent = new Intent(HEART_BEAT_ACTION);
  112.                 intent.putExtra("message""fail");
  113.                 sendBroadcast(intent);
  114.             }
  115.         }
  116.     }
  117.     public class ReadThread extends Thread {
  118.         private Socket rSocket;
  119.         private boolean isStart = true;
  120.         public ReadThread(Socket socket) {
  121.             rSocket=socket;
  122.         }
  123.         public void release() {
  124.             isStart = false;
  125.             releaseLastSocket(rSocket);
  126.         }
  127.         @SuppressLint("NewApi")
  128.         @Override
  129.         public void run() {
  130.             super.run();
  131.             String line="";
  132.             if (null != rSocket) {
  133.                 while (isStart&&!rSocket.isClosed()&&!rSocket.isInputShutdown()){
  134.                     try {
  135.                             Log.i(TAG,"开始读取消息");
  136.                             is=rSocket.getInputStream();
  137.                             count=is.available();
  138.                             byte[] data=new byte[count];
  139.                             is.read(data);
  140.                             line=new String(data);
  141.                             if (line!=null){
  142.                                 Log.i(TAG, "收到服务器发送来的消息:"+line);
  143.                                 if ("mdc_ok".equals(line)||"mdc_exist".equals(line)||"exist".equals(line)){
  144.                                     sendMsg("connect");
  145.                                     mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功后,就准备发送心跳
  146.                                     return;
  147.                                 }else if ("mdc_connect".equals(line)){//服务器发送继续接收的消息
  148.                                     boolean isSuccess=sendMsg("connect");
  149.                                     if (isSuccess){//成功发送,接收回执信息
  150.                                         mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);// 初始化成功后,就准备发送心跳
  151.                                     }else {//发送失败,处理善后工作
  152.                                         mHandler.removeCallbacks(heartBeatRunnable);
  153.                                         release();
  154.                                         releaseLastSocket(socket);
  155.                                     }
  156.                                     return;
  157.                                 }else if ("mdc_connectexit".equals(line)||"exit".equals(line)){//断开连接消息
  158.                                     Intent intent = new Intent(HEART_BEAT_ACTION);
  159.                                     intent.putExtra("message""exit");
  160.                                     sendBroadcast(intent);
  161.                                     return;
  162.                                 }
  163.                             }else if (line==null){
  164.                                 Log.i(TAG, "服务器发送过来的消息是空的");
  165.                             }
  166.                         } catch (IOException e) {
  167.                             Log.i(TAG,"Read-->"+e.getClass().getName());
  168.                             e.printStackTrace();
  169.                             continue;
  170.                         }
  171.                 }
  172.             }
  173.         }
  174.     }
  175.     @Override
  176.     public void onDestroy() {
  177.         super.onDestroy();
  178.         Log.i(TAG,"onDestroy");
  179.         mHandler.removeCallbacks(heartBeatRunnable);
  180.         mReadThread.release();
  181.         releaseLastSocket(socket);
  182.     }
  183. }

以上代码只是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://www.bieryun.com/1867.html

相关文章
|
3月前
|
Android开发 数据安全/隐私保护 虚拟化
安卓手机远程连接登录Windows服务器教程
安卓手机远程连接登录Windows服务器教程
538 4
|
4月前
|
Apache 数据中心 Windows
将网站迁移到阿里云Windows系统云服务器,访问该站点提示连接被拒绝,如何处理?
将网站迁移到阿里云Windows系统云服务器,访问该站点提示连接被拒绝,如何处理?
|
4月前
|
弹性计算 安全 Windows
通过远程桌面连接Windows服务器提示“由于协议错误,会话将被中断,请重新连接到远程计算机”错误怎么办?
通过远程桌面连接Windows服务器提示“由于协议错误,会话将被中断,请重新连接到远程计算机”错误怎么办?
|
4月前
|
IDE 网络安全 开发工具
IDE之vscode:连接远程服务器代码(亲测OK),与pycharm链接服务器做对比(亲自使用过了),打开文件夹后切换文件夹。
本文介绍了如何使用VS Code通过Remote-SSH插件连接远程服务器进行代码开发,并与PyCharm进行了对比。作者认为VS Code在连接和配置多个服务器时更为简单,推荐使用VS Code。文章详细说明了VS Code的安装、远程插件安装、SSH配置文件编写、服务器连接以及如何在连接后切换文件夹。此外,还提供了使用密钥进行免密登录的方法和解决权限问题的步骤。
1871 0
IDE之vscode:连接远程服务器代码(亲测OK),与pycharm链接服务器做对比(亲自使用过了),打开文件夹后切换文件夹。
|
4月前
|
IDE 网络安全 开发工具
IDE之pycharm:专业版本连接远程服务器代码,并配置远程python环境解释器(亲测OK)。
本文介绍了如何在PyCharm专业版中连接远程服务器并配置远程Python环境解释器,以便在服务器上运行代码。
744 0
IDE之pycharm:专业版本连接远程服务器代码,并配置远程python环境解释器(亲测OK)。
|
4月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
220 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
4月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
247 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
4月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
86 3
|
4月前
|
SQL 数据库
SQL-serve数据库不能连接本地服务器的解决方案
SQL-serve数据库不能连接本地服务器的解决方案
529 0
|
4月前
|
Ubuntu Linux Android开发
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器
本文介绍了如何在Android设备上安装Termux和AnLinux,并通过这些工具运行Ubuntu系统和桌面环境。
346 2
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器

热门文章

最新文章

  • 1
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    24
  • 2
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    33
  • 3
    Android历史版本与APK文件结构
    121
  • 4
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    29
  • 5
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    23
  • 6
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    57
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    37
  • 8
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    73
  • 9
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    118
  • 10
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29