socket在小弟大上的运用,通讯模块应该是怎么着的

1.2 WIFI链接

在 Android 上,1个总体的 UDP 通讯模块应该是何许的?

本文原创,转发请申明出处。
欢迎关切自作者的
简书
,关心本身的专题 Android
Class

我会长期始终不渝为大家收音和录音简书上高质量的 Android 相关博文。

正文例子的兼具代码在这边:

github 连接在此间
UDPSocket德姆o

写在头里:
在下周写了一篇关于 TCP 和 UDP 概念区别相比的作品:

TCP与UDP差别比较分析

在那篇小说中,在可相信性、数据发送、适用场景等多少个地点剖析了二者的界别。而本文的目标是想给大家介绍下在
Android 设备上,多少个有线电话通过热点连接另二个手提式有线话机。那种光景下,完整的 UDP
通讯模块应该考虑哪些方面,又应该怎样优化,怎么着规避一些坑呢?

能够读取手提式有线电话机的PID和VID,明确唯一的装备,能够给手提式有线电话机安装相应的驱动等

处理接收的新闻

    /**
     * 处理接受到的消息
     */
    private void receiveMessage() {
        while (isThreadRunning) {
            try {
                if (client != null) {
                    client.receive(receivePacket);
                }
                lastReceiveTime = System.currentTimeMillis();
                Log.d(TAG, "receive packet success...");
            } catch (IOException e) {
                Log.e(TAG, "UDP数据包接收失败!线程停止");
                stopUDPSocket();
                e.printStackTrace();
                return;
            }

            if (receivePacket == null || receivePacket.getLength() == 0) {
                Log.e(TAG, "无法接收UDP数据或者接收到的UDP数据为空");
                continue;
            }

            String strReceive = new String(receivePacket.getData(), 0, receivePacket.getLength());
            Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());

            //解析接收到的 json 信息

            // 每次接收完UDP数据后,重置长度。否则可能会导致下次收到数据包被截断。
            if (receivePacket != null) {
                receivePacket.setLength(BUFFER_LENGTH);
            }
        }
    }

拍卖接收信息时,有多少个值得注意的点:

  1. receive 方法是阻塞的,没收到数量包时会直接不通,所以要放置子线程中;
  2. 每趟接到到音信随后,重新调用 receivePacket.setLength
  3. 选择新闻刷新lastReceiveTime的值,暂停心跳包的殡葬;

拍卖收到的多寡具体在事情上正是刚刚大家谈的发送数据的题材,视工作而定。

 

“用户”的概念

上文已经谈过了 UDP
的性状,如果二个部手机早已拉开了热点,若三个手提式有线电话机与她相连接,则多个手提式有线电话机发送的消息它都得以吸收。假若发送方的端口与接收方的端口相同的话,甚至自身发的音信,自个儿都能够收起。那就很难堪了,也正是说大家既要剔除自身发给本人的新闻,也得区分分歧手提式有线电话机发来的新闻,那几个时候就理应有一个“用户”的定义。

