Java單播、組播(多播)、廣播的簡單實現

簡介
單播有TCP和UDP兩種實現,組播(多播)和廣播只有UDP一種實現。單播和廣播基本一樣,只是廣播的數據包IP爲廣播IP。
 
單播
DatagramSocket和DatagramPacket
服務端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class UDPServer {
public static void main(String[] args) throws Exception {
System.out.println("啓動UDP單播服務端");
// 構造DatagramSocket實例,指定本地端口6666
try (DatagramSocket datagramSocket = new DatagramSocket(6666)) {
//設置超時時間爲10秒
datagramSocket.setSoTimeout(10000);
while (true) {
// 應用層交給UDP多長的報文,UDP就照樣發送,一次發送一個報文。報文最大長度有限制,否則會導致IP層分片,最大值最好小於548字節
// 構造DatagramPacket實例,用來接收最大長度爲512字節的數據包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收報文,此方法在接收到數據報前一直阻塞
datagramSocket.receive(receivePacket);
System.out.println("客戶端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客戶端端口:" + receivePacket.getPort());
System.out.println("接收到的數據長度:" + receivePacket.getLength());
System.out.println("接收到的數據:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
}
}
}
}
 
客戶端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class UDPClient {
public static void main(String[] args) throws Exception {
{
// 構造DatagramSocket實例,指定本地端口8888
try (DatagramSocket datagramSocket = new DatagramSocket(8888)) {
for (int i = 0; i < 5; i++) {
String data = "當前循環:" + i;
// 構造數據報包,用來將data發送到指定主機上的指定端口號。
DatagramPacket sendPacket = new DatagramPacket(data.getBytes("UTF-8"), data.getBytes("UTF-8").length, new InetSocketAddress("10.206.16.67", 6666));
//發送報文
datagramSocket.send(sendPacket);
}
}
}
}
}
 
服務端打印:
啓動UDP單播服務端
客戶端地址:10.206.16.67
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:0
客戶端地址:10.206.16.67
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:1
客戶端地址:10.206.16.67
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:2
客戶端地址:10.206.16.67
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:3
客戶端地址:10.206.16.67
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:4
Exception in thread "main" java.net.SocketTimeoutException: Receive timed out
at java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(Native Method)
at java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:120)
at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:144)
at java.net.DatagramSocket.receive(DatagramSocket.java:812)
at UDPServer.main(UDPServer.java:16)
 
Socket和ServerSocket
服務端:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
 
public class TCPServer {
public static void main(String[] args) throws Exception {
System.out.println("啓動單播TCP服務端");
//構造ServerSocket實例,指定監聽的本地端口爲6666
try (ServerSocket serverSocket = new ServerSocket(6666)) {
//設置超時時間爲10秒
serverSocket.setSoTimeout(10000);
while (true) {
//偵聽並接收到此套接字的連接,此方法在連接傳入之前一直阻塞。
Socket socket = serverSocket.accept();
TCPServerHandleThread tcpServerHandleThread = new TCPServerHandleThread(socket);
Thread thread = new Thread(tcpServerHandleThread);
thread.start();
}
}
}
}
 
/**
* 爲每個連接進來的請求單獨起一個處理線程
*/
class TCPServerHandleThread implements Runnable {
private Socket socket;
 
public TCPServerHandleThread(Socket socket) {
this.socket = socket;
}
 
@Override
public void run() {
try {
try (BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {
//每次讀取的最大字節數,爲了演示特地寫小一點
byte[] bytes = new byte[8];
//每次讀取的有效字節數
int count;
//結果數組
byte[] result = new byte[0];
while ((count = inputStream.read(bytes)) != -1) {
//本次讀取的有效字節數組
byte[] temp1 = Arrays.copyOfRange(bytes, 0, count);
//複製結果數組到新數組,新數組長度爲結果數組的長度加上本次讀取的有效字節數,用0填充
byte[] temp2 = Arrays.copyOf(result, result.length + count);
// 將本次讀取的有效字節數組放到新數組裏
System.arraycopy(temp1, 0, temp2, result.length, count);
result = temp2;
}
InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("客戶端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());
InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();
System.out.println("本地綁定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());
System.out.println("接收到的數據長度:" + result.length);
System.out.println("接收到的數據:" + new String(result, "UTF-8"));
//關閉此socket輸入流
socket.shutdownInput();
outputStream.write("接收完畢".getBytes("UTF-8"));
outputStream.flush();
//關閉此socket輸出流
socket.shutdownOutput();
} finally {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
 
客戶端:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
 
public class TCPClient {
public static void main(String[] args) throws Exception {
// 構造Socket實例
try (Socket socket = new Socket("10.206.16.67", 6666); BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream())) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("服務端的ip和端口:" + inetSocketAddress.getAddress() + " " + inetSocketAddress.getPort());
InetSocketAddress inetSocketAddressL = (InetSocketAddress) socket.getLocalSocketAddress();
System.out.println("本地綁定的ip和端口:" + inetSocketAddressL.getAddress() + " " + inetSocketAddressL.getPort());
outputStream.write("abcdefg測試數據1234567890".getBytes("UTF-8"));
outputStream.flush();
//關閉此socket輸出流
socket.shutdownOutput();
byte[] bytes = new byte[1024];
int count;
if ((count = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, count, "UTF-8"));
}
//關閉此socket輸入流
socket.shutdownInput();
}
}
}
 
服務端打印:
啓動單播TCP服務端
客戶端的ip和端口:/10.206.16.67 62414
本地綁定的ip和端口:/10.206.16.67 6666
接收到的數據長度:29
接收到的數據:abcdefg測試數據1234567890
Exception in thread "main" java.net.SocketTimeoutException: Accept timed out
at java.net.DualStackPlainSocketImpl.waitForNewConnection(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:135)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:404)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at TCPServer.main(TCPServer.java:17)
 
客戶端打印:
服務端的ip和端口:/10.206.16.67 6666
本地綁定的ip和端口:/10.206.16.67 62414
接收完畢
 
客戶端還可以採用如下方式設置連接超時時間爲10秒:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("10.206.16.67", 6666), 10000);
 
組播(多播)
組播是一對多的通信,允許同時向大量的接收方發送數據包。一個主機可以有多個組播進程,這些進程可以加入到同一個組播組也可以加入到不同的組播組,主機會跟蹤記錄當前哪些進程屬於哪個組播組。進程隨時可以要求主機主動告訴路由器加入和退出哪個組播組,而且每隔一段時間(大概1分鐘)路由器也會向它所在的LAN發送一個查詢數據包,要求主機告訴路由器它自己屬於哪個組播組。支持組播的路由器負責向所有組內成員發送數據包,但不確保每個成員一定會收到。目的地址是組播ip的數據包會被路由器轉發到對應組播組內的主機。
組播ip地址是D類地址,可以使用224.0.2.0~238.255.255.255這個範圍的ip地址,組播進程根據組播ip進行分組。組播進程監聽某端口並通過此端口接收數據,當一個組播數據包被轉發到主機上的時候,主機會根據數據包的目的端口將數據包交給監聽此目的端口的組播進程。
如果主機是多網卡,那麼此時就需要注意了,一定要設置用哪個網卡發送和接受數據,因爲組播是無法跨網段的,否則會導致數據接收不到。
MulticastSocket繼承於DatagramSocket,因此可以發送也可以接收數據包。MulticastSocket綁定的端口是接收和發送數據的,如果數據包目的端口和此端口一致,則這個程序就能接收到數據包。setNetworkInterface方法是用來綁定網卡的。joinGroup告訴主機該程序要加入到哪個組播組,leaveGroup則是退出組播組。其他用法和DatagramSocket基本一致。
 
發送端:
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
 
public class MultiCastSender {
public static void main(String[] args) throws Exception {
InetAddress inetAddress = InetAddress.getByName("228.0.0.8");
MulticastSocket multicastSocket = new MulticastSocket();
multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("10.206.16.67")));
multicastSocket.joinGroup(inetAddress);
int count = 0;
while (true) {
if (count == 5) {
multicastSocket.leaveGroup(inetAddress);
return;
}
String message = "時間戳:" + System.currentTimeMillis();
byte[] bytes = message.getBytes("UTF-8");
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length,
inetAddress, 8888);
multicastSocket.send(datagramPacket);
count++;
}
}
}
 
接收端:
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
 
public class MultiCastReceiver {
public static void main(String[] args) throws Exception {
InetAddress inetAddress = InetAddress.getByName("228.0.0.8");
MulticastSocket multicastSocket = new MulticastSocket(8888);
multicastSocket.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getByName("10.206.16.67")));
multicastSocket.joinGroup(inetAddress);
int count = 0;
while (true) {
if (count == 5) {
multicastSocket.leaveGroup(inetAddress);
return;
}
 
// 構造DatagramPacket實例,用來接收最大長度爲512字節的數據包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收報文,此方法在接收到數據報前一直阻塞
multicastSocket.receive(receivePacket);
System.out.println("第" + (count + 1) + "次接收");
System.out.println("客戶端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客戶端端口:" + receivePacket.getPort());
System.out.println("接收到的數據長度:" + receivePacket.getLength());
System.out.println("接收到的數據:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
count++;
}
}
}
 
接收端打印:
第1次接收
客戶端地址:10.206.16.67
客戶端端口:54214
接收到的數據長度:25
接收到的數據:時間戳:1559133639107
第2次接收
客戶端地址:10.206.16.67
客戶端端口:54214
接收到的數據長度:25
接收到的數據:時間戳:1559133639108
第3次接收
客戶端地址:10.206.16.67
客戶端端口:54214
接收到的數據長度:25
接收到的數據:時間戳:1559133639108
第4次接收
客戶端地址:10.206.16.67
客戶端端口:54214
接收到的數據長度:25
接收到的數據:時間戳:1559133639108
第5次接收
客戶端地址:10.206.16.67
客戶端端口:54214
接收到的數據長度:25
接收到的數據:時間戳:1559133639108
 
廣播
服務端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class UDPServer {
public static void main(String[] args) throws Exception {
System.out.println("啓動UDP廣播服務端");
// 構造DatagramSocket實例,指定本地端口6666
try (DatagramSocket datagramSocket = new DatagramSocket(6666)) {
//設置超時時間爲10秒
datagramSocket.setSoTimeout(10000);
while (true) {
// 應用層交給UDP多長的報文,UDP就照樣發送,一次發送一個報文。報文最大長度有限制,否則會導致IP層分片,最大值最好小於548字節
// 構造DatagramPacket實例,用來接收最大長度爲512字節的數據包
byte[] data = new byte[512];
DatagramPacket receivePacket = new DatagramPacket(data, 512);
// 接收報文,此方法在接收到數據報前一直阻塞
datagramSocket.receive(receivePacket);
System.out.println("客戶端地址:" + receivePacket.getAddress().getHostAddress());
System.out.println("客戶端端口:" + receivePacket.getPort());
System.out.println("接收到的數據長度:" + receivePacket.getLength());
System.out.println("接收到的數據:" + new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8"));
}
}
}
}
 
客戶端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class UDPClient {
public static void main(String[] args) throws Exception {
{
// 構造DatagramSocket實例,指定本地端口8888
try (DatagramSocket datagramSocket = new DatagramSocket(8888)) {
for (int i = 0; i < 5; i++) {
String data = "當前循環:" + i;
// 構造數據報包,指定數據包的IP爲廣播地址,端口爲6666。
DatagramPacket sendPacket = new DatagramPacket(data.getBytes("UTF-8"), data.getBytes("UTF-8").length, new InetSocketAddress("255.255.255.255", 6666));
//發送報文
datagramSocket.send(sendPacket);
}
}
}
}
}
 
服務端打印:
啓動UDP廣播服務端
客戶端地址:192.168.56.1
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:0
客戶端地址:192.168.56.1
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:1
客戶端地址:192.168.56.1
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:2
客戶端地址:192.168.56.1
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:3
客戶端地址:192.168.56.1
客戶端端口:8888
接收到的數據長度:16
接收到的數據:當前循環:4
Exception in thread "main" java.net.SocketTimeoutException: Receive timed out
at java.net.DualStackPlainDatagramSocketImpl.socketReceiveOrPeekData(Native Method)
at java.net.DualStackPlainDatagramSocketImpl.receive0(DualStackPlainDatagramSocketImpl.java:120)
at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:144)
at java.net.DatagramSocket.receive(DatagramSocket.java:812)
at UDPServer.main(UDPServer.java:17)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章