一、前言:为了公司研究推送和保活方案

尝试了以Android作为Socket长连接的服务端server,以后台作为Socket的客户端client,后台进行推送,这样的目的是保证,后台不需要长时间连接多个设备,后台每次有新的消息,创建多个Client通过Socket推送给Android终端设备,数据传输完成后,所有的client终端关闭,释放资源。而Android作为SocketServer一直监听端口,处于挂起状态,所以保证了推送数据的实时性。当然这种做法缺点很明显:1.Android长期挂起耗电,2.一旦Android终端程序被杀,无法推送到手机,3.Android应用层没有真正的保活,4.也是最大的问题Android终端能通过3G、4G上网,WIFI上网,VPN上网,作为SocketServer需要提供公网的IP,因此Android端的IP是多少?怎么获取?

二、开发过程:

2.1、带着以上问题,进行Demo的编写和测试

2.2 开发环境:Android 5.1设备,后台Springboot集成的Tomcat服务

2.3 具体代码:

后台代码

 

 

Android作为SocketServer以及手机IP问题_第1张图片

@RequestMapping("/efss/test")@RestControllerpublic class TestController extends BaseController {    @RequestMapping("/uploadTerminalIp")    public ApiResponse uploadTerminalIp(@RequestBody String json) {        JSONObject jsonObject = JSONObject.parseObject(json);        JSONObject jsonData = jsonObject.getJSONObject("nameValuePairs");        String ip = jsonData.getString("ip");        Integer port = jsonData.getInteger("port");        if (StringUtils.isBlank(ip)) {            return apiFail("ip地址为空");        }        if (null == port) {            return apiFail("port为空");        }        new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    try {                        String currentTimeStr = TimeUtil.getDate2String(new Date(), "yyyy-MM-dd HH:mm:ss") + ",随机码:" + new Random().nextFloat();                        String responseStr = SocketUtil.tcpPost(ip, port.toString(), currentTimeStr);                        Thread.sleep(5000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }).start();        return apiSuccess("Socket connected....!!!.............");    }}   

模拟后台接口中直接写了一个定时,每5秒通过Socket向Android发送一个时间。

贴下SocketUtil 只负责写(推送消息),不读,当然如果需要自己添加实现

