成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

在Android上,一個完整的UDP通信模塊應該是怎樣的?

移動開發(fā) Android
我們都知道,開發(fā)一個 Android 應用程序,目前大多數(shù)還是使用的是 Java 語言。在 Java 語言中怎么去使用 UDP 協(xié)議呢?

TCP與UDP差異對比分析

在這篇文章中,在可靠性、數(shù)據(jù)發(fā)送、適用場景等多個方面分析了二者的區(qū)別。而本文的目的是想給大家介紹下在 Android 設備上,一個手機通過熱點連接另一個手機。這種場景下,完整的 UDP 通信模塊應該考慮哪些方面,又應該如何優(yōu)化,如何避開一些坑呢?

[[206556]]

UDP 在 Java 中的使用

我們都知道,開發(fā)一個 Android 應用程序,目前大多數(shù)還是使用的是 Java 語言。在 Java 語言中怎么去使用 UDP 協(xié)議呢?

上篇文章中我們沒說 Socket,其實 Socket 可以理解為對 TCP、UDP 協(xié)議在程序使用層面的封裝,提供出一些 api 來供程序員調用開發(fā),這就是 Socket 最表層的含義。

在 Java 中,與 UDP 相關的類有 DatagramSocket、DatagramPacket 等,關于他們的使用,這里不著重介紹。

好了,假設大家對他們的使用都已大概了解,可以正式開始本文的內(nèi)容了。

初始化一個 UDPSocket

首先創(chuàng)建一個叫 UDPSocket 的類。

 

  1. public UDPSocket(Context context) { 
  2.         this.mContext = context; 
  3.         int cpuNumbers = Runtime.getRuntime().availableProcessors(); 
  4.         // 根據(jù)CPU數(shù)目初始化線程池 
  5.         mThreadPool = Executors.newFixedThreadPool(cpuNumbers * POOL_SIZE); 
  6.         // 記錄創(chuàng)建對象時的時間 
  7.         lastReceiveTime = System.currentTimeMillis(); 
  8.     } 

在構造方法里,我們進行下一些初始化操作,簡單來說就是創(chuàng)建一個線程池,記錄一下當前時間毫秒值,至于他們有什么用,再往下看:

 

  1. public void startUDPSocket() { 
  2.         if (client != nullreturn
  3.         try { 
  4.             // 表明這個 Socket 在設置的端口上監(jiān)聽數(shù)據(jù)。 
  5.             client = new DatagramSocket(CLIENT_PORT); 
  6.             if (receivePacket == null) { 
  7.                 // 創(chuàng)建接受數(shù)據(jù)的 packet 
  8.                 receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH); 
  9.             } 
  10.             startSocketThread(); 
  11.         } catch (SocketException e) { 
  12.             e.printStackTrace(); 
  13.         } 
  14.     } 

這里我們首先創(chuàng)建了一個 DatagramSocket 作為“客戶端”,其實 UDP 本身沒有客戶端和服務端的概念,只有發(fā)送方和接收方的概念,我們把發(fā)送方暫時當成是一個客戶端吧。

創(chuàng)建 DatagramSocket 對象時,傳入了一個端口號,這個端口號可以在一個范圍內(nèi)自己定義,表示這個 DatagramSocket 在此端口上監(jiān)聽數(shù)據(jù)。

然后又創(chuàng)建了一個 DatagramPacket 對象,作為數(shù)據(jù)的接收包。

***調用 startSocketThread 啟動發(fā)送和接收數(shù)據(jù)的線程。

 

  1. /** 
  2.      * 開啟發(fā)送數(shù)據(jù)的線程 
  3.      */ 
  4.     private void startSocketThread() { 
  5.         clientThread = new Thread(new Runnable() { 
  6.             @Override 
  7.             public void run() { 
  8.                 Log.d(TAG, "clientThread is running..."); 
  9.                 receiveMessage(); 
  10.             } 
  11.         }); 
  12.         isThreadRunning = true
  13.         clientThread.start(); 
  14.         startHeartbeatTimer(); 
  15.     } 

首先 clientThread 線程的目的是調用 DatagramSocket receive 方法,因為 receive 方法是阻塞的,不能放在主線程,所以自然開啟一個子線程了。receiveMessage 就是處理接受到的 UDP 數(shù)據(jù)報,我們先不看接受數(shù)據(jù)的這個方法,畢竟還沒人發(fā)消息呢,自然就談不上收了。

心跳包保持“長連接”

來到本文的***個重點,我們都知道 UDP 本身沒有連接的概念。在 Android 端應用 UDP 和 TCP 的場景是一個手機連接另一個手機的熱點,二者處在同一局域網(wǎng)中。在二者并不知道對方的存在時,怎么才能發(fā)現(xiàn)彼此呢?