创造 User 对象,有怎么样属性能够看本身的作业,本文的例子就有
ip、imei、以及 softversion。

    /**
     * 创建本地用户信息
     */
    private void createUser() {
        if (localUser == null) {
            localUser = new Users();
        }
        if (remoteUser == null) {
            remoteUser = new Users();
        }

        localUser.setImei(DeviceUtil.getDeviceId(mContext));
        localUser.setSoftVersion(DeviceUtil.getPackageVersionCode(mContext));

        if (WifiUtil.getInstance(mContext).isWifiApEnabled()) {// 判断当前是否是开启热点方
            localUser.setIp("192.168.43.1");
        } else {// 当前是开启 wifi 方
            localUser.setIp(WifiUtil.getInstance(mContext).getLocalIPAddress());
            remoteUser.setIp(WifiUtil.getInstance(mContext).getServerIPAddress());
        }
    }

    /**
     * <p><b>IMEI.</b></p> Returns the unique device ID, for example, the IMEI for GSM and the MEID
     * or ESN for CDMA phones. Return null if device ID is not available.
     * <p>
     * Requires Permission: READ_PHONE_STATE
     *
     * @param context
     * @return
     */
    public synchronized static String getDeviceId(Context context) {
        if (context == null) {
            return "";
        }

        String imei = "";

        try {
            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            if (tm == null || TextUtils.isEmpty(tm.getDeviceId())) {
                // 双卡双待需要通过phone1和phone2获取imei,默认取phone1的imei。
                tm = (TelephonyManager) context.getSystemService("phone1");
            }

            if (tm != null) {
                imei = tm.getDeviceId();
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        }


        return imei;
    }

此间就不将富有的代码展开来看了。若是有了手提式有线电话机的 imei
号,那很不难就能够来做身份的界别,你既能够分别区其他发送方,也足以去除掉本人发给自身的音信。当然要是供给越来越多的音讯,能够服从自身的工作分别,将那几个新闻作为发送的
messge,通过 Socket 发送。

写在后边:
到今后始于本文的当先五分三内容都早已介绍成功,有的同学或许会咨询,你要用3个心跳来保持三个假的“长连接”,使用起来相比艰巨,而且还或许经受
UDP 造成的丢包的惨痛,为啥不选用 TCP
呢?问得好,其实那个版本是当下做的第3个本子,之后就使用 TCP+UDP
的艺术来达成这些模块了,下一篇小说再来看看加上 TCP 的立异版吧。

本文例子的拥有代码在这边:

github 连接在此间
UDPSocket德姆o

1.1 USB链接

UDP 在 Java 中的使用

咱俩都通晓,开发3个 Android 应用程序,近期多数大概利用的是 Java
语言。在 Java 语言中怎么去选拔 UDP 协议呢?

上篇文章中大家没说 Socket,其实 Socket 能够知晓为对 TCP、UDP
合计在程序行使范围的卷入,提供出某个 api 来供程序员调用开发,那便是Socket 最外面包车型的士意思。

在 Java 中,与 UDP 相关的类有 DatagramSocket、DatagramPacket
等,关于他们的运用,那里不重要介绍,能够看上面那篇小说:

Java UDP
Socket

好了,假诺大家对他们的利用都已大体通晓,可以规范启幕本文的始最后。

2.2数据量小

事件与数量

事件与数据那多个模块与事务就牢牢有关了。

先来说多少,双方发送的数据格式你们能够任意定义,当然小编以为还是定义成常规的
Json
格式就好。其中能够涵盖部分人命关天的轩然大波字段:比如广播心跳包、收到心跳包给对方上线的作答包、超时的底线包、以及各样业务相关的多寡等等。

理所当然发送数据时是转换到二进制数组发送的。发送普通话字符、图片等都未曾难点,但是只怕有一部分细节需求小心,随时
google 一下就好了。

再来说下事件:

与事务非亲非故的风浪有啥样?

比如:
DatagramSocket.send 方法之后正是发送数据成功的风浪;

DatagramSocket.receive 方法之后是数据接受成功的风浪;

在心跳包发送一段时间,仍没有吸收回信时,是两次三番超时的风云;

与工作相关的轩然大波就和我们上文提到的数据类型有关了,设备上线,心跳包回应等等。

事件又怎么着发送出去,布告到各样页面吗?用
Listener、大概别的事件总线的三方库都没难点,看您本身选取了。

 

开端化1个 UDPSocket

率先成立二个叫 UDPSocket 的类。

    public UDPSocket(Context context) {

        this.mContext = context;

        int cpuNumbers = Runtime.getRuntime().availableProcessors();
        // 根据CPU数目初始化线程池
        mThreadPool = Executors.newFixedThreadPool(cpuNumbers * POOL_SIZE);
        // 记录创建对象时的时间
        lastReceiveTime = System.currentTimeMillis();
    }

在构造方法里,大家开始展览下有些开头化操作,简单的话就是创建1个线程池,记录一下脚下岁月飞秒值,至于他们有如何用,再往下看:

    public void startUDPSocket() {
        if (client != null) return;
        try {
            // 表明这个 Socket 在设置的端口上监听数据。
            client = new DatagramSocket(CLIENT_PORT);

            if (receivePacket == null) {
                // 创建接受数据的 packet
                receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
            }

            startSocketThread();
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

那里我们率先创建了二个 DatagramSocket 作为“客户端”,其实 UDP
自个儿没有客户端和服务端的概念,唯有发送方和接收方的概念,大家把发送方一时当成是三个客户端吧。

创制 DatagramSocket
对象时,传入了叁个端口号,这几个端口号能够在五个范围内本身定义,表示那么些DatagramSocket 在此端口上监听数据。

接下来再创设了一个 DatagramPacket 对象,作为数据的接收包。

最后调用 startSocketThread 运行发送和接收数据的线程。

    /**
     * 开启发送数据的线程
     */
    private void startSocketThread() {
        clientThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "clientThread is running...");
                receiveMessage();
            }
        });
        isThreadRunning = true;
        clientThread.start();

        startHeartbeatTimer();
    }

先是 clientThread 线程的指标是调用 DatagramSocket receive 方法,因为
receive
方法是阻塞的,不可能放在主线程,所以本来张开2个子线程了。receiveMessage
就是拍卖接受到的 UDP
数据报,我们先不看接受多少的那么些点子,终究还没人发音信呢,自然就谈不上收了。

2.用途

心跳包保持“长连接”

