2019独角兽企业重金招聘Python工程师标准>>>
DatagramChannel——UDP数据报的通道
Java NIO中的DatagramChannel是一个能收发UDP包的通道。
操作步骤:
打开 DatagramChannel
接收/发送数据
用法中的一些步骤和SocketChannel类似,具体看前文博客,这里我就不说明了。
注意的是,UDP非阻塞网络,NIO中信息的接收端和发送端都是直接用DatagramChannel的open方法生成一个数据报通道,发送的时候指明地址和端口;接收的一方需要先bind一个端口号,监听着。接收方可能监听到多个发送端向其发送数据,为了单个线程能够处理多个客户端发送的数据,并且在发送的过程中不被某个发送端全程阻塞,同样需要用到选择器。
使用选择器仍然需要将通道注册到选择器上,并指明需要监听的事件,即选择键。
代码示例
package com.happybks.nio.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
public class TestNonBlockingNIO2 {
@Test
public void send() throws IOException{
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String inputStr=scanner.next();
buf.put((new Date().toString()+":\n"+inputStr).getBytes());
buf.flip();
datagramChannel.send(buf, new InetSocketAddress("127.0.0.1", 8888));
buf.clear();
}
scanner.close();
datagramChannel.close();
}
@Test
public void receive() throws IOException{
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.bind(new InetSocketAddress(8888));
Selector selector = Selector.open();
datagramChannel.register(selector, SelectionKey.OP_READ);
while(selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey=iterator.next();
if(selectionKey.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
datagramChannel.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
}
iterator.remove();
}
}
}
这里有一点上次没有提到,就是选择器的select()方法还有两个类似的方法。
通过Selector选择通道
一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
下面是select()方法:
int select()
int select(long timeout)
int selectNow()
select()阻塞到至少有一个通道在你注册的事件上就绪了。
select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。
select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。
(本文出自oschina的博主happyBKs的博文:https://my.oschina.net/happyBKs/blog/1603680)
运行结果
回到我们刚才的例子。还需要注意,每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
程序运行的结果:
我们在发送端输入信息,接收端立即受到信息并输出。