通過心跳包的方式,雙方都每隔一段時間發(fā)一個 UDP 包,如果對方接收到了,那就能知道對方的 ip,建立起通信了。

 

  1. private static final long TIME_OUT = 120 * 1000; 
  2.     private static final long HEARTBEAT_MESSAGE_DURATION = 10 * 1000; 
  3.     /** 
  4.      * 啟動心跳,timer 間隔十秒 
  5.      */ 
  6.     private void startHeartbeatTimer() { 
  7.         timer = new HeartbeatTimer(); 
  8.         timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() { 
  9.             @Override 
  10.             public void onSchedule() { 
  11.                 Log.d(TAG, "timer is onSchedule..."); 
  12.                 long duration = System.currentTimeMillis() - lastReceiveTime; 
  13.                 Log.d(TAG, "duration:" + duration); 
  14.                 if (duration > TIME_OUT) {//若超過兩分鐘都沒收到我的心跳包,則認為對方不在線。 
  15.                     Log.d(TAG, "超時,對方已經(jīng)下線"); 
  16.                     // 刷新時間,重新進入下一個心跳周期 
  17.                     lastReceiveTime = System.currentTimeMillis(); 
  18.                 } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超過十秒他沒收到我的心跳包,則重新發(fā)一個。 
  19.                     String string = "hello,this is a heartbeat message"
  20.                     sendMessage(string); 
  21.                 } 
  22.             } 
  23.         }); 
  24.         timer.startTimer(0, 1000 * 10); 
  25.     } 

這段心跳的目的就是每隔十秒通過 sendMessage 發(fā)送一個消息,看看對方能不能收到。若對方收到消息,則刷新下 lastReceiveTime 的時間。

這里我每隔十秒向對方發(fā)送了一個字符串。

 

  1. private static final String BROADCAST_IP = "192.168.43.255"
  2.     /** 
  3.      * 發(fā)送心跳包 
  4.      * 
  5.      * @param message 
  6.      */ 
  7.     public void sendMessage(final String message) { 
  8.         mThreadPool.execute(new Runnable() { 
  9.             @Override 
  10.             public void run() { 
  11.                 try { 
  12.                     InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP); 
  13.                     DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT); 
  14.                     client.send(packet); 
  15.                     Log.d(TAG, "數(shù)據(jù)發(fā)送成功"); 
  16.                 } catch (UnknownHostException e) { 
  17.                     e.printStackTrace(); 
  18.                 } catch (IOException e) { 
  19.                     e.printStackTrace(); 
  20.                 } 
  21.             } 
  22.         }); 
  23.     } 

這里就是發(fā)送一個消息的代碼。最初在填寫 DatagramPacket 的參數(shù)之時,我有一個疑問,那個 targetAddress 其實是自己的 ip 地址。問題來了,我填寫了自己的 ip 地址和對方的端口,怎么可能找得到對方呢?你可能有一個疑惑 "192.168.43.255" 這個自己的 ip 地址是怎么來的,為什么要這么定義?

首先 android 手機開啟熱點,可以理解成一個網(wǎng)關,有一個默認的 ip 地址:"192.168.43.1"

這個 ip 地址不是我瞎編的一個,在 Android 源碼之中,就是這么定義的:

WifiStateMachine

 

  1. ifcg = mNwService.getInterfaceConfig(intf); 
  2.                         if (ifcg != null) { 
  3.                             /* IP/netmask: 192.168.43.1/255.255.255.0 */ 
  4.                             ifcg.setLinkAddress(new LinkAddress( 
  5.                                     NetworkUtils.numericToInetAddress("192.168.43.1"), 24)); 
  6.                             ifcg.setInterfaceUp(); 
  7.                             mNwService.setInterfaceConfig(intf, ifcg); 
  8.                         } 

所以我是知道所謂打開熱點一方的 ip 地址,而 UDP 發(fā)送消息時還有一個特性,就是發(fā)出去的消息,處在整個網(wǎng)關的設備是都可以接收到的,所以我自己的 ip 地址就定為了 "192.168.43.255",所以這個 ip 地址和 "192.168.43.1" 在同一網(wǎng)關中,你發(fā)送的消息,它是可以收到的。

至于怎么判斷兩個 ip 地址是否處在同一網(wǎng)段中:

判斷兩個IP大小及是否在同一個網(wǎng)段中

來做一個階段總結:

首先我們創(chuàng)建了一個發(fā)送端 DatagramSocket,啟動了一個心跳程序,每間隔一段時間發(fā)送一個心跳包。

因為我知道熱點方的 ip 地址是默認的 "192.168.43.1",并且 UDP 的特性就是發(fā)送的消息同一網(wǎng)段的設備都可以收到。所以發(fā)送方的 ip 地址定為了與熱點一方處在同一網(wǎng)段的 "192.168.43.255"。

事件與數(shù)據(jù)

