• 通过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进行服务器自动发现,这个功能还是有那么一点点用处。。。。
  • 相关阅读:
    Mac如何自定义本地化文件夹名
    Mac如何升级自带的vim
    0. GC 前置知识
    Git如何修改一个过去的Commit
    1. GC标记-清除算法(Mark Sweep GC)
    防火墙、WAF、IPS、IDS都是什么
    Python dir和vars的区别
    【Kafka】Kafka数据可靠性深度解读
    Java网络编程基础之TCP粘包拆包
    【Kafka】Consumer配置
  • 原文地址:https://www.cnblogs.com/coldcode/p/4504811.html
Copyright © 2020-2023  润新知