解释

  1. 通过UDP广播查询服务器的IP地址,然后再建立TCP点对点连接。

应用场景

  1. 在服务器IP未知时,并且已知服务器与客户端明确在一个局域网或者允许组播的子网下。
  2. 通过UDP发现服务器地址然后再进行TCP连接。
  3. PS:万维网很多路由器对组播进行了限制,所以不能通过UDP报文在万维网上进行服务器查询)

主要问题

  1. Android真机和模拟器对组播处理不同
  2. Android不同系统版本对组播处理不同
  3. 不同网络对组播有限制,如部分路由网络限制UDP报文

简单实现

  1. 传统组播方式,通过255.255.255.255地址全转发。
  2. 客户端收到报文后进行相应check,然后通过UDP报文数据获取服务器的IP地址。
  3. 然后通过IP地址连接。
  4. PS
  5. 会有点问题、比如在Android 5.0.1真机(鄙人的手机)上无法接收255.255.255.255组播报文。
  6. 也有可能是被路由器过滤掉了,但是在模拟器上一切正常。
  7. 后续给出真机上的解决方案
  1. 服务器广播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. }
  1.  
  1. 客户端接收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. }
  1. 在模拟器上进行调试时,上面代码就已经足够了。
  2. 但是在真机上运行时,发现一直阻塞在receive那里。
  3. 各种 百度 + Google ,依然无果,并且发现很多人都遇到过这个问题。
  4. 有三个可能的原因:
  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();
  1. 但经过尝试,并非这个原因导致。

2.路由or网络设备屏蔽掉了255报文

  1. WIFI网络屏蔽掉了255.255.255.255的组播报文
  2. 但是通过反向发送255报文(AndroidPC发,发现PC可以收到)可以成功。
  3. 所以排除了这个原因。
  4. PS
  5. 这个原因在其他地方还是很有可能的,很多路由器为了防止广播风暴,都默认屏蔽了255组播,或者限制了组播数量。
  6. 比如华为的设备默认限制为30%,丢包率可想而知

3.子网域无法向外发送广播

  1. 这个就比较理论化了。
  2. 说的是家里的路由器的IP地址是192.168.0.1的地址,而192.168.x.x属于内网域,无法向外广播啥的....
  3. 计算机网络没学好,暂时不考虑这个原因

改进实现

  1. 由于各种搜索无果,决定换一种思路尝试,受到在尝试过程中可以通过AndroidPC发送255广播报文的其他,想到了以下两种解决方案
  2. 1. 利用Android建立服务器,让PC反向连接到Android
  3. 优点是可以简单快速的建立P2P连接,但是仅限于P2P连接,如果想让服务器接入多个客户端,该方法不适用
  4. 2. 先通过PC获取AndroidIP地址,然后发定点UDP报文,再从定点UDP报文获取服务器IP
  5. 优点是还是基本的PCserverandroidclient的结构,只是要多一步IP中转
  6. 至于我的方法,由于服务器的代码基本写完了,不想做大的更改,所以采用的第二种方法,代码还是比较简单:
  1. 通过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. }
  1.  
  1. 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. }
  1. 至此,基本的核心代码就全贴出来了,目前经过测试在模拟器上和android真机上均可以连接到服务器

总结

  1. 其实主要用途还是局域网内在不知道对方IP情况下怎么建立连接的一种手段,解决方案其实有很多。
  2. 比如全通过UDP交互等,但是考虑到TCP不需要手动维护稳定性,所以还是偏好使用TCP连接进行数据传输。
  3. (能够使用场景:局域网游戏、物联网相互连接啥的)
  4. 诚然,也可以手动查询IP地址,然后输入IP地址进行TCP连接。
  5. 但是在移动设备上大家还是比较喜欢一键式的东西,通过UDP进行服务器自动发现,这个功能还是有那么一点点用处。。。。

