流水不争先,争的是滔滔不绝

Android实现局域网设备的发现与通信

IM安全 macgrady 73℃

开发语言:ANDROID (JAVA) iOS(Object-C) PC (C++) 原生开发

后端开发语言:PHP

服务器操作系统: Linux Centos 7

数据库:mysql

DEMO:联系客服 QQ:340554038 下载DEMO 无限畅聊

本产品经过多年技术积累,不断完善优化用户体验,功能强大稳定,界面简洁美观,操作便捷。现将成品APP打包出售,源码全部开放。本套系统支持Android,ios,PC端,适配兼容各种常用手机设备,消息通讯采用私有二进制加密协议,通讯传输安全可靠。可提供服务器搭建服务,支持2次开发。本产品原生开发,非h5,wap网页封装产品可比,性能稳定,功能强大,满足企业私有化部署需求。有DEMO免费测试体验,详情请加QQ:340554038,非诚勿扰。

 IM功能详情

1端口类型安卓端、iOS端、、Windows(PC)端
2消息收发可以发送文字、语音、图片、视频、表情、名片、文件等多种消息类型
3收发方式支持阅后即焚 消息撤回 个人转发 群转发 复制 粘贴 删除等功能
41V1聊天文字,语音,图片,视频,GI动态图表情,传送文件,引用回复,撤回,复制,删除,撤回,录制,红包,个人转发,群转发等功能
5群聊文字,语音,图片,视频,GI动态图表情,传送文件,引用回复,撤回,复制,删除,撤回,录制,红包,个人转发,群转发,@特殊提醒等功能
6名片个人名片信息
7黑名单设置添加黑名单,拒收对方消息,拒绝加好友,基本资料不可见
8搜索关键字模糊查询,聊天记录,搜索好友,搜索联系人
9消息同步支出三端消息同步,聊天记录漫游同步
10消息回执显示消息回执状态 消息已读 对方正在输入
11送达文件 视频消息已发送 已下载 状态显示
12转发支持个人转发 群转发 以及多选合并转发
13消息推送支持后台消息推送功能 (须相关手机品牌处申请)
14重发网络不通或弱网信号,支持自动或手动重发功能
15多端登录支持三端同一账号同时登录(Android,iOS,PC)端
16账号多开同一设备可同时登录多个账号
17撤回消息1V1聊天或群聊支持设定时间内消息撤回
18阅后即焚以阅后即焚方式发送消息,文件,视频
19通讯方式RSA+流式加密通讯
20好友验证设置加好友时需不需要验证
21群禁言群主或管理员权限设置单独群成员禁言或群整体禁言
22群内加好友群主或管理员权限设置群成员加好友权限
23群资料设置设置分配群管理权限,添加更改群资料或设置群成员均可更改
24群转让群主转让给群成员
25群解散解散本群
26资料编辑编辑头像 手机 邮箱 生日 签名等基本信息
27二维码可通过二维码添加好友
28签到设置每日签到领取积分,可通过后台设置每日积分
29@群成员支持长按@群成员,被@的群成员具有被提醒
30邀请群成员邀请好友进群,可设置好友进群验证方式
31群讯息设置支持群信息设置,屏蔽/接收群消息
32设置管理员群主设置管理员,管理员具有一定的权限,可删除群成员
33删除群成员支持群主、管理员删除群成员
35朋友圈可编辑 点赞 评论 发表朋友圈
36设置可设置免打扰模式
37听筒外音和听筒设置
38后台管理管理员可对后台账号管理,并根据管理员权限进行分配

在使用Android开发智能设备时,一般会分为用于遥控与管理的Host端,和用于执行个性功能的Slave端,二者可以借助网络或蓝牙等途径实现通信。

局域网设备发现

如果是借助网络通信,那就必须知道对方的ip地址,而常见的网络环境中ip地址一般是通过DHCP服务动态分配的,所以事先无法确定对方的ip地址。为了确定对方的地址,可以通过向局域网内发送查找设备的广播,收到广播的Slave端就知道了Host端的ip地址,在向Host端发送应答包之后,双方就都知道了对方的ip地址。

局域网设备通信

在Host端与Slave端互相知道ip地址后,就可以实现局域网通信了。局域网通信一般通过TCP或UDP实现,TCP的优势在于它的可靠性,通过TCP传送的数据不丢失、无差错、不重复且按序到达,但只支持一对一,效率比UDP低;UDP的优势在于效率比TCP高,支持一对一、一对多、多对一、多对多,但不保证可靠性,网络差的环境下可能丢包和顺序错乱。因此如果对可靠性要求较高,建议使用TCP,如果对实时性要求较高则可以使用UDP。

