Android UDP 单播,组播(多播),广播

最近需要用到主机探测功能,想通过 UDP 组播来实现,因此学习了下 UDP。

知识库:

由于组播(多播)和广播会用到一些网络相关的基本知识,可以参看我之前写的文章网络基础知识)。

  • UDP(User Datagram Protocol 用户数据报协议) 基本概念
  • UDP 单播
  • UDP 组播(多播)
  • UDP 广播

基本概念

以下内容引自维基百科)。

用户数据报协议(英语:User Datagram Protocol,缩写:UDP;又称用户数据包协议)是一个简单的面向数据报通信协议,位于OSI模型传输层。该协议由David P. Reed在1980年设计且在RFC 768中被规范。典型网络上的众多使用UDP协议的关键应用在一定程度上是相似的。

TCP/IP模型中,UDP为网络层以上和应用层以下提供了一个简单的接口。UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份(所以UDP有时候也被认为是不可靠的数据报协议)。UDP在IP数据报的头部仅仅加入了复用和数据校验字段。

UDP适用于不需要或在程序中执行错误检查和纠正应用,它避免了协议栈中此类处理的开销。对时间有较高要求的应用程序通常使用UDP,因为丢弃数据包比等待或重传导致延迟更可取。

  UDP 头部(共8个字节)如下:(图片引自维基百科

UDP 头部

  UDP报头包括4个字段,每个字段占用2个字节(即16个二进制位)。在IPv4中,“来源连接端口”和“校验和”是可选字段(以粉色背景标出)。在IPv6中,只有来源连接端口是可选字段。各16bit来源端口目的端口用来标记发送和接受的应用进程。因为UDP不需要应答,所以来源端口是可选的,如果来源端口不用,那么置为零。

报文长度

  该字段指定UDP报头和数据总共占用的长度。可能的最小长度是8字节,因为UDP报头已经占用了8字节。由于这个字段的存在,UDP报文总长不可能超过65535字节(2的16次方)(包括8字节的报头,和65527字节的数据)。实际上通过IPv4协议传输时,由于IPv4的头部信息要占用20字节,因此数据长度不可能超过65507字节(65,535 − 8字节UDP报头 − 20字节IP头部)。

  由于种种原因,在实际使用过程中,建议报长度不要超过8K(即8192字节),该大小通常会保证在各种不同的情况下,报文的可达性。

校验和

  首部剩下地16bit是用来对首部和数据部分一起做校验和(Checksum)的,IPv4中这部分是可选的,但是对于IPv6而言是必要字段。如果不使用该字段的话,那么置为零。但在实际应用中一般都使用这一功能。

UDP 示例

通用设置

AndroidManifest.xml 文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ho1ho.android.udpexample">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".udpbroadcast.UdpBroadCastClientActivity"></activity>

<service android:name=".multicast.MultiCastService1" />
<service android:name=".udpbroadcast.UdpBroadCastService1" />
<service android:name=".singlecast.SingleCastService1" />

<activity android:name=".multicast.MultiCastClientActivity" />
<activity android:name=".singlecast.SingleCastClientActivity" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
常量类 UDPConstant.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.ho1ho.android.udpexample;

/**
* Author: Michael Leo
* Date: 19-9-20 上午10:15
*/
public interface UDPConstant {

// Singlecast
// String SINGLE_CAST_IP_ADDRESS = "";
int SINGLE_CAST_PORT = 8434;
int SINGLE_CAST_SO_TIMEOUT = 60 * 1000;

// =============================================================================================
// Broadcast
String BROADCAST_IP_ADDRESS = "255.255.255.255";
int BROADCAST_PORT = 8334;
int BROADCAST_SO_TIMEOUT = 60 * 1000;

// =============================================================================================

// Multicast
// Multicast Range: (224.0.0.0, 239.255.255.255]
// 多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即 224.0.0.0 至 239.255.255.255 之间的IP地址,
// 并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
//
// 1.局部多播地址:在 224.0.0.0~224.0.0.255 之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
// 2.预留多播地址:在 224.0.1.0~238.255.255.255 之间,可用于全球范围(如Internet)或网络协议。
// 3.管理权限多播地址:在 239.0.0.0~239.255.255.255 之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。
String MULTICAST_IP_ADDRESS = "239.10.8.7";
int MULTICAST_PORT = 12501;
// The ttl must be in the range 0 <= ttl <= 255 or an IllegalArgumentException will be thrown.
// Multicast packets sent with a TTL of 0 are not transmitted on the network but may be delivered locally.
int MULTICAST_TTLTIME = 60;
}

UDP 单播

单播即点对点的通信。直接上代码:

服务器端代码如下,SingleCastService1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.ho1ho.android.udpexample.singlecast;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.ho1ho.android.udpexample.UDPConstant;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* Author: Michael Leo
* Date: 19-9-20 上午10:18
*/
public class SingleCastService1 extends Service {

private static final String TAG = "SingleCastService1";

private DatagramSocket mSocket;

@Override
public void onCreate() {
super.onCreate();
try {
mSocket = new DatagramSocket(UDPConstant.SINGLE_CAST_PORT);
mSocket.setSoTimeout(UDPConstant.SINGLE_CAST_SO_TIMEOUT);
new WorkThread().start();
} catch (Exception e) {
e.printStackTrace();
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

private class WorkThread extends Thread {

@Override
public void run() {
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
while (true) {
try {
mSocket.receive(datagramPacket);
final String result = new String(buffer, 0, datagramPacket.getLength());
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(SingleCastService1.this, "SingleCast Server 1 Received: " + result, Toast.LENGTH_SHORT).show());

InetAddress ip = datagramPacket.getAddress();
int port = datagramPacket.getPort();
Log.e(TAG, "Received from SingleCast Client: " + ip.getHostAddress() + "\tPort: " + port + "\tMsg: " + result);

String responseMsg = "I'm SingleCast server1.";
byte[] buf = responseMsg.getBytes();
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length, ip, port);
mSocket.send(receivePacket);

} catch (IOException e) {
e.printStackTrace();
}

}

}
}
}
客户端代码,SingleCastClientActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.ho1ho.android.udpexample.singlecast;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.ho1ho.android.udpexample.R;
import com.ho1ho.android.udpexample.UDPConstant;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class SingleCastClientActivity extends AppCompatActivity {

private static final String TAG = "SingleCastClient";

private DatagramSocket mSocket;
private TextView mReceivedServerTv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_cast);

mReceivedServerTv = findViewById(R.id.txtReceived);

// Emulate servers
startService(new Intent(this, SingleCastService1.class));

try {
// Client side
// 获取 DatagramSocket 实例。无需要指定端口,客户端的端口由系统随机分配
mSocket = new DatagramSocket();
mSocket.setSoTimeout(UDPConstant.SINGLE_CAST_SO_TIMEOUT);
} catch (IOException e) {
e.printStackTrace();
}
}