/** * * Created by jambestwick@126.com * * on 2019/5/31 * * */public class SocketUtil {    private static final Logger logger = LoggerFactory.getLogger(SocketUtil.class);    /**     * 发送socket请求     *     * @param clientIp     * @param clientPort     * @param msg     * @return     */    public static synchronized String tcpPost(String clientIp, String clientPort, String msg) {        String rs = "";        if (clientIp == null || "".equals(clientIp) || clientPort == null || "".equals(clientPort)) {            logger.error("Ip或端口不存在...");            return null;        }        int clientPortInt = Integer.parseInt(clientPort);        logger.info("clientIp:" + clientIp + " clientPort:" + clientPort);        Socket s = null;        OutputStream out = null;        //InputStream in = null;        try {            s = new Socket(clientIp, clientPortInt);            s.setSendBufferSize(4096);            s.setTcpNoDelay(true);            s.setSoTimeout(60 * 1000);            s.setKeepAlive(true);            out = s.getOutputStream();            //in = s.getInputStream();            //准备报文msg            logger.info("准备发送报文:" + msg);            out.write(msg.getBytes("GBK"));            out.flush();        } catch (Exception e) {            logger.error("tcpPost发送请求异常:" + e.getMessage());        } finally {            logger.info("tcpPost(rs):" + rs);            try {                if (out != null) {                    out.close();                    out = null;                }                if (s != null) {                    s.close();                    s = null;                }            } catch (IOException e) {                e.printStackTrace();            }        }        return rs;    }}

前端

package com.yxytech.parkingcloud.efsspda.service;import android.app.Service;import android.content.Intent;import android.media.MediaPlayer;import android.os.Build;import android.os.IBinder;import android.support.annotation.Nullable;import com.yxytech.parkingcloud.baselibrary.utils.LogUtil;import com.yxytech.parkingcloud.efsspda.R;import com.yxytech.parkingcloud.efsspda.utils.NetWorkUtil;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.util.Date;import java.util.concurrent.Executor;import java.util.concurrent.Executors;/** * 

文件描述:

*

作者:jambestwick

*

创建时间:2019/5/30

*

更新时间:2019/5/30

*

版本号:${VERSION}

*/public class SingASongService extends Service { private static final String TAG = SingASongService.class.getName(); private MediaPlayer mMediaPlayer; private Thread thread; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); MyThread myThread = new MyThread(); thread = new Thread(myThread); mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.no_notice); mMediaPlayer.setLooping(true);//循环播放 LogUtil.d(TAG, "onCreate() 创建播放对象:" + mMediaPlayer.hashCode()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { thread.start(); LogUtil.d(TAG, "播放时 线程名称:" + thread.getName()); return START_STICKY; } private void startPlaySong() { if (mMediaPlayer == null) { mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.no_notice); LogUtil.d(TAG, "音乐启动播放,播放对象为: " + mMediaPlayer.hashCode()); mMediaPlayer.start(); } else { mMediaPlayer.start(); LogUtil.d(TAG, "音乐启动播放,播放对象为: " + mMediaPlayer.hashCode()); } try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); }// if (mMediaPlayer != null) {// mMediaPlayer.pause();// LogUtil.d(TAG, "音乐启动播放,播放对象为: " + mMediaPlayer.hashCode());// int progress = mMediaPlayer.getCurrentPosition();// LogUtil.d(TAG, "音乐暂停,播放进度:" + progress);// } } private void stopPlaySong() { if (mMediaPlayer != null) { mMediaPlayer.stop(); LogUtil.d(TAG, "音乐停止播放,播放对象为:" + mMediaPlayer.hashCode()); LogUtil.d(TAG, "音乐播放器是否在循环:" + mMediaPlayer.isLooping()); LogUtil.d(TAG, "音乐播放器是否还在播放:" + mMediaPlayer.isPlaying()); mMediaPlayer.release(); LogUtil.d(TAG, "播放对象销毁,播放对象为:" + mMediaPlayer.hashCode()); mMediaPlayer = null; } } private void startSocketConn() { //服务端在8899端口监听客户端请求的TCP连接 try { ServerSocket server = new ServerSocket(8899); Socket client = null; //通过调用Executors类的静态方法,创建一个ExecutorService实例 //ExecutorService接口是Executor接口的子接口 Executor service = Executors.newCachedThreadPool(); while (true) { //等待客户端的连接 System.out.println("等待客户端连接!"+new Date()); LogUtil.e(NetWorkUtil.class, "等待客户端连接" ); client = server.accept(); System.out.println("与客户端连接成功!" + new Date()); LogUtil.e(NetWorkUtil.class, "与客户端连接成功" ); //调用execute()方法时,如果必要,会创建一个新的线程来处理任务,但它首先会尝试使用已有的线程, //如果一个线程空闲60秒以上,则将其移除线程池; //另外,任务是在Executor的内部排队,而不是在网络中排队 service.execute(new NetWorkUtil.ServerThread(client)); } } catch (IOException e) { e.printStackTrace(); } //server.close(); } @Override public void onDestroy() { super.onDestroy(); mMediaPlayer.pause(); LogUtil.d(TAG, "恢复播放 时当前播放器对象:" + mMediaPlayer.hashCode()); stopPlaySong(); LogUtil.d(TAG, "应用播放服务被杀死,正在重启"); LogUtil.d(TAG, "目标播放工作线程是否存活:" + thread.isAlive()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(new Intent(getApplicationContext(), SingASongService.class)); } else { startService(new Intent(getApplicationContext(), SingASongService.class)); } } class MyThread implements Runnable { @Override public void run() { startPlaySong(); startSocketConn(); } }}

package com.yxytech.parkingcloud.efsspda.utils;import android.content.Context;import android.util.Log;import com.yxytech.parkingcloud.baselibrary.utils.LogUtil;import com.yxytech.parkingcloud.baselibrary.utils.ToastUtil;import org.json.JSONObject;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintStream;import java.net.HttpURLConnection;import java.net.Inet4Address;import java.net.InetAddress;import java.net.NetworkInterface;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketException;import java.net.URL;import java.util.Enumeration;/** * 

文件描述:

*

作者:jambestwick

*

创建时间:2019/5/31

*

更新时间:2019/5/31

*

版本号:${VERSION}

*

邮箱:jambestwick@126.com

*/public class NetWorkUtil { public static final int SOCKET_PORT = 8899; /** * 获取内网IP地址 * * @return * @throws SocketException */ public static String getLocalIPAddress() throws SocketException { for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { NetworkInterface intf = en.nextElement(); for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && (inetAddress instanceof Inet4Address)) { return inetAddress.getHostAddress().toString(); } } } return "null"; } /**** * 获取外网的IP地址 * @return * * * ***/ public static String getNetIp() { String IP = ""; try { String address = "http://pv.sohu.com/cityjson?ie=utf-8"; URL url = new URL(address); //URLConnection htpurl=url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); connection.setUseCaches(false); connection.setRequestMethod("GET"); connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.7 Safari/537.36"); //设置浏览器ua 保证不出现503 if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream in = connection.getInputStream(); // 将流转化为字符串 BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String tmpString = ""; StringBuilder retJSON = new StringBuilder(); while ((tmpString = reader.readLine()) != null) { retJSON.append(tmpString + "\n"); } JSONObject jsonObject = new JSONObject(retJSON.toString()); String code = jsonObject.getString("code"); if (code.equals("0")) { JSONObject data = jsonObject.getJSONObject("data"); IP = data.getString("ip") + "(" + data.getString("country") + data.getString("area") + "区" + data.getString("region") + data.getString("city") + data.getString("isp") + ")"; Log.e("提示", "您的IP地址是:" + IP); } else { IP = ""; Log.e("提示", "IP接口异常,无法获取IP地址!"); } } else { IP = ""; Log.e("提示", "网络连接异常,无法获取IP地址!"); } } catch ( Exception e) { IP = ""; Log.e("提示", "获取IP地址时出现异常,异常信息是:" + e.toString()); } return IP; } public static class serverPool { public static void conn(int port) throws IOException { //服务端在20006端口监听客户端请求的TCP连接 final ServerSocket server = new ServerSocket(port); //在线程池中一共只有THREADPOOLSIZE个线程, //最多有THREADPOOLSIZE个线程在accept()方法上阻塞等待连接请求 //匿名内部类,当前线程为匿名线程,还没有为任何客户端连接提供服务 Thread thread = new Thread() { public void run() { //线程为某连接提供完服务后,循环等待其他的连接请求 while (true) { try { //等待客户端的连接 Socket client = server.accept(); System.out.println("与客户端连接成功!"); //一旦连接成功,则在该线程中与客户端通信 ServerThread.execute(client); } catch (IOException e) { e.printStackTrace(); } } } }; //先将所有的线程开启 thread.start(); } } /** * 该类为多线程类,用于服务端 */ public static class ServerThread implements Runnable { private Socket client = null; public ServerThread(Socket client) { this.client = client; } //处理通信细节的静态方法,这里主要是方便线程池服务器的调用 public static void execute(Socket client) { String str = null; try { //获取Socket的输出流,用来向客户端发送数据 PrintStream out = new PrintStream(client.getOutputStream()); //获取Socket的输入流,用来接收从客户端发送过来的数据 BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream())); boolean flag = true; while (flag) { //接收从客户端发送过来的数据 str = buf.readLine(); if (str == null || "".equals(str)) { flag = false; } else { if ("bye".equals(str)) { flag = false; } else { //将接收到的字符串前面加上echo,发送到对应的客户端 out.println("echo:" + str); LogUtil.e(NetWorkUtil.class, "接受到的推送信息:" + str); } } } out.close(); buf.close(); client.close(); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { execute(client); } }}

Android作为SocketServer以及手机IP问题_第2张图片

这张图片是访问上面后台的接口需要的参数,根据自己的业务需求做,

逻辑是这样,Android端用户登录APP,通过NetWorkUtil.getNetIp()方法获取到手机的IP,定义一个端口(8899),发送给后台,后台通过发来的IP端口,连接到Socket并进行推送。

三、问题:

代码讲完了现在开始说问题

3.1 Android长期作为Socket的Server长期挂起耗电,由于我们是定制设备,一线人员都有充电设备,而且一个最多班次12小时,在锁屏的情况下是足够的。

3.2 Android进程被杀,这个需要大量真机适配,而且我们可以命令用户加入白名单,因此存活率大大提高。

3.3 重点:手机的IP问题:看到很多有关手机IP的文章,才分析出来,这个方案是不可行的,

我在WIFI局域网环境模拟是可以,我的后台是192.168.0.107,我的手机是192.168.0.105,手机Socket端口8899,收发都很正常。

但是问题就在公网上,由于你是内网地址,内网可能存在多台设备,暴露在公网上怎么知道连接哪台设备呢,所以作为SocketServer没有内网映射是做不到了,

还有大部分一线人员是在道路上,用的是4G网络,而运营商卡的IP地址会动态变化的,如下:

1.49.128.0,中国,贵州,安顺,电信1.49.129.0,中国,贵州,安顺,电信1.49.131.0,中国,贵州,安顺,电信1.49.132.0,中国,贵州,安顺,电信1.49.136.0,中国,贵州,贵阳,电信1.49.192.0,中国,贵州,黔西南,电信1.86.37.95,中国,陕西,西安,电信

看到这些大家会不会接的奇怪,没错这不是真正意义上的公网IP地址,手机上网的IP是运营商ISP自动分配的,每次上网运营商ISP都会选出一个空闲的IP给你,以保证你手机的IP在同一个时间里不会和其他手机相同。但这些IP是属于运营商基站的网内IP。
手机上网有点像公司内网上的电脑通过网关服务器上网。对外界,所有的电脑都用同一个IP地址,也就是网关服务器的IP地址。

所以,拿到了这个手机IP是无法通过公网访问到你的。

总结:

由此,做推送,还是的采用三方的平台,如:极光Android作为SocketServer以及手机IP问题_第3张图片推送,

或者你们可能有数据的安全考虑,那就自己开启服务,手机Pull拉取消息,其实也就短连接的接口访问一样了。

更多相关文章

  1. android handler 多线程demo
  2. android JNI层线程回调Java函数
  3. Android 客户端与服务器交互方式
  4. Android 使用Thread+Handler实现非UI线程更新UI界面
  5. Android异步加载图像小结(含线程池,缓存方法)[转]
  6. Android的UI设计与后台线程交互

随机推荐

  1. Source not found-Android(安卓)使用隐藏
  2. Android(安卓)将activity设置成对话框样
  3. Android调用相机和相册详解
  4. android有用的知识
  5. Android(安卓)ListView嵌套Button,Button
  6. android获取路径
  7. 如何制作Jar包并在android中调用jar包
  8. android 获取手机信息(device ip,os versio
  9. 为Android内核添加新驱动,并添加到menucon
  10. Ionic新建项目(以Android为例)