代码实现设备发现

编写用于搜索局域网中设备DeviceSearcher,向局域网发送特定格式的广播包,并接收应答包,从而发现局域网中的己方设备。

/**

 * 用于搜索局域网中的设备

 */

public class DeviceSearcher {

         private static ExecutorService executorService = Executors.newSingleThreadExecutor();

         private static Handler uiHandler = new Handler(Looper.getMainLooper());

         /**

          * 开始搜索

          * @param onSearchListener

          */

         public static void search(OnSearchListener onSearchListener){

                   executorService.execute(new SearchRunnable(onSearchListener));

         }

         public static interface OnSearchListener{

                   void onSearchStart();

                   void onSearchedNewOne(Device device);

                   void onSearchFinish();

         }

         private static class SearchRunnable implements Runnable {

                   OnSearchListener searchListener;

                   public SearchRunnable(OnSearchListener listener){

                            this.searchListener = listener;

                   }

                   @Override

                   public void run() {

                            try {

                                     if(searchListener!=null){

                                               uiHandler.post(new Runnable() {

                                                        @Override

                                                        public void run() {

                                                                 searchListener.onSearchStart();

                                                        }

                                               });

                                     }

                                     DatagramSocket socket = new DatagramSocket();

                                     //设置接收等待时长

                                     socket.setSoTimeout(RemoteConst.RECEIVE_TIME_OUT);

                                     byte[] sendData = new byte[1024];

                                     byte[] receData = new byte[1024];

                DatagramPacket recePack = new DatagramPacket(receData, receData.length);

                //使用广播形式(目标地址设为255.255.255.255)的udp数据包

                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(“255.255.255.255”), RemoteConst.DEVICE_SEARCH_PORT);

                //用于存放已经应答的设备

                HashMap<String, Device> devices = new HashMap<>();

                //搜索指定次数

                                     for(int i=0;i<RemoteConst.SEARCH_DEVICE_TIMES;i++){

                                               sendPacket.setData(packSearchData(i+1));

                                               //发送udp数据包

                                               socket.send(sendPacket);

                         try {

                                  //限定搜索设备的最大数量

                             int rspCount = RemoteConst.SEARCH_DEVICE_MAX;

                             while (rspCount > 0) {

                                 socket.receive(recePack);

                                 final Device device = parseRespData(recePack);

                                 if(devices.get(device.getIp())==null){

                                          //保存新应答的设备

                                           devices.put(device.getIp(), device);

                                                                           if(searchListener!=null){

                                                                                    uiHandler.post(new Runnable() {

                                                                                             @Override

                                                                                             public void run() {

                                                                                                       searchListener.onSearchedNewOne(device);

                                                                                             }

                                                                                    });

                                                                           }

                                 }

                                                                 rspCount –;

                             }

                         } catch (SocketTimeoutException e) {

                                  e.printStackTrace();

                         }

                                     }

                                     socket.close();

                                     if(searchListener!=null){

                                               uiHandler.post(new Runnable() {

                                                        @Override

                                                        public void run() {

                                                                 searchListener.onSearchFinish();

                                                        }

                                               });

                                     }

                            } catch (IOException e) {

                                     e.printStackTrace();

                            }

                   }

        /**

         * 校验和解析应答的数据包

                    * @param pack udp数据包

                    * @return

         */

                   private Device parseRespData(DatagramPacket pack) {

                            if (pack.getLength() < 2) {

                     return null;

                 }

                 byte[] data = pack.getData();

                 int offset = pack.getOffset();

                 //检验数据包格式是否符合要求

                 if (data[offset++] != RemoteConst.PACKET_PREFIX || data[offset++] != RemoteConst.PACKET_TYPE_SEARCH_DEVICE_RSP) {

                     return null;

                 }

                 int length = data[offset++];

                 String uuid = new String(data, offset, length);

                            return new Device(pack.getAddress().getHostAddress(), pack.getPort(), uuid);

                   }

                   /**

         * 生成搜索数据包

                    * 格式:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]

                    *  packType – 报文类型

                    *  sendSeq – 发送序列

                    *  dataLen – 数据长度

                    *  data – 数据内容

                    * @param seq

         * @return

         */