到来本文的率先个关键,大家都知情 UDP 本身并未连接的定义。在 Android
端应用 UDP 和 TCP
的情形是3个部手提式有线电电话机总是另2个部手提式有线电话机的热点,二者处在同一局域网中。在二者并不知道对方的留存时,怎么才能觉察互相呢?

透过心跳包的章程,双方都每隔一段时间发三个 UDP
包,要是对方接到到了,那就能明白对方的 ip,建立起通讯了。

    private static final long TIME_OUT = 120 * 1000;
    private static final long HEARTBEAT_MESSAGE_DURATION = 10 * 1000;
    /**
     * 启动心跳,timer 间隔十秒
     */
    private void startHeartbeatTimer() {
        timer = new HeartbeatTimer();
        timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
            @Override
            public void onSchedule() {
                Log.d(TAG, "timer is onSchedule...");
                long duration = System.currentTimeMillis() - lastReceiveTime;
                Log.d(TAG, "duration:" + duration);
                if (duration > TIME_OUT) {//若超过两分钟都没收到我的心跳包,则认为对方不在线。
                    Log.d(TAG, "超时,对方已经下线");
                    // 刷新时间,重新进入下一个心跳周期
                    lastReceiveTime = System.currentTimeMillis();
                } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超过十秒他没收到我的心跳包,则重新发一个。
                    String string = "hello,this is a heartbeat message";
                    sendMessage(string);
                }
            }

        });
        timer.startTimer(0, 1000 * 10);
    }

那段心跳的指标就是每隔十秒通过 sendMessage
发送贰个信息,看看对方能或无法收到。若对方接收信息,则刷新下
lastReceiveTime 的光阴。

此间自个儿每隔十秒向对方发送了二个字符串。

    private static final String BROADCAST_IP = "192.168.43.255";
    /**
     * 发送心跳包
     *
     * @param message
     */
    public void sendMessage(final String message) {
        mThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);

                    DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);

                    client.send(packet);

                    Log.d(TAG, "数据发送成功");

                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        });
    }

此处就是出殡和埋葬贰个音信的代码。最初在填充 DatagramPacket
的参数之时,作者有2个难点,那么些 targetAddress 其实是自身的 ip
地址。难点来了,我填写了温馨的 ip
地址和对方的端口,怎么恐怕找获得对方呢?你大概有2个迷惑
“192.168.43.255” 这么些团结的 ip 地址是怎么来的,为啥要那样定义?

先是 android 手机开启热点,能够明白成一个网关,有三个默许的 ip
地址:”192.168.43.1″

那些 ip 地址不是自身瞎编的一个,在 Android 源码之中,正是这么定义的:

WifiStateMachine

                        ifcg = mNwService.getInterfaceConfig(intf);
                        if (ifcg != null) {
                            /* IP/netmask: 192.168.43.1/255.255.255.0 */
                            ifcg.setLinkAddress(new LinkAddress(
                                    NetworkUtils.numericToInetAddress("192.168.43.1"), 24));
                            ifcg.setInterfaceUp();

                            mNwService.setInterfaceConfig(intf, ifcg);
                        }

故此小编是明白所谓打开热点一方的 ip 地址,而 UDP
发送信息时还有三个特色,正是发出去的音信,处在整个网关的装置是都足以采取到的,所以自个儿要好的
ip 地址就定为了 “192.168.43.255”,所以那么些 ip 地址和 “192.168.43.1”
在同一网关中,你发送的音讯,它是足以收起的。

有关怎么判断五个 ip 地址是还是不是处于同一网段中:

看清三个IP大小及是或不是在同3个网段中

来做八个阶段计算:

首先我们创制了三个发送端
DatagramSocket,运行了2个心跳程序,每间隔一段时间发送多少个心跳包。

因为本身晓得热点方的 ip 地址是私下认可的 “192.168.43.1”,并且 UDP
的风味就是发送的新闻同一网段的装备都足以吸收接纳。所以发送方的 ip
地址定为了与热点一方处于同一网段的 “192.168.43.255”。

 

socket在固化端口通讯

pc在微型总结机在全路网段发送UDP数据包,手提式有线电话机总是wifi后能够监听那几个端口,收到数据包,试图与pc机建立连接

 

 

 

音信推送,精准营销

2.1 数据量大,实时传输必要高

import java.util.Timer;
import java.util.TimerTask;


public class TimerTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1");
            }
        }, 1000,1000);//1秒以后执行,每一秒执行一次
    }

}

1.手提式有线电电话机帮手

 

通过蓝牙5.0( Bluetooth® )Socket进行配对

聊天工具,股票交易

1.3 蓝牙

每隔10分钟发先生送UDP包,数据包中要求包罗身份认证信息(应用+用户id)

 

Socket长连接,心跳包,每隔30秒发送心跳包给服务端