事件與數(shù)據(jù)這兩個模塊與業(yè)務就緊密相關了。

先來說數(shù)據(jù),雙方發(fā)送的數(shù)據(jù)格式你們可以隨意定義,當然我覺得還是定義成常規(guī)的 Json 格式就好。其中可以包含一些關鍵的事件字段:比如廣播心跳包、收到心跳包給對方上線的應答包、超時的下線包、以及各種業(yè)務相關的數(shù)據(jù)等等。

當然發(fā)送數(shù)據(jù)時是轉換成二進制數(shù)組發(fā)送的。發(fā)送中文字符、圖片等都沒有問題,但是可能有一些細節(jié)需要注意,隨時 google 一下就好了。

再來說下事件:

與業(yè)務無關的事件有哪些?

比如:

  • DatagramSocket.send 方法之后就是發(fā)送數(shù)據(jù)成功的事件;
  • DatagramSocket.receive 方法之后是數(shù)據(jù)接收成功的事件;
  • 在心跳包發(fā)送一段時間,仍沒有接到回信時,是連接超時的事件;
  • 與業(yè)務相關的事件就和我們上文提到的數(shù)據(jù)類型有關了,設備上線,心跳包回應等等。

事件又如何發(fā)送出去,通知到各個頁面呢?用 Listener、或者其他事件總線的三方庫都沒問題,看你自己選擇了。

處理接收的消息

 

  1. /** 
  2.     * 處理接受到的消息 
  3.     */ 
  4.    private void receiveMessage() { 
  5.        while (isThreadRunning) { 
  6.            try { 
  7.                if (client != null) { 
  8.                    client.receive(receivePacket); 
  9.                } 
  10.                lastReceiveTime = System.currentTimeMillis(); 
  11.                Log.d(TAG, "receive packet success..."); 
  12.            } catch (IOException e) { 
  13.                Log.e(TAG, "UDP數(shù)據(jù)包接收失敗!線程停止"); 
  14.                stopUDPSocket(); 
  15.                e.printStackTrace(); 
  16.                return
  17.            } 
  18.            if (receivePacket == null || receivePacket.getLength() == 0) { 
  19.                Log.e(TAG, "無法接收UDP數(shù)據(jù)或者接收到的UDP數(shù)據(jù)為空"); 
  20.                continue
  21.            } 
  22.            String strReceive = new String(receivePacket.getData(), 0, receivePacket.getLength()); 
  23.            Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort()); 
  24.            //解析接收到的 json 信息 
  25.            // 每次接收完UDP數(shù)據(jù)后,重置長度。否則可能會導致下次收到數(shù)據(jù)包被截斷。 
  26.            if (receivePacket != null) { 
  27.                receivePacket.setLength(BUFFER_LENGTH); 
  28.            } 
  29.        } 
  30.    } 

處理接收消息時,有幾個值得注意的點:

  • receive 方法是阻塞的,沒收到數(shù)據(jù)包時會一直阻塞,所以要放到子線程中;
  • 每次接收到消息之后,重新調用 receivePacket.setLength;
  • 收到消息刷新lastReceiveTime的值,暫停心跳包的發(fā)送;
  • 處理收到的數(shù)據(jù)具體在業(yè)務上就是剛才我們談的發(fā)送數(shù)據(jù)的問題,視業(yè)務而定。

“用戶”的概念

上文已經(jīng)談過了 UDP 的特性,假如一個手機已經(jīng)開啟了熱點,若多個手機與他相連接,則多個手機發(fā)送的消息它都可以收到。如果發(fā)送方的端口與接收方的端口相同的話,甚至自己發(fā)的消息,自己都可以收到。這就很尷尬了,也就是說我們既要剔除自己發(fā)給自己的消息,也得區(qū)分不同手機發(fā)來的消息,這個時候就理應有一個“用戶”的概念。