通过UDP建立TCP连接的更多相关文章

  1. Linux 建立 TCP 连接的超时时间分析(解惑)

    Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...

  2. 一个人也可以建立 TCP 连接呢

    今天(恰巧是今天)看到有人在 SegmentFault 上问「TCP server 为什么一个端口可以建立多个连接?」.提问者认为 client 端就不能使用相同的本地端口了.理论上来说,确定一条链路 ...

  3. 为什么建立TCP连接需要三次握手,为什么断开TCP连接需要四次握手,TIME_WAIT状态的意义

    为什么建立TCP连接需要三次握手? 原因:为了应对网络中存在的延迟的重复数组的问题 例子: 假设client发起连接的连接请求报文段在网络中没有丢失,而是在某个网络节点长时间滞留了,导致延迟到达ser ...

  4. 最简单的理解 建立TCP连接 三次握手协议

     最简单的理解一:建立TCP连接:三次握手协议    客户端:我要对你讲话,你能听到吗:服务端:我能听到:而且我也要对你讲话,你能听到吗:客户端:我也能听到.…….互相开始通话…….. 二:关闭TCP ...

  5. 详解TCP三次握手(建立TCP连接过程)

    在讲述TCP三次握手,即建立TCP连接的过程之前,需要先介绍一下TCP协议的包结构. 这里只对涉及到三次握手过程的字段做解释 (1) 序号(Sequence number) 我们通过 TCP 协议将数 ...

  6. 图说使用socket建立TCP连接

    在网络应用如火如荼的今天,熟悉TCP/IP网络编程,那是最好不过.如果你并不非常熟悉,不妨花几分钟读一读. 为了帮助快速理解,先上个图,典型的使用socket建立和使用TCP/UDP连接过程为(截图来 ...

  7. 建立TCP连接的三次握手

    请求端(通常称为客户)发送一个 SYN 报文段( SYN 为 1 )指明客户打算连接的服务器的端口,以及初始顺序号( ISN ).服务器发回包含服务器的初始顺序号( ISN )的 SYN 报文段( S ...

  8. 建立TCP连接过程

    1.服务器实例化一个ServerSocket 对象, 表示通过服务器上的端口通信. ServerSocket serverSocket = new ServerSocket(port); 2.服务器调 ...

  9. Nginx 针对建立TCP连接优化

    L:124 sysctl -a | grep file-max //通过命令查看系统最大句柄数 [root@3 ~]# sysctl -a | grep file-max fs.file-max = ...

随机推荐

  1. hdu 1166敌兵布阵(线段树入门题)

    >>点击进入原题测试<< 思路:这两天在学线段树,这个题直接手敲一下线段树就行了,都没有用上懒人标记.入门题 cin,cout会超时,记得加std::ios::sync_wit ...

  2. hadoop full cluster 改为伪分布

    https://hadoop.apache.org/docs/r2.7.6/hadoop-project-dist/hadoop-common/SingleCluster.html#Pseudo-Di ...

  3. https://blog.csdn.net/u011495642/article/details/79958444

    https://blog.csdn.net/u011495642/article/details/79958444--- 一文读懂大数据平台——写给大数据开发初学者的话! 史上最详细的Hadoop环境 ...

  4. [K/3Cloud]屏蔽页签的关闭按钮

    如图,屏蔽企业门户的X关闭按钮. this.View.GetControl<TabControl>("FMainTab").SetItemCloseable(1, fa ...

  5. 超级钢琴(codevs 2934)

    题目描述 Description 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音 ...

  6. vim中256色的配色表

    vim貌似支持的颜色很有限.white  black  yellow  cyan   magenta   blue   grey   green   red,大多数颜色前面都可以加light 或者 d ...

  7. oracle中的类似BIN$MrkCYT9eTTK+0sStMwn7+Q==$0的表的作用

    https://www.2cto.com/database/201211/166482.html https://docs.oracle.com/cd/E11882_01/server.112/e40 ...

  8. Regular Number 字符串匹配算法 Shift_and

    Using regular expression to define a numeric string is a very common thing. Generally, use the shape ...

  9. J - Invitation Cards 最短路

    In the age of television, not many people attend theater performances. Antique Comedians of Malidine ...

  10. Snail—UI学习之自己定义通知NSNotification

    背景是:一个界面跳转到第二个界面 然后 第一个界面发了一个通知  然后第二个界面收到这个通知后 把里面的数据取出来 在RootViewController.m中写入以下代码 #import " ...