通过UDP广播实现Android局域网Peer Discovering
本文是对个人笔记中内容的整理,部分代码及图片来自互联网,由于不好找到原始出处,所以未加注明。
如有痛感,联系删除。
本文将介绍以下知识点:
- TCP与UDP的区别;
- 单播、多播、广播;
- Java中实现UDP的重要的类;
- Peer Discovering方案
一、TCP vs UDP
TCP:Transmission Control Protocol(传输控制协议)
TCP是一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议,由IETF的RFC 793说明(specified)。TCP建立连接之后,通信双方都同时可以进行数据的传输,是全双工的。
- 在保证可靠性上,采用超时重传和捎带确认机制;
- 在流量控制上,采用滑动窗口协议,协议中规定,对于窗口内未经确认的分组需要重传;
- 在拥塞控制上,采用慢启动算法。
TCP传输过程示意图:
Client和Server建立连接之后,服务器处于监听状态,即:服务器端Socket并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端Socket提出连接请求,要连接的目标是服务器端Socket。为此,客户端Socket必须首先描述它要连接的服务器Socket,指出服务端Socket的地址和端口号,然后就向服务器端Socket提出连接请求。
当服务器端Socket监听到或者说接收到客户端Socket的连接请求时,就响应客户端Socket的请求,建立一个新的线程,把服务器端Socket的描述发给客户端,一旦客户端确认了此描述,双方就正式通信。
而服务端Socket继续处于监听状态,继续接收其他客户端Socket的连接请求。
TCP服务器端代码:
try { Boolean endFlag = false; ServerSocket ss = new ServerSocket(12345); while (!endFlag) { // 等待客户端连接 Socket s = ss.accept(); BufferedReader input = new BufferedReader(newInputStreamReader(s.getInputStream())); //注意第二个参数据为true将会自动flush,否则需要需要手动操作output.flush() PrintWriter output = newPrintWriter(s.getOutputStream(),true); String message = input.readLine(); Log.d("Tcp Demo", "message from Client:"+message); output.println("message received!"); //output.flush(); if("shutDown".equals(message)){ endFlag=true; } s.close(); } ss.close();
} catch (UnknownHostException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace();
}
TCP客户端代码:
try { Socket s = new Socket("localhost", 12345); // outgoing stream redirect to socket OutputStream out = s.getOutputStream(); // 注意第二个参数据为true将会自动flush,否则需要需要手动操作out.flush() PrintWriter output = new PrintWriter(out, true); output.println("Hello World!"); BufferedReader input = new BufferedReader(newInputStreamReader(s.getInputStream())); // read line(s) String message = input.readLine(); Log.d("Tcp Demo", "message From Server:" + message); s.close();
} catch (UnknownHostException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace();
}
UDP:User Datagram Protocol(用户数据包协议)
UDP是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。它是IETF RFC 768是UDP的正式规范。
- UDP协议的主要作用是将网络数据流量压缩成数据报的形式。
- 一个典型的数据报就是一个二进制数据的传输单位。
- 每一个数据报的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
相比于TCP,UDP在通信之前并不建立连接,UDP服务端Socket监听某个端口的流量,客户端Socket发送报文给服务端Socket指定端口,服务端Socket处理完信息之后也并不反馈信息给客户端Socket。
即:客户端Socket发送报文后,不关心服务端是否收到报文;服务端Socket若收到报文,也并不告知客户端Socket。
UDP服务器端代码:
// UDP服务器监听的端口
Integer port = 12345;
// 接收的字节大小,客户端发送的数据不能超过这个大小
byte[] message = new byte[1024];
try { // 建立Socket连接 DatagramSocket datagramSocket = new DatagramSocket(port); DatagramPacket datagramPacket = new DatagramPacket(message, message.length);try { while (true) { // 准备接收数据 datagramSocket.receive(datagramPacket); Log.d("UDP Demo", datagramPacket.getAddress() .getHostAddress().toString() + ":" + new String(datagramPacket.getData())); } } catch (IOException e) { e.printStackTrace(); }
} catch (SocketException e) { e.printStackTrace();
}
UDP客户端代码:
public static void send(String message) { message = (message == null ? "Hello IdeasAndroid!" : message); int server_port = 12345; DatagramSocket s = null; try { s = new DatagramSocket(); } catch (SocketException e) { e.printStackTrace(); } InetAddress local = null; try { // 换成服务器端IP local = InetAddress.getByName("localhost"); } catch (UnknownHostException e) { e.printStackTrace(); } int msg_length = message.length(); byte[] messagemessageByte = message.getBytes(); DatagramPacket p = new DatagramPacket(messageByte, msg_length, local, server_port); try { s.send(p); } catch (IOException e) { e.printStackTrace(); }
}
总结下TCP和UDP的主要区别:
TCP | UDP | |
---|---|---|
是否连接 | 面向连接 | 面向非连接 |
传输是否可靠 | 可靠 | 不可靠 |
速度 | 慢 | 快 |
应用场景 | 要求准确性数据(例如金融、库存) | 不求准确,但求实时、快(语音、图像数据) |
二、单播、多播、广播
单播(unicast): 是指封包在计算机网络的传输中,目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛,通常所使用的网络协议或服务大多采用单播传输,例如一切基于TCP的协议。
每次只有两个实体相互通信,发送端和接收端都是唯一确定的。在IPv4网络中,0.0.0.0到223.255.255.255属于单播地址。你对小月月喊“小月月”,那么只有小月月回过头来答应你。
组播(multicast): 也叫多播, 多点广播或群播。 指把信息同时传递给一组目的地址。它使用策略是最高效的,因为消息在每条网络链路上只需传递一次,而且只有在链路分叉的时候,消息才会被复制。
“组播”这个词通常用来指代IP组播。IP组播是一种通过使用一个组播地址将数据在同一时间以高效的方式发往处于TCP/IP网络上的多个接收者的协议。此外,它还常用来与RTP等音视频协议相结合。互联网架构师戴夫·克拉克是这样描述IP组播的:“你把数据包从一头放进去,网络就会试图将它们传递到想要得到它们的人那里。”组播报文的目的地址使用D类IP地址, D类地址不能出现在IP报文的源IP地址字段。在IPv4网络中,224.0.0.0到239.255.255.255属于多播地址。你在大街上大喊一声“美女”, 会有一群女性回头看你。
广播(broadcast):是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式。实际上,这里所说的“所有设备”也是限定在一个范围之中,称为“广播域”。
并非所有的计算机网络都支持广播,例如X.25网络和帧中继都不支持广播,而且也没有在“整个互联网范围中”的广播。IPv6亦不支持广播,广播相应的功能由任播(anycast)代替。通常,广播都是限制在局域网中的,比如以太网或令牌环网络。因为广播在局域网中造成的影响远比在广域网中小得多。
以太网和IPv4网都用全1的地址表示广播,分别是ff:ff:ff:ff:ff:ff和255.255.255.255。
令牌环网络使用IEEE 802.2控制域中的一个特殊值来表示广播。你在公司大喊一声“放假了”, 全部同事都会响应,大叫爽死了。
任播(anycast):是一种网络寻址和路由的策略,使得资料可以根据路由拓朴来决定送到“最近”或“最好”的目的地。
任播是与单播、广播和组播不同的方式。
在单播中,在网络位址和网络节点之间存在一一对应的关系。
在广播和组播中,在网络位址和网络节点之间存在一对多的关系:每一个目的位址对应一群接收可以复制资讯的节点。
在任播中,在网络位址和网络节点之间存在一对多的关系:每一个位址对应一群接收节点,但在任何给定时间,只有其中之一可以接收到传送端来的资讯。在互联网中,通常使用边界网关协议来实现任播。作为老板,你在公司大喊一声“开发组的过来一个人”, 总会有一个人灰溜溜去响应, 挨批还是发钱啊?
以上内容部分出自单播,组播(多播),广播以及任播。
三、Java中实现UDP的重要的类
几个关键的类:
- DatagramSocket
- DatagramPacket
- NetworkInterface
1、DatagramPacket类:数据报文
如果把DatagramSocket比作创建的港口码头,那么DatagramPacket就是发送和接收数据的集装箱。
- 接收构造函数
public DatagramPacket(byte[] buf,int length) //接收数据
比如,要接收数据长度为1024的字节,构建字节缓存区byte buf[] = new byte[1024]
,创建DatagramPacket只需传入buf[]
和长度,实现接收长度为length的包。
while (true) {byte buf[] = new byte[1024];// 接收数据DatagramPacket packet = new DatagramPacket(buf, buf.length);datagramSocket.receive(packet);String content = new String(packet.getData()).trim();// ……
}
- 发送构造函数
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
比如,要发送数据为byte[] data
,构造函数需要字节数组,数组长度,接收端地址(IP)和端口(Port),构造数据报文包用来把长度为length 的包传送到指定宿主的指定的端口号。
byte[] data = paramVarArgs[0].getBytes();
DatagramPacket dataPacket = new DatagramPacket(data,data.length, inetAddress, BROADCAST_PORT);
try {datagramSocket.send(dataPacket);
} catch (IOException e) {e.printStackTrace();return App.getInstance().getResources().getString(R.string.send_failed);
}
return App.getInstance().getResources().getString(R.string.send_success);
- 主要方法
getAddress()
返回接收或发送此数据报文的机器的 IP 地址。getData()
返回接收的数据或发送出的数据。getLength()
返回发送出的或接收到的数据的长度。getPort()
返回接收或发送该数据报文的远程主机端口号。
2、DatagramSocket类:数据报套接字
此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。
- 不绑定地址及端口构造函数:
DatagramSocket()
创建数据报套接字。
try {datagramSocket = new DatagramSocket();datagramSocket.setBroadcast(true);
} catch (Exception e) {e.printStackTrace();
}
用于发送报文的套接字,一般不指定特定端口及地址。
绑定端口构造函数:
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。- 绑定地址与端口构造函数:
DatagramSocket(int port, InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。
// 保持一个套接字打开,监听该端口上所有UDP流量(0.0.0.0表示所有未处理的流量)
datagramSocket = new DatagramSocket(BROADCAST_PORT, InetAddress.getByName("0.0.0.0"));
datagramSocket.setBroadcast(true);
关于0.0.0.0的意义,可参考:全零网络IP地址0.0.0.0表示意义详谈
- 主要方法
receive(DatagramPacket p)
从此套接字接收数据报包。void send(DatagramPacket p)
从此套接字发送数据报包。bind(SocketAddress addr)
将此 DatagramSocket 绑定到特定的地址和端口。void close()
关闭此数据报套接字。void connect(InetAddress address, int port)
将套接字连接到此套接字的远程地址。void connect(SocketAddress addr)
将此套接字连接到远程套接字地址(IP 地址 + 端口号)。void disconnect()
断开套接字的连接。getInetAddress()
返回此套接字连接的地址。InetAddress getLocalAddress()
获取套接字绑定的本地地址。
3、NetworkInterface类:网络接口
NetworkInterface是JDK1.4中添加的一个获取网络接口的类,该网络接口既可以是物理的网络接口,也可以是虚拟的网络接口,而一个网络接口通常由一个 IP 地址来表示。
既然 NetworkInterface 用来表示一个网络接口,那么如果可以获得当前机器所有的网络接口(包括物理的和虚拟的),然后筛选出表示局域网的那个网络接口,那就可以得到机器在局域网内的 IP 地址。
NetworkInterface常用到的方法有两个:
getNetworkInterfaces()
用于获取当前机器上所有的网络接口;getInetAddresses()
用于获取绑定到该网卡的所有的 IP 地址。
来看下这段代码,实现的功能是遍历所有本地网络接口,获取广播地址,并向它们发送广播报文。
// 获取本地所有网络接口
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {NetworkInterface networkInterface = interfaces.nextElement();if (networkInterface.isLoopback() || !networkInterface.isUp()) {continue;}// getInterfaceAddresses()方法返回绑定到该网络接口的所有 IP 的集合for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {InetAddress broadcast = interfaceAddress.getBroadcast();// 不广播回环网络接口if (broadcast == null) {continue;}// 发送广播报文try {DatagramPacket sendPacket = new DatagramPacket(data,data.length, broadcast, BROADCAST_PORT);datagramSocket.send(sendPacket);} catch (Exception e) {e.printStackTrace();}Log.d("发送请求", getClass().getName() + ">>> Request packet sent to: " +broadcast.getHostAddress() + "; Interface: " + networkInterface.getDisplayName());}
}
getInterfaceAddresses
方法返回的是一个绑定到该网络接口的所有 InterfaceAddress 的集合。InterfaceAddress 是 JDK1.6 之后添加的类,包含 IP 地址(InetAddress),以及该地址对应的广播地址和掩码长度。
以上内容部分出自使用 NetworkInterface 获得本机在局域网内的 IP 地址。
四、Peer Discovering方案
在局域网内通过UDP广播实现Peer Discovering的方法非常简单:
- 新加入局域网的设备发送广播消息“我来了”;
- 其它已存在的设备回复“知道了”。
整个流程如下图所示:
- 因此,在初始化阶段,首先要启动一个广播接收线程,用于接收指定端口的所有广播流量:
try {handler = new ReceiveMsgHandler(this);new ServerSocket(handler).start();
} catch (IOException e) {e.printStackTrace();
}
在ServerSocket的构造函数中实例化DatagramSocket
,指定端口,IP设置为0.0.0.0。
public ServerSocket(Handler handler) throws IOException {// Keep a socket open to listen to all the UDP trafic that is destined for this portdatagramSocket = new DatagramSocket(BROADCAST_PORT, InetAddress.getByName("0.0.0.0"));datagramSocket.setBroadcast(true);// handlerthis.handler = handler;
}
在接收线程的run()
方法中,接收所有广播消息:
while (true) {byte buf[] = new byte[1024];// 接收数据DatagramPacket packet = new DatagramPacket(buf, buf.length);datagramSocket.receive(packet);String content = new String(packet.getData()).trim();if (content.equals("DISCOVER_REQUEST") &&!packet.getAddress().toString().equals("/" + IPUtil.getLocalIPAddress())) {byte[] feedback = "DISCOVER_RESPONSE".getBytes();// 发送数据DatagramPacket sendPacket = new DatagramPacket(feedback, feedback.length,packet.getAddress(), BROADCAST_PORT);datagramSocket.send(sendPacket);// 发送Handler消息sendHandlerMessage(packet.getAddress().toString());} else if (content.equals("DISCOVER_RESPONSE") &&!packet.getAddress().toString().equals("/" + IPUtil.getLocalIPAddress())) {// 发送Handler消息sendHandlerMessage(packet.getAddress().toString());}
}
如上图所示,接收线程需要接收两种广播消息:“我来了”(DISCOVER_REQUEST
)和“知道了”(DISCOVER_RESPONSE
)。接收到DISCOVER_REQUEST
后,发送DISCOVER_RESPONSE
。需要注意的是:
// 发送数据
DatagramPacket sendPacket = new DatagramPacket(feedback, feedback.length, packet.getAddress(), BROADCAST_PORT);
这里需要指定端口为BROADCAST_PORT
,因为DISCOVER_REQUEST
报文的的端口是随机的。不然无法在BROADCAST_PORT
端口接收到DISCOVER_RESPONSE
报文,新加入局域网的设备就无法感知其他设备的存在。
- 广播发送线程在类的构造函数中初始化
DatagramSocket
:
private ClientSocket() {try {inetAddress = InetAddress.getByName(BROADCAST_IP);datagramSocket = new DatagramSocket();datagramSocket.setBroadcast(true);} catch (Exception e) {e.printStackTrace();}
}
在本demo中,发送通过AsyncTask
进行实现,在background中发送消息,发送完成后通过Handler在界面Toast提示。
new AsyncTask<String, Integer, String>() {@Overrideprotected String doInBackground(String... paramVarArgs) {byte[] data = paramVarArgs[0].getBytes();DatagramPacket dataPacket = new DatagramPacket(data,data.length, inetAddress, BROADCAST_PORT);try {datagramSocket.send(dataPacket);} catch (IOException e) {e.printStackTrace();return App.getInstance().getResources().getString(R.string.send_failed);}return App.getInstance().getResources().getString(R.string.send_success);}@Overrideprotected void onPostExecute(String result) {super.onPostExecute(result);Message msg = new Message();msg.what = SendMsgHandler.STATUS;msg.obj = result;handler.sendMessage(msg);}
}.execute(content);
代码已上传github:yhthu / intercom,如有兴趣,可移步参考代码Demo。
转载于:https://www.cnblogs.com/younghao/p/6681264.html
通过UDP广播实现Android局域网Peer Discovering相关推荐
- 【Delphi】IOS 15 UDP 广播消息(局域网)
开发环境: FMX Delphi 11.2 试用版 IOS 15.0.2(iPhone 13 Pro Max) Android 10 手机(主要用来测试) 局域网(wifi环境) 在 IOS 中使用U ...
- android udp 广播通信,Android开启热点进行UDP通信中的坑
1.写在前面: 2018年的第一篇文章,最近在使用UDP协议进行硬件通信,大家都知道UDP协议通信必须在同一个局域网内,但是每个用户家的wifi都是不一样的,硬件设备是无法只值连接到用户家的wifi的 ...
- Android 通过局域网udp广播自动建立socket连接
Android开发中经常会用到socket通讯.由于项目需要,最近研究了一下这方面的知识. 需求是想通过wifi实现android移动设备和android平台的电视之间的文件传输与控制. 毫无疑问这中 ...
- C# 实现udp广播收集局域网类所有设备信息
一个简单好理解的例子,复制过去就能用,能看到效果 首先对功能的思考,他怎么去实现 1.制定udp广播的端口(如果收发用同一个端口就会一直接收到自己给自己广播的消息) 2.启动后向局域网广播约定的字符串 ...
- 基于UDP广播的局域网聊天工具
最近项目在做一个基于UDP模式的通信程序,考虑到项目的需求有一对多的需要,所以采用socket UDP广播模式进行数据通信.网上了解了一下知道这种模式也是目前QQ采用的方式,于是为了更好的理解s ...
- unity udp广播 android,unity发送局域网广播信息
开发中有事须要一个功能是教师机经过广播发送给学生机实现通信,这时能够考试使用socket的udp广播功能来实现,代码以下:socket using System; using System.Text; ...
- android 局域网 发现,局域网内android设备发现及通讯
最近一个项目需要实现在局域网内android手机操控另一个没有显示屏的android设备(音箱),具体实现就是手机端向音箱端发送命令字符串,音箱端通过解析命令字符串来完成操作,而手机端也要实时显示音箱 ...
- Android 局域网扫描
本篇简单介绍通过UDP广播扫描局域网设备IP,并且通过ZMQ进行通信. UDP连接 主要流程: 1.1 Step1:主机发送广播信息,并指定接收端的端口(9000) 广播地址(255.255.255. ...
- 基于 P2P 技术的 Android 局域网内设备通信实践
Android 局域网内的多设备通信方式有多种,其中常见的方式有: 基于 TCP/UDP 的 Socket 通信 基于 Bluetooth 的近场通信 基于 Wifi 的 Wi-Fi Direct 连 ...
- Qt | UDP广播通信的使用、实战项目使用案例
Qt | UDP广播通信的使用.实战项目使用案例 1.UDP广播介绍 UDP广播地址固定IP地址为:XXX.XXX.XXX.255. 如果向全网段发送广播消息,那么广播地址为:255.255.255. ...
最新文章
- Flink java作为消费者连接虚拟机中的kafka/或本地的kafka,并解决java.net.UnknownHostException报错
- echarts中datazoom相关配置
- 1120. Friend Numbers (20)-PAT甲级真题
- php pdo setfetchmode,PDOStatement::setFetchMode
- SQL Server 2008中的CTE递归查询
- Linux内存映射——mmap
- 线性代数的本质-基向量部分理解
- 沪深300傻瓜定投验证
- 网络推销经典案例——所有的骗子都应该向他学习
- 树莓派集成BH1750光敏传感器配置
- Bugzilla安装完初次登录提示“couldn‘t create child process: 720002: index.cgi”解决方法
- mysql的时间模糊chax_MySQL™ 参考手册(通用安装指南)
- 【课程表小程序源码】增加今日课表功能|开源代码
- 外卖小哥莫名成10家公司监事 企业登记存监管漏洞
- 计算机网络实践网线制作,网线怎么制作
- mysql中ddl和ddm_浅析分布式数据库中间件DDM
- 【大数据处理与可视化】八、文本数据分析
- 太子家居:引领家居风尚,不容错过的橱柜定制名牌
- oracle手动挂载crs盘,手动清除CRS信息
- 开学哪些耳机值得学生党入手?耐用的蓝牙耳机推荐
热门文章
- 编译器的不同,导致运行结果不一样
- 博客排名400-300的数据变化
- 晚间看图片就高亮,这体验太差
- TensorRT:AttributeError: 'module' object has no attribute 'Logger'
- 解决办法:ubuntu登录后,桌面空空如也,状态栏没了
- “姑娘好像花儿一样”如何英译
- jpeglib的jpeg_finish_compress函数疑似越界
- NSString中如何正确判断包含一个变量字串NSString
- day21保护操作系统
- mysql界面导出数据库有乱码_导出的MYSQL数据库是乱码还可以变回中文吗