如何创建多个udp客户端在java nio中侦听同一个端口?

时间:2017-07-05 09:44:38

标签: java io udp nio

对于普通的io,我可以这样做:

s1=DatagramChannel.open()
s1.bind(new Inetsocketaddress("localhost",1234)

s2=DatagramChannel.open()
s2.bind(new Inetsocketaddress("localhost",1234)// will throw an exception

但是在nio。这该怎么做?我试过这样:

第一

s1=DatagramChannel.open()
s1.setOption(StandardSocketOptions.SO_REUSEADDR, true);
s1.bind(new Inetsocketaddress("localhost",1234)

s2=DatagramChannel.open()
s2.setOption(StandardSocketOptions.SO_REUSEADDR, true);
s2.bind(new Inetsocketaddress("localhost",1234)

//these code will not throw exception. but s2 cannot receive any data.
// when i close s1,  then s2 will receive data.

第二

{{1}}

2 个答案:

答案 0 :(得分:2)

如果您确实需要所有客户端处理消息,则需要多播/广播协议。从这个意义上讲,如前一个问题(Does Java NIO support broadcast or multicast?)所述,NIO2支持多播和广播但不使用DatagramChannel类。相反,您拥有MultiCastChannel界面(您可以在http://javanio.info/filearea/nioserver/WhatsNewNIO2.pdf中找到官方文档,并在How do I implement a multicast client in NIO.2?中找到示例)。 关于您的代码,它应该如下所示:

   NetworkInterface netInterface = NetworkInterface.getByName("em1");
   InetAddress group = InetAddress.getByName("localhost");

   // Reader 1
   System.out.println("Create Reader 1");
   DatagramChannel s1 = DatagramChannel.open(StandardProtocolFamily.INET);
   s1.setOption(StandardSocketOptions.SO_REUSEADDR, true);
   s1.bind(new InetSocketAddress(PORT));
   s1.setOption(StandardSocketOptions.IP_MULTICAST_IF, netInterface);

   // Reader 2
   System.out.println("Create Reader 2");
   DatagramChannel s2 = DatagramChannel.open(StandardProtocolFamily.INET);
   s2.setOption(StandardSocketOptions.SO_REUSEADDR, true);
   s2.bind(new InetSocketAddress(PORT));
   s2.setOption(StandardSocketOptions.IP_MULTICAST_IF, netInterface);

请注意,底层硬件堆栈必须支持多播/广播。否则,join方法将抛出异常。

如果您不需要所有客户端处理消息,而是需要其中任何一个来处理它而不阻塞其他客户端,则可以设置非阻塞套接字选项。考虑到您提供的代码,我在下面添加了一个非阻塞的解决方案:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.StandardCharsets;


public class Main {

    private static final String HOST = "localhost";
    private static final int PORT = 1234;

    private static final String MESSAGE = "HelloWorld";
    private static final int MESSAGE_SIZE = MESSAGE.length();


    public static void main(String[] args) throws IOException {
        // Reader 1
        System.out.println("Create Reader 1");
        DatagramChannel s1 = DatagramChannel.open();
        s1.configureBlocking(false);
        s1.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        s1.bind(new InetSocketAddress(HOST, PORT));

        // Reader 2
        System.out.println("Create Reader 2");
        DatagramChannel s2 = DatagramChannel.open();
        s2.configureBlocking(false);
        s2.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        s2.bind(new InetSocketAddress(HOST, PORT));

        // Writer
        System.out.println("Create Writer");
        DatagramChannel s3 = DatagramChannel.open();

        // Send and receive messages
        System.out.println("Send message");
        send(s3);
        System.out.println("Receive message on Reader 1");
        receive(s1);
        System.out.println("Receive message on Reader 2");
        receive(s2);

        // Close
        System.out.println("Close");
        s1.close();
        s2.close();
        s3.close();
    }

    private static void send(DatagramChannel channel) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(MESSAGE_SIZE);
        buf.clear();
        buf.put(MESSAGE.getBytes());
        buf.flip();

        int bytesSent = channel.send(buf, new InetSocketAddress(HOST, PORT));
        System.out.println("Sent: " + bytesSent);
    }

    private static void receive(DatagramChannel channel) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(MESSAGE_SIZE);
        buf.clear();

        channel.receive(buf);

        String str = new String(buf.array(), StandardCharsets.UTF_8);
        System.out.println("Received: " + str);
    }

}

哪个输出是:

Create Reader 1
Create Reader 2
Create Writer
Send message
Sent: 10
Receive message on Reader 1
Received: 
Receive message on Reader 2
Received: HelloWorld
Close

答案 1 :(得分:-1)

我知道原因。

单播只能由一个客户端接收,如果多个客户端监听同一个端口,则只有第一个客户端会收到数据。

如果您希望多个客户端接收数据,则应使用多播或广播。

如果使用广播,则应将数据包发送到255.255.255:端口,以便侦听端口的所有客户端都将接收数据。 并且客户端绑定地址无法使用localhost或127.0.0.1。你应该使用0.0.0.0或InetSocketAddress(端口)或你的本地地址,如192.168.100.123

如果任何其他应用程序绑定没有' reuseAddress'的端口,那么您也无法绑定端口。

kotlin代码:

fun main(args: Array<String>) {
    val selector = Selector.open()
    val reader = DatagramChannel.open()
    val sender = DatagramChannel.open()

    reader.configureBlocking(false)
    reader.socket().reuseAddress = true
//    reader.bind(InetSocketAddress("0.0.0.0", 1234))
//    reader.bind(InetSocketAddress("192.168.100.37", 1234))
    reader.bind(InetSocketAddress(1234))
    reader.register(selector, SelectionKey.OP_READ)

    sender.socket().broadcast = true

    val t = Thread {
        while (true) {
            val line = readLine()
            if (line != null) {
                val buffer = ByteBuffer.wrap(line.toByteArray())
                sender.send(buffer, InetSocketAddress("255.255.255.255", 1234))
            }
        }
    }
    t.isDaemon = true
    t.start()


    while (selector.isOpen) {
        val n = selector.select()
        if (n == 0) continue
        val keys = selector.selectedKeys().iterator()
        while (keys.hasNext()) {
            val key = keys.next()
            keys.remove()
            if (key.isReadable) {
                val buffer = ByteBuffer.allocateDirect(1024)
                val channel = key.channel() as DatagramChannel
                val address = channel.receive(buffer)
                buffer.flip()
                val s = Charsets.UTF_8.decode(buffer)
                println("$address says : $s")
            }
        }
    }
}
相关问题