众所周知,java的NIO模块想要传输数据需要依赖缓冲区和通道,二者缺一不可。
补充一下缓冲区(buf)的几个基本方法:
1.allocate():分配一个指定大小的缓冲区。
2.put():向buf里写数据。
3.get():从buf里读数据。
4.flip():将缓冲区从写模式切换到读模式,position被重置为0。
5.clear():从读模式切换到写模式,表面清空,里边的数据只是被遗忘,但是后续写的数据会覆盖原有的数据。
好了,关于缓冲区、通道、选择器这些东西的定义就不细说了,直接上代码了,自认为注释非常详细,一行一注,首先我们先演示一下阻塞式的通信:
package niotest;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.Test;
public class TestBlockingNIO {
@Test
public void client() throws IOException {
//获取通道,一个socketChannel用于tcp通信,一个FileChannel用于读取本地文件以备后续发送
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
FileChannel fileChannel = FileChannel.open(Paths.get("D://1.tif"), StandardOpenOption.READ);
//分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//读取本地文件,发送到服务端
while(fileChannel.read(buf)!=-1) {
buf.flip();
socketChannel.write(buf);
buf.clear();
}
//表明客户端发送完毕
socketChannel.shutdownOutput();
//接受服务端的反馈
int len = 0;
while((len=socketChannel.read(buf))!=-1) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
socketChannel.close();
fileChannel.close();
}
@Test
public void server() throws IOException {
//获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
FileChannel fileChannel = FileChannel.open(Paths.get("D://3.tif"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9999));
//获取客户端连接的通道,这是阻塞式的,会阻塞等待远程连接
SocketChannel socketChannel = serverSocketChannel.accept();
//分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//接受客户端发送的数据,保存成本地文件
while(socketChannel.read(buf)!=-1) {
buf.flip();
fileChannel.write(buf);
buf.clear();
}
//给客户端发送反馈
buf.put("服务端接受完毕!!!".getBytes());
buf.flip();
socketChannel.write(buf);
socketChannel.close();
serverSocketChannel.close();
fileChannel.close();
}
}
上面这个阻塞式的远程传输虽然简单,但是显然发挥不出NIO的优势,下面就来个复杂点的非阻塞式的实现:
package niotest;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import org.junit.Test;
public class TestNoBlockingNIO {
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
//2.切换非阻塞模式
socketChannel.configureBlocking(false);
//3.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(2048);
//4.发送数据给server端
buf.put(new Date().toString().getBytes());
buf.flip();
socketChannel.write(buf);
buf.clear();
//5.关闭通道
socketChannel.close();
}
@Test
public void server() throws IOException, InterruptedException {
//1.获取服务端socket通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.切换非阻塞模式
serverSocketChannel.configureBlocking(false);
//3.绑定端口
serverSocketChannel.bind(new InetSocketAddress(9999));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上,并指定监听的事件类型(一个选择器上可以注册多个通道),attach可附带一个信息,使用时用selectionKey.attachment()方法获取
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT).attach("accept");
//6.不断的循环获取选择器上已经准备就绪的事件,selector.select()>0表示至少有一个事件已经准备就绪
while((selector.select()) > 0) {
//7.获取当前选择器中所有注册的已经就绪的事件,iterator()方法是Set集合中的方法,生成一个迭代接口
Set<SelectionKey> skSet = selector.selectedKeys();
Iterator<SelectionKey> skIterator = skSet.iterator();
while(skIterator.hasNext()) {
//8.获取准备就绪的事件
SelectionKey sk = skIterator.next();
//9.判断是什么事件准备就绪
if(sk.isAcceptable()) {
//10.若是接受就绪,则获取客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
//11.切换非阻塞模式
socketChannel.configureBlocking(false);
//12.把这个通道也注册到选择器上
socketChannel.register(selector, SelectionKey.OP_READ).attach("read");
}else if(sk.isReadable()) {
//13.获取选择器上读就绪的通道
SocketChannel socketChannel = (SocketChannel)sk.channel();
//14.读数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while ((len=socketChannel.read(buf))!=-1) {
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
//15.取消当前的selectionKey,不然会一直循环,比如会一直获取到连接准备就绪事件,然后重复获取新的客户端连接
//remove删除实际上是删除的63行中的skSet中的selectionKey,不然skSet中的元素会越来越多,就出问题了,可以自己调一下试试(我一开始就忘了清空)
skIterator.remove();
}
}
}
}