通過UDP廣播查詢服務器的ip地址,然后再建立TCP點對點連接。應用場景在服務器IP未知時,并且已知服務器與客戶端明確在一個局域網或者允許組播的子網下。通過UDP發現服務器地址然后再進行TCP連接。(PS:萬維網很多路由器對組播進行了限制,所以不能通過UDP報文在萬維網上進行服務器查詢)主要問題Android真機和模擬器對組播處理不同Android不同系統版本對組播處理不同不同網絡對組播有限制,如部分路由網絡限制UDP報文簡單實現傳統組播方式,通過255.255.255.255地址全轉發??蛻舳耸盏綀笪暮筮M行相應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 message19 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 ,依然無果,并且發現很多人都遇到過這個問題。有三個可能的原因:省電???
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 message14 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 message24 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進行服務器自動發現,這個功能還是有那么一點點用處。。。。新聞熱點
疑難解答