public void sendSingleCast(View view) {
new sendMsgToServerThread().start();
new receiveServerDataThread().start();
}

private class sendMsgToServerThread extends Thread {
@Override
public void run() {
DatagramPacket datagramPacket;
byte[] data = "I'm SingleCast client".getBytes();
try {
InetAddress address = InetAddress.getLocalHost();
datagramPacket = new DatagramPacket(data, data.length, address, UDPConstant.SINGLE_CAST_PORT);
mSocket.send(datagramPacket);
// mSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

private class receiveServerDataThread extends Thread {
@Override
public void run() {
while (true) {
try {
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, bytes.length);
mSocket.receive(receivePacket);
String ip = receivePacket.getAddress().getHostAddress();
int port = receivePacket.getPort();
String msg = new String(bytes, 0, receivePacket.getLength());
Log.e(TAG, "Received from SingleBroadCast server: " + ip + "\tPort: " + port + "\tMsg: " + msg);

runOnUiThread(() -> {
mReceivedServerTv.setText(mReceivedServerTv.getText() + "\nReceived from Server: " + ip + " Port: " + port + " Msg: " + msg);
});

Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

UDP 组播(多播)

  背景知识可以参看我之前写的文章网络基础知识)。

  多播和广播类似,指定接受者的端口号,并且地址范围是(224.0.0.0 至 239.255.255.255]

  多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即 224.0.0.0 至 239.255.255.255 之间的 IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:

  1. 局部多播地址:在 224.0.0.0~224.0.0.255 之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
  2. 预留多播地址:在 224.0.1.0~238.255.255.255 之间,可用于全球范围(如Internet)或网络协议。
  3. 管理权限多播地址:在 239.0.0.0~239.255.255.255 之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。

  在 Java 中,实现多播需要用到 MulticastSocket 类。该类是 DatagramSocket 的子类。在使用时,除了多播自己的一些特性外,把它当做 DatagramSocket 类使用就可以了。

  发送的大致步骤如下:

  1. 建立多播 Socket
  2. 设置端口以及 TTL(避免延时导致等待循环)
  3. 需要发送 packet 时,建立 DatagramPacket 并设置相应的多播地址及数据,然后通过 Socket 发送数据。

  接收的大致步骤如下:

  1. 建立 MulticastSocket 进行监听
  2. 加入到多播网段组
  3. 接收服务端返回的报文
服务端代码,MultiCastService1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.ho1ho.android.udpexample.multicast;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.ho1ho.android.udpexample.UDPConstant;
import com.ho1ho.android.udpexample.exceptions.NoMulticastException;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

/**
* Author: Michael Leo
* Date: 19-9-20 上午10:18
*/
public class MultiCastService1 extends Service {

private static final String TAG = "MultiCastService1";

private MulticastSocket mSocket;
private InetAddress mAddress;

@Override
public void onCreate() {
super.onCreate();
try {
mAddress = InetAddress.getByName(UDPConstant.MULTICAST_IP_ADDRESS);

if (!mAddress.isMulticastAddress()) {
throw new NoMulticastException();
}

mSocket = new MulticastSocket(UDPConstant.MULTICAST_PORT);
mSocket.setTimeToLive(UDPConstant.MULTICAST_TTLTIME);
mSocket.joinGroup(mAddress);
mSocket.setLoopbackMode(false); // 必须是false才能开启广播功能(需要进一步调查)
new WorkThread().start();
} catch (Exception e) {
e.printStackTrace();
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

private class WorkThread extends Thread {

@Override
public void run() {
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, 1024);
while (true) {
try {
mSocket.receive(datagramPacket);
final String result = new String(buffer, 0, datagramPacket.getLength());
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(MultiCastService1.this, "MultiCast Server 1 Received: " + result, Toast.LENGTH_SHORT).show());

InetAddress ip = datagramPacket.getAddress();
int port = datagramPacket.getPort();
Log.e(TAG, "Service-1(MultiCast) Received: " + ip.getHostAddress() + "\tPort: " + port + "\tMsg: " + result);

String responseMsg = "I'm MultiCast server1.";
byte[] buf = responseMsg.getBytes();
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length, ip, port);
mSocket.send(receivePacket);

} catch (IOException e) {
e.printStackTrace();
}

}

}
}
}
客户端代码 MultiCastClientActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.ho1ho.android.udpexample.multicast;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.ho1ho.android.udpexample.R;
import com.ho1ho.android.udpexample.UDPConstant;
import com.ho1ho.android.udpexample.exceptions.NoMulticastException;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class MultiCastClientActivity extends AppCompatActivity {

private static final String TAG = "MultiCastClientActivity";

private MulticastSocket mSocket;

private TextView mReceivedServerTv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_cast_client);

mReceivedServerTv = findViewById(R.id.txtReceived);

// Emulate servers
startService(new Intent(this, MultiCastService1.class));

try {
// Client side
mSocket = new MulticastSocket(/*UDPConstant.MULTICAST_PORT*/);
mSocket.setTimeToLive(UDPConstant.MULTICAST_TTLTIME);
mSocket.setLoopbackMode(false); // 必须是false才能开启广播功能
} catch (IOException e) {
e.printStackTrace();
}
}

public void sendMulticast(View view) {
new sendMsgToServerThread().start();
new receiveServerDataThread().start();
}

private class sendMsgToServerThread extends Thread {
@Override
public void run() {
DatagramPacket datagramPacket;
byte[] data = "I'm MultiCast client".getBytes();
try {
InetAddress address = InetAddress.getByName(UDPConstant.MULTICAST_IP_ADDRESS);
if (!address.isMulticastAddress()) {
throw new NoMulticastException();
}
datagramPacket = new DatagramPacket(data, data.length, address, UDPConstant.MULTICAST_PORT);
mSocket.send(datagramPacket);
// mSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

private class receiveServerDataThread extends Thread {
@Override
public void run() {
while (true) {
try {
InetAddress address = InetAddress.getByName(UDPConstant.MULTICAST_IP_ADDRESS);
if (!address.isMulticastAddress()) {
throw new NoMulticastException();
}
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, bytes.length);
mSocket.receive(receivePacket);
String ip = receivePacket.getAddress().getHostAddress();
int port = receivePacket.getPort();
String msg = new String(bytes, 0, receivePacket.getLength());
Log.e(TAG, "Received from Server(MultiCast): " + ip + "\tPort: " + port + "\tMsg: " + msg);

runOnUiThread(() -> {
mReceivedServerTv.setText(mReceivedServerTv.getText() + "\nReceived from Server: " + ip + " Port: " + port + " Msg: " + msg);
});

Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
自定义异常类 NoMulticastException.java
1
2
3
4
5
6
7
8
9
10
11
package com.ho1ho.android.udpexample.exceptions;

/**
* Author: Michael Leo
* Date: 19-9-20 上午10:16
*/
public class NoMulticastException extends Exception {
public NoMulticastException() {
super();
}
}

UDP 广播

  背景知识可以参看我之前写的文章网络基础知识)。

  UDP广播与UDP单播的最大区别在于 IP 地址不同。广播使用的预留的255.255.255.255地址。通过这一广播地址,消息会被发送到该网络上的所有网络主机。

  广播地址分为以下四种:

  • 有限广播,有限广播的地址设为255.255.255.255。使用该广播地址是不会被路由器转发的,因为如果路由器转发了广播信息,那么网络上的所有器都会进行链式传播,引发网络瘫痪。在指定给本地网络的广播数据包时,目的地址的网络标识部分和主机标识部分全都是1(即 255.255.255.255)。在任何情况下,路由器都不转发目的地址为有限广播地址的数据报,这样的数据报仅出现在本地网络中。
  • 非定向广播,这种地址的形式为“netid.255.255.255。”如126.255.255.255。使用非定向广播向特定网段上的所有主机发送数据包。
  • 子网定向广播,在划分为子网的网络中,子网定向广播地址仅限于特定子网上的主机。
  • 全部子网定向广播,在划分为子网的 internet 网络中,网络设备可以使用全部子网定向广播地址向所有子网的主机发送广播消息。这一类型的地址现在已经基本不使用了,而由D类组播地址所取代 。
服务端代码,UdpBroadCastService1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.ho1ho.android.udpexample.udpbroadcast;

import android.app.Service;
import android.bluetooth.BluetoothClass;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.ho1ho.android.udpexample.UDPConstant;
import com.ho1ho.android.udpexample.exceptions.NoMulticastException;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.List;

/**
* Author: Michael Leo
* Date: 19-9-20 上午10:18
*/
public class UdpBroadCastService1 extends Service {

private static final String TAG = "UdpBroadCastService1";

private DatagramSocket mSocket;

@Override
public void onCreate() {
super.onCreate();
try {
mSocket = new DatagramSocket(UDPConstant.BROADCAST_PORT);
mSocket.setSoTimeout(UDPConstant.BROADCAST_SO_TIMEOUT);
new WorkThread().start();
} catch (Exception e) {
e.printStackTrace();
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

private class WorkThread extends Thread {

@Override
public void run() {
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
while (true) {
try {
mSocket.receive(datagramPacket);
final String result = new String(buffer, 0, datagramPacket.getLength());
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(UdpBroadCastService1.this, "Server 1 Receive: " + result, Toast.LENGTH_SHORT).show());

InetAddress ip = datagramPacket.getAddress();
int port = datagramPacket.getPort();
Log.e(TAG, "Received UDP BroadCast client: " + ip.getHostAddress() + "\tPort: " + port + "\tMsg: " + result);

String responseMsg = "I'm BroadCast server1.";
byte[] buf = responseMsg.getBytes();
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length, ip, port);
mSocket.send(receivePacket);

} catch (IOException e) {
e.printStackTrace();
}

}

}
}
}
客户端代码,UdpBroadCastClientActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.ho1ho.android.udpexample.udpbroadcast;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.ho1ho.android.udpexample.R;
import com.ho1ho.android.udpexample.UDPConstant;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpBroadCastClientActivity extends AppCompatActivity {

private static final String TAG = "UdpBroadCastClient";

private DatagramSocket mSocket;
private TextView mReceivedServerTv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_udp_broadcast);

mReceivedServerTv = findViewById(R.id.txtReceived);

// Emulate servers
startService(new Intent(this, UdpBroadCastService1.class));

try {
// Client side
mSocket = new DatagramSocket();
mSocket.setSoTimeout(UDPConstant.BROADCAST_SO_TIMEOUT);
} catch (IOException e) {
e.printStackTrace();
}
}

public void sendBroadCast(View view) {
new sendMsgToServerThread().start();
new receiveServerDataThread().start();
}

private class sendMsgToServerThread extends Thread {
@Override
public void run() {
DatagramPacket datagramPacket;
byte[] data = "UDP Broadcast from client".getBytes();
try {
InetAddress address = InetAddress.getByName(UDPConstant.BROADCAST_IP_ADDRESS);
datagramPacket = new DatagramPacket(data, data.length, address, UDPConstant.BROADCAST_PORT);
mSocket.send(datagramPacket);
// mSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

private class receiveServerDataThread extends Thread {
@Override
public void run() {
while (true) {
try {
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes, bytes.length);
mSocket.receive(receivePacket);
String ip = receivePacket.getAddress().getHostAddress();
int port = receivePacket.getPort();
String msg = new String(bytes, 0, receivePacket.getLength());
Log.e(TAG, "Received UDP BroadCast server: " + ip + "\tPort: " + port + "\tMsg: " + msg);

runOnUiThread(() -> {
mReceivedServerTv.setText(mReceivedServerTv.getText() + "\nBroadCast Server: " + ip + " Port: " + port + " Msg: " + msg);
});

Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

组播(多播)特点

组播(多播)的应用主要有网上视频、网上会议等。

优点

  1. 具有同种业务的主机加入同一数据流,共享同一通道,节省了带宽和服务器的优点,具有广播的优点而又没有广播所需要的带宽。

  2. 服务器的总带宽不受客户端带宽的限制。由于组播协议由接收者的需求来确定是否进行数据流的转发,所以服务器端的带宽是常量,与客户端的数量无关。

  3. 与单播一样,多播是允许在广域网即 Internet 上进行传输的,而广播仅仅在同一局域网上传输。

缺点

  1. 多播与单播相比没有纠错机制,当发生错误的时候难以弥补,但是可以在应用层来实现此种功能。

  2. 多播的网络支持存在缺陷,需要路由器及网络协议栈的支持。

组播(多播)与广播

广播数据报的接收是被动的。
连接到子网上的所有主机都要接收广播数据报,这会增加网络流量,并且子网上的主机增加额外的负担。
UDP广播只能在内网(同一网段)有效,而组播可以较好实现跨网段群发数据。
UDP广播:消耗更多网络带宽,路由器向子网内的每个终端都投递一份数据包,不论这些终端是否乐于接收该数据包。
UDP组播:有了很大优化,只有终端加入到了一个广播组,UDP组播的数据才能被他接收到;
多播数据报的接收是主动的。主机主动加入指定的多播组,才会接收该组的多播数据报。
不同子网内的A,B进行组播通信,依靠IGMP协议。局域网组播,不考虑跨网段的组播实现。

组播路由协议IGMP与本文要介绍的内容无关。

其它说明

  广播和单播的处理过程是不同的,单播的数据只是收发数据的特定主机进行处理,而广播的数据整个局域网都进行处理。

例如在一个以太网上有3个主机:

主 机 A B C
IP地址 192.168.1.150 192.168.1.151 192.168.1.158
MAC地址 00:00:00:00:00:01 00:00:00:00:00:02 00:00:00:00:00:03

  单播流程:主机A向主机B发送UDP数据报,发送的目的IP为192.168.1.151,端口为 80,目的MAC地址为00:00:00:00:00:02。此数据经过UDP层、IP层,到达数据链路层,数据在整个以太网上传播,在此层中其他主机会 判断目的MAC地址。主机C的MAC地址为00:00:00:00:00:03,与目的MAC地址00:00:00:00:00:02不匹配,数据链路层 不会进行处理,直接丢弃此数据。

  主机B的MAC地址为00:00:00:00:00:02,与目的MAC地址00:00:00:00:00:02一致,此数据会经过IP层、UDP层,到达接收数据的应用程序。

  广播的流程:主机A向整个网络发送广播数据,发送的目的IP为192.168.1.255,端口为 80,目的MAC地址为FF:FF:FF:FF:FF:FF。此数据经过UDP层、IP层,到达数据链路层,数据在整个以太网上传播,在此层中其他主机会 判断目的MAC地址。由于目的MAC地址为 FF:FF:FF:FF:FF:FF,主机C和主机B会忽略MAC地址的比较(当然,如果协议栈不支持广播,则仍然比较 MAC 地址),处理接收到的数据。

  主机B和主机C的处理过程一致,此数据会经过IP层、UDP层,到达接收数据的应用程序。

参考文献

坚持原创及高品质技术分享,您的支持将鼓励我继续创作!