通过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 ,依然无果,并且发现很多人都遇到过这个问题。
有三个可能的原因:
-
省电???
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进行服务器自动发现,这个功能还是有那么一点点用处。。。。