通过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报文代码:
private class BoadrcastDispense implements Runnable {
@Override
public void run() {
try {
if (UDPSocket == null) {
UDPSocket = new DatagramSocket(udpPortServer);
}
} catch (SocketException e1) {
}
while (!UDPSocket.isClosed() && flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// send message
String str = "Hello Client";
byte backData[] = str.getBytes();
DatagramPacket backPacket = new DatagramPacket(backData,
backData.length,
InetAddress.getByName("255.255.255.255"),
udpPortClient);
UDPSocket.send(backPacket);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端接收Hello报文代码
public static boolean searchServer() {
try {
// receive packet
if (udpSocket == null) {
udpSocket = new DatagramSocket(udpPortClient);
udpSocket.setSoTimeout(5000);
}
byte receiveData[] = new byte[bufferSize];
DatagramPacket receivePacket = new DatagramPacket(receiveData,
receiveData.length);
udpSocket.receive(receivePacket);
String recieveStr = new String(receivePacket.getData(),
receivePacket.getOffset(), receivePacket.getLength());
System.out.println("服务器IP地址:"
+ receivePacket.getAddress().getHostAddress());
System.out.println("接收到服务器反馈:" + recieveStr);
if (recieveStr.equalsIgnoreCase("Hello Client")) {
serverIP = receivePacket.getAddress().getHostAddress();
System.out.println("连接到服务器成功");
return true;
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("连接到服务器失败");
return false;
}
在模拟器上进行调试时,上面代码就已经足够了。
但是在真机上运行时,发现一直阻塞在receive那里。
各种 百度 + Google ,依然无果,并且发现很多人都遇到过这个问题。
有三个可能的原因:
省电???
Android部分机型为了省电,关闭了wlan的组播功能,但是可以通过代码开启。
WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock lock = manager.createMulticastLock("test wifi");
lock.acquire();
// 要执行的组播操作,如receive等
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报文
public static void sendPacktToServer() {
try {
// receive packet
if (udpSocket == null) {
udpSocket = new DatagramSocket(udpPortClient);
udpSocket.setSoTimeout(5000);
}
String str = "Hello Server";
byte[] data = str.getBytes();
DatagramPacket sendPacket = new DatagramPacket(data,
data.length,
InetAddress.getByName("255.255.255.255"), udpPortServer);
lock.acquire();
udpSocket.send(sendPacket);
lock.release();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
PC端接收到Android发送的255报文后直接回馈定向UDP报文,这样Android端就可以收到该报文了,然后再建立TCP连接
private class BoardcastListener implements Runnable {
@Override
public void run() {
try {
if (UDPSocket == null) {
UDPSocket = new DatagramSocket(udpPortServer);
}
} catch (SocketException e1) {
}
while (!UDPSocket.isClosed() && flag) {
try {
// receive message
byte[] recvBuf = new byte[bufferSize];
DatagramPacket recvPacket = new DatagramPacket(recvBuf,
recvBuf.length);
UDPSocket.receive(recvPacket);
String recvStr = new String(recvPacket.getData(), 0,
recvPacket.getLength());
ServerFrame.getInstance().appendServerStr(
"接收到UDP消息: " + recvStr + " 来自:"
+ recvPacket.getAddress().getHostAddress());
// send back message
String str = "Hello Client";
byte backData[] = str.getBytes();
DatagramPacket backPacket = new DatagramPacket(backData,
backData.length, recvPacket.getAddress(),
udpPortClient);
UDPSocket.send(backPacket);
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
至此,基本的核心代码就全贴出来了,目前经过测试在模拟器上和android真机上均可以连接到服务器
总结
其实主要用途还是局域网内在不知道对方IP情况下怎么建立连接的一种手段,解决方案其实有很多。
比如全通过UDP交互等,但是考虑到TCP不需要手动维护稳定性,所以还是偏好使用TCP连接进行数据传输。
(能够使用场景:局域网游戏、物联网相互连接啥的)
诚然,也可以手动查询IP地址,然后输入IP地址进行TCP连接。
但是在移动设备上大家还是比较喜欢一键式的东西,通过UDP进行服务器自动发现,这个功能还是有那么一点点用处。。。。
通过UDP建立TCP连接的更多相关文章
- Linux 建立 TCP 连接的超时时间分析(解惑)
Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...
- 一个人也可以建立 TCP 连接呢
今天(恰巧是今天)看到有人在 SegmentFault 上问「TCP server 为什么一个端口可以建立多个连接?」.提问者认为 client 端就不能使用相同的本地端口了.理论上来说,确定一条链路 ...
- 为什么建立TCP连接需要三次握手,为什么断开TCP连接需要四次握手,TIME_WAIT状态的意义
为什么建立TCP连接需要三次握手? 原因:为了应对网络中存在的延迟的重复数组的问题 例子: 假设client发起连接的连接请求报文段在网络中没有丢失,而是在某个网络节点长时间滞留了,导致延迟到达ser ...
- 最简单的理解 建立TCP连接 三次握手协议
最简单的理解一:建立TCP连接:三次握手协议 客户端:我要对你讲话,你能听到吗:服务端:我能听到:而且我也要对你讲话,你能听到吗:客户端:我也能听到.…….互相开始通话…….. 二:关闭TCP ...
- 详解TCP三次握手(建立TCP连接过程)
在讲述TCP三次握手,即建立TCP连接的过程之前,需要先介绍一下TCP协议的包结构. 这里只对涉及到三次握手过程的字段做解释 (1) 序号(Sequence number) 我们通过 TCP 协议将数 ...
- 图说使用socket建立TCP连接
在网络应用如火如荼的今天,熟悉TCP/IP网络编程,那是最好不过.如果你并不非常熟悉,不妨花几分钟读一读. 为了帮助快速理解,先上个图,典型的使用socket建立和使用TCP/UDP连接过程为(截图来 ...
- 建立TCP连接的三次握手
请求端(通常称为客户)发送一个 SYN 报文段( SYN 为 1 )指明客户打算连接的服务器的端口,以及初始顺序号( ISN ).服务器发回包含服务器的初始顺序号( ISN )的 SYN 报文段( S ...
- 建立TCP连接过程
1.服务器实例化一个ServerSocket 对象, 表示通过服务器上的端口通信. ServerSocket serverSocket = new ServerSocket(port); 2.服务器调 ...
- Nginx 针对建立TCP连接优化
L:124 sysctl -a | grep file-max //通过命令查看系统最大句柄数 [root@3 ~]# sysctl -a | grep file-max fs.file-max = ...
随机推荐
- 洛谷 2476 [SCOI2008]着色方案
50%的数据满足:1 <= k <= 5, 1 <= ci <= 3 100%的数据满足:1 <= k <= 15, 1 <= ci <= 5 [题解] ...
- dev的动态汉化
放控件TcxLocalizer.将其FIlename设定成汉化文件.ini.选择Locale的值是中文,然后active=true.OK了文件如下 ini如下: [2052] CHINA_STR=&q ...
- 使用HTML5 Canvas API
一.检测浏览器支持情况 HTML5 Canvas的确是一个好东西,但是并不是所有浏览器都支持HTML5 Canvas的,这就要求我们在使用HTML5 Canvas前要检查浏览器是否支持这玩意儿. 在创 ...
- 根据判断数组不为空然后取他的值----数组不会为空---只能判断其size是否大于0
private List<Integer> classId=new ArrayList<Integer>(); business.getClassId()!=null 以上为错 ...
- pace.js – 网页自动加载进度条插件
网站顶部的页面加载进度条是怎么实现的,页面的加载进度百分比,有时候获取是比较麻烦的,当然也可以利用一些优秀的JavaScript插件来实现,今天就为大家介绍这样子的一款插件:pace.js. [官方网 ...
- 【UOJ34】高精度乘法(FFT)
题意: 思路:FFT模板,自带10倍常数 type cp=record x,y:double; end; arr=..]of cp; var a,b,cur:arr; n,m,n1,n2,i,j:lo ...
- Linux下汇编语言学习笔记77 ---
这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...
- 20180725关于quartz的初识
请参照: https://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ https://www.w3cschool.cn/quartz_ ...
- Holedox Eating HDU4302 模拟
Problem Description Holedox is a small animal which can be considered as one point. It lives in a st ...
- Windows下擴展ubuntu虛擬機的分區大小
在虛擬分區上安裝ubuntu,8G的分区不够用,不願意重装,增加VM分区吧!先备份虛擬硬盤文件 VMWARE自带的工具:找到vmware安装目录下vmware-vdiskmanager.exe,双击無 ...