                   private byte[] packSearchData(int seq) {

                            byte[] data = new byte[6];

                            int offset = 0;

                            data[offset++] = RemoteConst.PACKET_PREFIX;

                            data[offset++] = RemoteConst.PACKET_TYPE_SEARCH_DEVICE_REQ;

                            data[offset++] = (byte) seq;

                            data[offset++] = (byte) (seq >> 8);

                            data[offset++] = (byte) (seq >> 16);

                            data[offset++] = (byte) (seq >> 24);

                            return data;

                   }

         }

编写用于响应搜索广播的DeviceSearchResponser,监听局域网中的广播包,在收到符合要求的广播包后发送应答包,并带上自己的标识。

/**

 * 用于响应局域网设备搜索

 */

public class DeviceSearchResponser {

    private static SearchRespThread searchRespThread;

    /**

     * 启动响应线程,收到设备搜索命令后,自动响应

     */

    public static void open() {

        if (searchRespThread == null) {

            searchRespThread = new SearchRespThread();

            searchRespThread.start();

        }

    }

    /**

     * 停止响应

     */

    public static void close() {

        if (searchRespThread != null) {

            searchRespThread.destory();

            searchRespThread = null;

        }

    }

    private static class SearchRespThread extends Thread {

        DatagramSocket socket;

        volatile boolean openFlag;

        public void destory() {

            if (socket != null) {

                socket.close();

                socket = null;

            }

            openFlag = false;

        }

        @Override

        public void run() {

            try {

                //指定接收数据包的端口

                socket = new DatagramSocket(RemoteConst.DEVICE_SEARCH_PORT);

                byte[] buf = new byte[1024];

                DatagramPacket recePacket = new DatagramPacket(buf, buf.length);

                openFlag = true;

                while (openFlag) {

                    socket.receive(recePacket);

                    //校验数据包是否是搜索包

                    if (verifySearchData(recePacket)) {

                        //发送搜索应答包

                        byte[] sendData = packSearchRespData();

                        DatagramPacket sendPack = new DatagramPacket(sendData, sendData.length, recePacket.getSocketAddress());

                        socket.send(sendPack);

                    }

                }

            } catch (IOException e) {

                destory();

            }

        }

        /**

         * 生成搜索应答数据

         * 协议:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]

         * packType – 报文类型

         * sendSeq – 发送序列

         * dataLen – 数据长度

         * data – 数据内容

         * @return

         */

        private byte[] packSearchRespData() {

            byte[] data = new byte[1024];

            int offset = 0;

            data[offset++] = RemoteConst.PACKET_PREFIX;

            data[offset++] = RemoteConst.PACKET_TYPE_SEARCH_DEVICE_RSP;

            // 添加UUID数据

            byte[] uuid = getUuidData();

            data[offset++] = (byte) uuid.length;

            System.arraycopy(uuid, 0, data, offset, uuid.length);

            offset += uuid.length;

            byte[] retVal = new byte[offset];

            System.arraycopy(data, 0, retVal, 0, offset);

            return retVal;

        }

        /**

         * 校验搜索数据是否符合协议规范

         * 协议:$(1) + packType(1) + sendSeq(4) + dataLen(1) + [data]

         * packType – 报文类型

         * sendSeq – 发送序列

         * dataLen – 数据长度

         * data – 数据内容

         */

        private boolean verifySearchData(DatagramPacket pack) {

            if (pack.getLength() < 6) {

                return false;

            }

            byte[] data = pack.getData();

            int offset = pack.getOffset();

            int sendSeq;

            if (data[offset++] != ‘$’ || data[offset++] != RemoteConst.PACKET_TYPE_SEARCH_DEVICE_REQ) {

                return false;

            }

            sendSeq = data[offset++] & 0xFF;

            sendSeq |= (data[offset++] << 8) & 0xFF00;

            sendSeq |= (data[offset++] << 16) & 0xFF0000;

            sendSeq |= (data[offset++] << 24) & 0xFF000000;

            if (sendSeq < 1 || sendSeq > RemoteConst.SEARCH_DEVICE_TIMES) {

                return false;

            }

            return true;

        }

        /**

         * 获取设备uuid

         * @return

         */

        private byte[] getUuidData() {

            return (Build.PRODUCT + Build.ID).getBytes();

        }

    }

}

————————————————

版权声明:本文为CSDN博主「gogo_wei」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/gogo_wei/article/details/83118329

版权声明:部分文章、图片等内容为用户发布或互联网整理而来,仅供学习参考。如有侵犯您的版权,请联系我们,将立刻删除。
点击这里给我发消息