通過UDP建立TCP連接


解釋

通過UDP廣播查詢服務器的IP地址,然后再建立TCP點對點連接。

應用場景

在服務器IP未知時,並且已知服務器與客戶端明確在一個局域網或者允許組播的子網下。
通過UDP發現服務器地址然后再進行TCP連接。
(PS:萬維網很多路由器對組播進行了限制,所以不能通過UDP報文在萬維網上進行服務器查詢)

主要問題

Android真機和模擬器對組播處理不同
Android不同系統版本對組播處理不同
不同網絡對組播有限制,如部分路由網絡限制UDP報文

簡單實現

傳統組播方式,通過255.255.255.255地址全轉發。
客戶端收到報文后進行相應check,然后通過UDP報文數據獲取服務器的IP地址。
然后通過IP地址連接。
PS:
會有點問題、比如在Android 5.0.1真機(鄙人的手機)上無法接收255.255.255.255組播報文。
也有可能是被路由器過濾掉了,但是在模擬器上一切正常。
后續給出真機上的解決方案
服務器廣播Hello報文代碼:
 1 private class BoadrcastDispense implements Runnable {
 2 
 3         @Override
 4         public void run() {
 5             try {
 6                 if (UDPSocket == null) {
 7                     UDPSocket = new DatagramSocket(udpPortServer);
 8                 }
 9             } catch (SocketException e1) {
10             }
11             while (!UDPSocket.isClosed() && flag) {
12                 try {
13                     Thread.sleep(1000);
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17                 try {
18                     // send message
19                     String str = "Hello Client";
20                     byte backData[] = str.getBytes();
21                     DatagramPacket backPacket = new DatagramPacket(backData,
22                             backData.length,
23                             InetAddress.getByName("255.255.255.255"),
24                             udpPortClient);
25                     UDPSocket.send(backPacket);
26                 } catch (SocketException e) {
27                     e.printStackTrace();
28                 } catch (IOException e) {
29                     e.printStackTrace();
30                 }
31             }
32         }
33 
34     }

 

 
客戶端接收Hello報文代碼
 1 public static boolean searchServer() {
 2         try {
 3             // receive packet
 4             if (udpSocket == null) {
 5                 udpSocket = new DatagramSocket(udpPortClient);
 6                 udpSocket.setSoTimeout(5000);
 7             }
 8             byte receiveData[] = new byte[bufferSize];
 9             DatagramPacket receivePacket = new DatagramPacket(receiveData,
10                     receiveData.length);
11             udpSocket.receive(receivePacket);
12             String recieveStr = new String(receivePacket.getData(),
13                     receivePacket.getOffset(), receivePacket.getLength());
14             System.out.println("服務器IP地址:"
15                     + receivePacket.getAddress().getHostAddress());
16             System.out.println("接收到服務器反饋:" + recieveStr);
17             if (recieveStr.equalsIgnoreCase("Hello Client")) {
18                 serverIP = receivePacket.getAddress().getHostAddress();
19                 System.out.println("連接到服務器成功");
20                 return true;
21             }
22         } catch (SocketException e) {
23             e.printStackTrace();
24         } catch (UnknownHostException e) {
25             e.printStackTrace();
26         } catch (IOException e) {
27             e.printStackTrace();
28         }
29         System.out.println("連接到服務器失敗");
30         return false;
31 
32     }
在模擬器上進行調試時,上面代碼就已經足夠了。
但是在真機上運行時,發現一直阻塞在receive那里。
各種 百度 + Google ,依然無果,並且發現很多人都遇到過這個問題。
有三個可能的原因:
  1. 省電???

    Android部分機型為了省電,關閉了wlan的組播功能,但是可以通過代碼開啟。

1 WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
2 WifiManager.MulticastLock lock = manager.createMulticastLock("test wifi");
3 lock.acquire();
4 // 要執行的組播操作,如receive等
5 lock.release();

 

但經過嘗試,並非這個原因導致。

2.路由or網絡設備屏蔽掉了255報文

WIFI網絡屏蔽掉了255.255.255.255的組播報文
但是通過反向發送255報文(Android向PC發,發現PC可以收到)可以成功。
所以排除了這個原因。
PS:
這個原因在其他地方還是很有可能的,很多路由器為了防止廣播風暴,都默認屏蔽了255組播,或者限制了組播數量。
比如華為的設備默認限制為30%,丟包率可想而知

3.子網域無法向外發送廣播

這個就比較理論化了。
說的是家里的路由器的IP地址是192.168.0.1的地址,而192.168.x.x屬於內網域,無法向外廣播啥的....
計算機網絡沒學好,暫時不考慮這個原因

改進實現

由於各種搜索無果,決定換一種思路嘗試,受到在嘗試過程中可以通過Android向PC發送255廣播報文的其他,想到了以下兩種解決方案

 1. 利用Android建立服務器,讓PC反向連接到Android
 優點是可以簡單快速的建立P2P連接,但是僅限於P2P連接,如果想讓服務器接入多個客戶端,該方法不適用
 2. 先通過PC獲取Android的IP地址,然后發定點UDP報文,再從定點UDP報文獲取服務器IP
 優點是還是基本的PC做server,android做client的結構,只是要多一步IP中轉
 至於我的方法,由於服務器的代碼基本寫完了,不想做大的更改,所以采用的第二種方法,代碼還是比較簡單:
通過Android端向PC端發送255報文
 1     public static void sendPacktToServer() {
 2         try {
 3             // receive packet
 4             if (udpSocket == null) {
 5                 udpSocket = new DatagramSocket(udpPortClient);
 6                 udpSocket.setSoTimeout(5000);
 7             }
 8             String str = "Hello Server";
 9             byte[] data = str.getBytes();
10             DatagramPacket sendPacket = new DatagramPacket(data,
11                     data.length,
12                     InetAddress.getByName("255.255.255.255"), udpPortServer);
13             lock.acquire();
14             udpSocket.send(sendPacket);
15             lock.release();
16         } catch (SocketException e) {
17             e.printStackTrace();
18         } catch (UnknownHostException e) {
19             e.printStackTrace();
20         } catch (IOException e) {
21             e.printStackTrace();
22         }
23     }

 

 
PC端接收到Android發送的255報文后直接回饋定向UDP報文,這樣Android端就可以收到該報文了,然后再建立TCP連接
 1    private class BoardcastListener implements Runnable {
 2 
 3         @Override
 4         public void run() {
 5             try {
 6                 if (UDPSocket == null) {
 7                     UDPSocket = new DatagramSocket(udpPortServer);
 8                 }
 9             } catch (SocketException e1) {
10             }
11             while (!UDPSocket.isClosed() && flag) {
12                 try {
13                     // receive message
14                     byte[] recvBuf = new byte[bufferSize];
15                     DatagramPacket recvPacket = new DatagramPacket(recvBuf,
16                             recvBuf.length);
17                     UDPSocket.receive(recvPacket);
18                     String recvStr = new String(recvPacket.getData(), 0,
19                             recvPacket.getLength());
20                     ServerFrame.getInstance().appendServerStr(
21                             "接收到UDP消息: " + recvStr + " 來自:"
22                                     + recvPacket.getAddress().getHostAddress());
23                     // send back message
24                     String str = "Hello Client";
25                     byte backData[] = str.getBytes();
26                     DatagramPacket backPacket = new DatagramPacket(backData,
27                             backData.length, recvPacket.getAddress(),
28                             udpPortClient);
29                     UDPSocket.send(backPacket);
30                 } catch (SocketException e) {
31                     e.printStackTrace();
32                 } catch (IOException e) {
33                     e.printStackTrace();
34                 }
35             }
36         }
37 
38     }

 

至此,基本的核心代碼就全貼出來了,目前經過測試在模擬器上和android真機上均可以連接到服務器

總結

其實主要用途還是局域網內在不知道對方IP情況下怎么建立連接的一種手段,解決方案其實有很多。
比如全通過UDP交互等,但是考慮到TCP不需要手動維護穩定性,所以還是偏好使用TCP連接進行數據傳輸。
(能夠使用場景:局域網游戲、物聯網相互連接啥的)
誠然,也可以手動查詢IP地址,然后輸入IP地址進行TCP連接。
但是在移動設備上大家還是比較喜歡一鍵式的東西,通過UDP進行服務器自動發現,這個功能還是有那么一點點用處。。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM