基于UDP协议的网络编程(Java)

1、什么是UDP?

计算机网络(重点)运输层_江南煮酒的博客-CSDN博客

2、DatagramSocket

  1. Java中使用DatagramSocket代表UDP协议的Socket,
  2. DatagramSocket本身只是“对外接口”,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报
  3. Java使用DatagramPacket来代表数据报,
  4. DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。

DatagramSocket的构造器:

  • DatagramSocket(SocketAddress bindaddr):创建一个数据报套接字,绑定到指定的本地套接字地址。
    如果地址为null ,则创建未绑定的套接字。
    
    参数:
    bindaddr – 要绑定的本地套接字地址,对于未绑定的套接字为null 
  • DatagramSocket():等价于DatagramSocket(new InetSocketAddress(0))。
  • DatagramSocket(DatagramSocketImpl impl):使用指定的 DatagramSocketImpl 创建未绑定的数据报套接字。
  • DatagramSocket(int port, InetAddress laddr):创建一个数据报套接字,绑定到指定的本地地址。
    本地端口必须介于 0 和 65535 之间(包括 0 和 65535)。
    如果 IP 地址为 0.0.0.0,则套接字将绑定到wildcard地址,即内核选择的 IP 地址。
    
    参数:
    端口 – 要使用的本地端口
    laddr – 要绑定的本地地址
  • DatagramSocket(int port):等价于DatagramSocket(port, null))。

DatagramSocket实例的接收和发送数据的方法:

  • public synchronized void receive(DatagramPacket p):从此套接字接收数据报包。 
    当此方法返回时, DatagramPacket的缓冲区将填充接收到的数据。 
    数据报包DatagramPacket还包含发送者的 IP 地址和发送者机器上的端口号。
    该方法阻塞直到接收到数据报,数据报包对象的length字段包含接收到的消息的长度,如果消息长于数据包的长度,则消息将被截断。
    
    参数:
    p – 放置传入数据的DatagramPacket 。
  • public void send(DatagramPacket p):从此套接字发送数据报包。
    DatagramPacket包含指示要发送的数据、其长度、远程主机的 IP 地址和远程主机上的端口号的信息。
    
    参数:
    p – 要发送的DatagramPacket 。

3、DatagramPacket

该类代表一个数据报包。

构造方法:

  • DatagramPacket(byte buf[], int offset, int length):构造一个DatagramPacket用于接收长度为length的DatagramPacket包,指定缓冲区中的偏移量。
    length参数必须小于或等于buf.length 。
    
    参数:
    buf – 用于保存传入数据报的缓冲区。
    offset – 缓冲区的偏移量
    length – 要读取的字节数。
    
  • DatagramPacket(byte buf[], int length):等价于DatagramPacket(buf, 0, length)。
  • DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port):构造一个数据报包,用于向指定主机上的指定端口号发送长度length具有偏移量ioffset的数据包。 
    length参数必须小于或等于buf.length 。
    
    参数:
    buf – 数据包数据。
    offset – 分组数据偏移量。
    length – 数据包数据长度。
    address - 目标地址。
    port - 目标端口号。
  • DatagramPacket(byte buf[], int offset, int length, SocketAddress address):构造一个数据报包,用于向指定主机上的指定端口号发送长度length具有偏移量ioffset的数据包。 
    length参数必须小于或等于buf.length 。
    
    参数:
    buf – 数据包数据。
    offset – 分组数据偏移量。
    length – 数据包数据长度。
    address – 目标套接字地址。
    
  • DatagramPacket(byte buf[], int length, InetAddress address, int port):构造一个数据报包,用于将长度为length包发送到指定主机上的指定端口号。 
    length参数必须小于或等于buf.length 。
    
    参数:
    buf – 数据包数据。
    length – 数据包长度。
    address - 目标地址。
    port - 目标端口号。
  • DatagramPacket(byte buf[], int length, SocketAddress address):等价于DatagramPacket(buf, 0, length, address)。

其他方法:

  • public synchronized InetAddress getAddress():
    当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;
    当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。
    
  • public synchronized int getPort():
    当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;
    当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。
    
  • public synchronized SocketAddress getSocketAddress() {
        return new InetSocketAddress(getAddress(), getPort());
    }
    当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;
    当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的SocketAddress。
    

4、简单的DatagramSocket通信实例

Client1.java

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;

/**
 * @program: udp
 * @description: 客户端1
 * @author: YOUNG
 * @create: 2021-10-24 13:27
 */
public class Client1 {
    private DatagramSocket datagramSocket;
    private DatagramPacket datagramPacket;

    public static void main(String[] args) {
        try {
            new Client1().start("127.0.0.1", 9000);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    public void start(String hostname, int port) throws SocketException {
        datagramSocket = new DatagramSocket(new InetSocketAddress(hostname, port));
        System.out.println("客户端" + datagramSocket.getLocalSocketAddress() + "开始监听...");
        datagramPacket = new DatagramPacket(new byte[1024], 1024);
        new Thread(() -> {
            try {
                while (true) {
                    datagramSocket.receive(datagramPacket);
                    System.out.println("来自" + datagramPacket.getSocketAddress() + "的消息:" + new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
                    //业务逻辑
                    byte[] bytes = "收到".getBytes();
                    datagramSocket.send(new DatagramPacket(bytes, bytes.length, datagramPacket.getSocketAddress()));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        //业务逻辑
    }
}

Client2.java

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

/**
 * @program: udp
 * @description: 客户端2
 * @author: YOUNG
 * @create: 2021-10-24 14:20
 */
public class Client2 {
    private DatagramSocket datagramSocket;
    private DatagramPacket datagramPacket;

    public static void main(String[] args) {
        try {
            new Client2().start("127.0.0.1", 9001);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(String hostname, int port) throws IOException {
        datagramSocket = new DatagramSocket(new InetSocketAddress(hostname, port));
        System.out.println("客户端" + datagramSocket.getLocalSocketAddress() + "开始监听...");
        datagramPacket = new DatagramPacket(new byte[1024], 1024);
        new Thread(() -> {
            try {
                while (true) {
                    datagramSocket.receive(datagramPacket);
                    System.out.println("来自" + datagramPacket.getSocketAddress() + "的消息:" + new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
                    //业务逻辑
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        //业务逻辑
        byte[] bytes = "您好".getBytes();
        datagramSocket.send(new DatagramPacket(bytes, bytes.length, new InetSocketAddress("127.0.0.1", 9000)));
    }
}

实例结果:

5、使用MulticastSocket实现多点广播

  1. MulticastSocket 是一个 (UDP) DatagramSocket,具有加入 Internet 上其他多播主机“组”的附加功能。
  2. MulticastSocket是DatagramSocket的一个子类。
  3. MulticastSocket可以将数据报以广播方式发送到多个客户端。
  4. 多播组由 D 类 IP 地址和标准 UDP 端口号指定。 D 类 IP 地址的范围为224.0.0.0到239.255.255.255 ,包括端点。 地址 224.0.0.0 是保留地址,不应使用。

MulticastSocket类是实现多点广播的关键,当MulticastSocket把一个DatagramPacket发送到多点广播IP地址时,该数据报将被自动广播到加入该地址的所有Multicast Socket。MulticastSocket既可以将数据报发送到多点广播地址,也可以接收其他主机的广播信息。

构造器:

  • MulticastSocket(SocketAddress bindaddr) :创建一个绑定到指定套接字地址的MulticastSocket,或者如果地址为null ,则创建一个未绑定的套接字。
    
    形参:
    bindaddr – 要绑定到的套接字地址,或者对于未绑定的套接字为null 。
  • MulticastSocket(int port):等价于MulticastSocket(new InetSocketAddress(port))。
  • MulticastSocket():等价于MulticastSocket(new InetSocketAddress(0))。

其他方法:

  • public void setTimeToLive(int ttl):设置在此MulticastSocket上发送的多播数据包的默认生存时间,以控制多播的范围。
    ttl必须在0 <= ttl <= 255范围内,否则将抛出IllegalArgumentException 。 TTL 为0多播数据包不会在网络上传输,但可能会在本地传送。
    
    形参:
    ttl – 生存时间
  • public int getTimeToLive():获取在套接字上发送的多播数据包的默认生存时间。
  • public void joinGroup(InetAddress mcastaddr) :加入多播组。 它的行为可能会受到setInterface或setNetworkInterface影响。
    
    形参:
    mcastaddr – 要加入的多播地址
  • public void leaveGroup(InetAddress mcastaddr):离开多播组。 它的行为可能会受到setInterface或setNetworkInterface影响。
    
    形参:
    mcastaddr – 要离开的多播地址
  • public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf):在指定接口加入指定的多播组。
    
    形参:
    netIf – 指定接收多播数据报包的本地接口,或者为null以遵循由setInterface(InetAddress)或setNetworkInterface(NetworkInterface)设置的接口
  • public void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf):在指定的本地接口上保留多播组。
    
    形参:
    mcastaddr – 要离开的多播地址
    netIf – 指定本地接口或null以遵循由setInterface(InetAddress)或setNetworkInterface(NetworkInterface)设置的接口
  • public void setInterface(InetAddress inf):强制MulticastSocket使用指定的网络接口。
    
    提示:
    如果创建仅用于发送数据报的MulticastSocket对象,则使用默认地址、随机端口即可。但如果创建接收用的MulticastSocket对象,则该MulticastSocket对象必须具有指定端口,否则发送方无法确定发送数据报的目标端口。
    
  • public InetAddress getInterface():检索用于多播数据包的网络接口的地址。
    
    返回值:
    表示用于多播数据包的网络接口地址的InetAddress 
  • public void setNetworkInterface(NetworkInterface netIf):指定在此套接字上发送的传出多播数据报的网络接口。
  • public NetworkInterface getNetworkInterface():获取多播网络接口集。
  • public void setLoopbackMode(boolean disable):禁用/启用多播数据报的本地环回
    
    形参:
    disable – true禁用 LoopbackMode
  • public boolean getLoopbackMode():获取多播数据报本地环回的设置。
    
    返回值:
    如果 LoopbackMode 已被禁用,则为 true

6、简单的多播通信

multicastSender.java

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

/**
 * @program: udp
 * @description:发送者
 * @author: YOUNG
 * @create: 2021-10-24 15:08
 */
public class multicastSender {
    private MulticastSocket multicastSocket;
    private DatagramPacket datagramPacket;

    public static void main(String[] args) {
        new multicastSender().start();
    }

    public void start() {
        try {
            multicastSocket = new MulticastSocket(3000);
            multicastSocket.joinGroup(InetAddress.getByName("230.0.0.1"));
            multicastSocket.setLoopbackMode(true);
            datagramPacket = new DatagramPacket(new byte[1024], 1024);
            new Thread(() -> {
                try {
                    while (true) {
                        multicastSocket.receive(datagramPacket);
                        System.out.println("来自" + datagramPacket.getSocketAddress() + "的消息:" + new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
                        //业务逻辑
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
            //业务逻辑
            byte[] bytes = "您好".getBytes();
            while (true) {
                multicastSocket.send(new DatagramPacket(bytes, bytes.length, InetAddress.getByName("230.0.0.1"), 3000));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

multicastReceiver1.java

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

/**
 * @program: udp
 * @description:接受者1
 * @author: YOUNG
 * @create: 2021-10-24 15:08
 */
public class multicastReceiver1 {
    private MulticastSocket multicastSocket;
    private DatagramPacket datagramPacket;

    public static void main(String[] args) {
        new multicastReceiver1().start("230.0.0.1", 3001);
    }

    public void start(String mcastaddr, int port) {
        try {
            multicastSocket = new MulticastSocket(port);
            multicastSocket.joinGroup(InetAddress.getByName(mcastaddr));
            multicastSocket.setLoopbackMode(false);
            datagramPacket = new DatagramPacket(new byte[1024], 1024);
            new Thread(() -> {
                try {
                    while (true) {
                        multicastSocket.receive(datagramPacket);
                        System.out.println("来自" + datagramPacket.getSocketAddress() + "的消息:" + new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
                        //业务逻辑
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
            //业务逻辑
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7、DatagramChannel

DatagramChannel是一个可以发送和接收 UDP 数据包的通道。

  1. 打开通道
    DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind(new InetSocketAddress(9999));
  2. 接收数据
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    
    channel.receive(buf);
  3. 发送数据
    String newData = "..."
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    
    int bytesSent = channel.send(buf, new InetSocketAddress("IP地址", 80));
  4. 连接到特定地址
    可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。
    channel.connect(new InetSocketAddress("IP地址", 80));    

    连接时,还可以像正在使用传统通道一样,只是对发送的数据的交付没有任何保证。下面是一些示例:

    int bytesRead = channel.read(buf);
    int bytesWritten = channel.write(but);
    
  5. 可以通过Selector来选择DatagramChannel(不举例,用法都差不多,详见(14条消息) Java NIO(五)- Selector_江南煮酒的博客-CSDN博客

8、MulticastChannel

支持 Internet 协议 (IP) 多播的网络通道。

多播实现意在直接映射到本地多播设施。 因此,在开发接收 IP 多播数据报的应用程序时,应考虑以下事项:

  1. 通道的创建应指定与通道将加入的多播组的地址类型相对应的ProtocolFamily 。 当多播组的地址对应于另一个协议族时,不能保证到一个协议族中的套接字的通道可以加入并接收多播数据报。 例如,如果到IPv6套接字的通道可以加入IPv4多播组并接收发送到该组的多播数据报,则它是特定于实现的。
  2. 如果套接字绑定到特定地址,而不是通配符地址,那么它是特定于实现的,如果套接字接收到多播数据报。
  3. SO_REUSEADDR选项应该在binding套接字之前启用。 这是允许组的多个成员绑定到同一地址所必需的。

用法示例:

       //在此接口上加入多播组,并使用此接口发送多播数据报。
       NetworkInterface ni = NetworkInterface.getByName("hme0");
  
       DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET)
           .setOption(StandardSocketOptions.SO_REUSEADDR, true)
           .bind(new InetSocketAddress(5000))
           .setOption(StandardSocketOptions.IP_MULTICAST_IF, ni);
  
       InetAddress group = InetAddress.getByName("225.4.5.6");
  
       MembershipKey key = dc.join(group, ni);

おすすめ

転載: blog.csdn.net/qq_40100414/article/details/120932906