創(chuàng)建 User 對象,有哪些屬性可以看自己的業(yè)務,本文的例子就有 ip、imei、以及 softversion。

 

  1. /** 
  2.     * 創(chuàng)建本地用戶信息 
  3.     */ 
  4.    private void createUser() { 
  5.        if (localUser == null) { 
  6.            localUser = new Users(); 
  7.        } 
  8.        if (remoteUser == null) { 
  9.            remoteUser = new Users(); 
  10.        } 
  11.        localUser.setImei(DeviceUtil.getDeviceId(mContext)); 
  12.        localUser.setSoftVersion(DeviceUtil.getPackageVersionCode(mContext)); 
  13.        if (WifiUtil.getInstance(mContext).isWifiApEnabled()) {// 判斷當前是否是開啟熱點方 
  14.            localUser.setIp("192.168.43.1"); 
  15.        } else {// 當前是開啟 wifi 方 
  16.            localUser.setIp(WifiUtil.getInstance(mContext).getLocalIPAddress()); 
  17.            remoteUser.setIp(WifiUtil.getInstance(mContext).getServerIPAddress()); 
  18.        } 
  19.    } 
  20.    /** 
  21.     * <p><b>IMEI.</b></p> Returns the unique device ID, for example, the IMEI for GSM and the MEID 
  22.     * or ESN for CDMA phones. Return null if device ID is not available. 
  23.     * <p> 
  24.     * Requires Permission: READ_PHONE_STATE 
  25.     * 
  26.     * @param context 
  27.     * @return 
  28.     */ 
  29.    public synchronized static String getDeviceId(Context context) { 
  30.        if (context == null) { 
  31.            return ""
  32.        } 
  33.        String imei = ""
  34.        try { 
  35.            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 
  36.            if (tm == null || TextUtils.isEmpty(tm.getDeviceId())) { 
  37.                // 雙卡雙待需要通過phone1和phone2獲取imei,默認取phone1的imei。 
  38.                tm = (TelephonyManager) context.getSystemService("phone1"); 
  39.            } 
  40.            if (tm != null) { 
  41.                imei = tm.getDeviceId(); 
  42.            } 
  43.        } catch (SecurityException e) { 
  44.            e.printStackTrace(); 
  45.        } 
  46.        return imei; 
  47.    }</p> 

這里就不將所有的代碼展開來看了。如果有了手機的 imei 號,那很容易就可以來做身份的區(qū)分,你既可以區(qū)分不同的發(fā)送方,也可以剔除掉自己發(fā)給自己的消息。當然如果需要更多的信息,可以按照自己的業(yè)務區(qū)分,將這些信息作為發(fā)送的 messge,通過 Socket 發(fā)送。

寫在后面:

到現(xiàn)在開始本文的大部分內(nèi)容都已經(jīng)介紹完成,有的同學可能會發(fā)問,你要用一個心跳來維持一個假的“長連接”,使用起來比較麻煩,而且還可能忍受 UDP 造成的丟包的痛苦,為什么不選擇 TCP 呢?問得好,其實這個版本是當時做的***個版本,之后就使用 TCP+UDP 的方式來完成這個模塊了,下一篇文章再來看看加上 TCP 的改進版吧。

責任編輯:未麗燕 來源: MeloDev
相關推薦

2017-03-21 15:20:11

數(shù)據(jù)團隊模式思路

2015-09-16 09:09:46

設計WindowsLinux

2017-04-24 13:51:16

設計師分析

2015-12-01 10:54:49

安全產(chǎn)品采購供應商選擇信息安全官

2020-04-24 10:02:44

組件Vue組件庫

2011-12-15 18:38:57

2023-07-10 18:30:48

2020-03-19 15:21:57

智慧城市藝術社會

2023-03-02 08:37:15

2016-03-08 09:41:50

程序員大神成長

2016-03-07 10:18:26

程序員使命感

2021-01-15 23:28:50

區(qū)塊鏈開發(fā)數(shù)字化

2014-07-28 10:22:05

5G5G網(wǎng)絡無線網(wǎng)絡

2012-12-27 14:54:48

簡歷求職者

2021-07-07 10:01:13

編程語言計算機斯坦福大學

2022-12-01 16:56:03

智慧城市安全環(huán)境能源

2018-08-23 17:38:01

多云混合云云平臺

2017-09-04 16:43:08

Linux云原生環(huán)境開源

2012-05-17 14:04:22

統(tǒng)一通信UC

2023-10-13 08:51:11

IT員工iPod
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91高清在线观看 | 久久精品91久久久久久再现 | 一区二区视频在线 | 日韩视频成人 | 久草在线在线精品观看 | 欧美一级二级视频 | aa级毛片毛片免费观看久 | 久久美女网 | 国产精品久久久久无码av | 天天干天天玩天天操 | 亚洲精品一区二区三区在线观看 | 久久久99国产精品免费 | 自拍偷拍第1页 | 亚洲自拍偷拍欧美 | 一区二区三区小视频 | 欧美jizzhd精品欧美巨大免费 | 国产乱码精品一区二区三区中文 | 国产黄色在线观看 | 免费视频一区二区 | 欧美中文字幕在线观看 | 日韩精品一区中文字幕 | 亚洲黄色片免费观看 | 国产成年人视频 | 日韩欧美高清 | 国产一级片一区二区 | 国产丝袜一区二区三区免费视频 | 日本电影免费完整观看 | 亚洲永久入口 | 国产精品视频一区二区三区, | 婷婷久久精品一区二区 | 99国产精品久久久久 | 国产精品久久亚洲7777 | 免费一区 | 日韩成人免费av | 黄色一级片视频 | 欧美一区二区网站 | 天天操夜夜操免费视频 | 日韩久久久久久 | 在线观看成年视频 | 日韩精品福利 | 精品一区精品二区 |