引入:
基础概念:
- 同步:一个对象或者逻辑,在一个时间段内只允许一个线程操作,就是同步操作。
- 异步:一个对象或者逻辑,在一个时间段内只允许多个线程操作,就是异步操作。
- 阻塞:线程没有获取到想要的结果,就会停留在原地等待。
- 非阻塞:线程无论有没有获取到想要的结果,都会继续往下走。
Java基础中,学过IO的操作,我们把它叫做BIO,也就是同步阻塞式IO,同步阻塞式IO有它不好的地方,比如它的效率很低,保持恶意连接线程无法释放。所以我们今天学习的就是NIO(同步非阻塞式线程),他的作用就是传输数据。
NIO的三大组件:
NIO有三大组件,分别是buffer,channel,selector。
buffer:
buffer顾名思义叫缓冲区,作用就是用来存储数据的。
可存储类型:
buffer中可以存储很多的基本数据类型,从这些名字可以很容易看出来,最常用的是ByteBuffer,所以下面就用ByteBuffer来做案例。
buffer中的一些重要位置:
capacity:用来指定缓存区的容量大小,一但声明了就不能改变。
position:指向需要写入的位置,初始化的时候position=0,如果后面写入数据到缓存,position自动往后移动。
limit:position所能到达的最大位置,可以在遍历中使用,把position的位置给limit后,position=0,然后遍历,这样就可以避免遍历出那些没有存储东西的单元。buffer初始化的时候,position和limit重合。
channel:
在java网络编程中我们学习过TCP和UDP,那么就对channel一定不会陌生,他的作用就是用来传输数据的,而在NIO中我们也使用了管道来进行数据的传输。
channel种类:
常用的channel为TCP channel,而tcp channel中分为socket channel和server socket channel,也就是客户端和服务端,下面的案例也使用该channel。
注意: channel默认为阻塞方式,所以在使用的时候可以手动切换为非阻塞,并且channel可以双向传输也就是客户端用channel传输数据到服务端接收,服务端可以用channel传输数据到客户端接收。
selector:
selector可以选择channel中的事件(write,read,accept),根据选中的不同事件来做不同的操作,需要注意的是selector只能对非阻塞的channel来进行选择。
综合案例:
服务端:
public class Server {
public static void main(String[] args) throws IOException {
// 开启服务器端通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 设置非阻塞
ssc.configureBlocking(false);
// 绑定端口
ssc.bind(new InetSocketAddress(8090));
// 开启选择器
Selector selc = Selector.open();
// 将通道注册到选择器上
ssc.register(selc, SelectionKey.OP_ACCEPT);
// 模拟:服务器开启之后不关闭
while (true) {
// 随着运行时间的延长,接收到的请求会越来越多
// 需要针对这些请求进行选择,将能触发事件的请求留下
// 将不能触发事件的请求过滤掉
selc.select();
// 选完之后,留下来的请求都是有用的请求
// connect/read/write -> accept/write/read
// 因为这些请求中不一定存在所有的事件
// 所以需要获取请求的事件类型
Set<SelectionKey> set = selc.selectedKeys();
// 需要针对请求的不同类型来进行分门别类的处理
Iterator<SelectionKey> it = set.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// 触发服务器的accept操作
// -> 说明客户端一定调用了connect方法
if (key.isAcceptable()) {
// 从事件中获取通道
ServerSocketChannel sscx =
(ServerSocketChannel) key.channel();
// 接收连接
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 根据需求确定,如果需要读操作,那么就给READ
// 如果需要写操作,那么就给WRITE
// 如果存在多个register,那么后边的会覆盖前边的
sc.register(selc,
//SelectionKey.OP_READ + SelectionKey.OP_WRITE);
// SelectionKey.OP_READ | SelectionKey.OP_WRITE);
SelectionKey.OP_READ ^ SelectionKey.OP_WRITE);
}
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(new String(buffer.array(), 0,
buffer.position()));
// 读取完成之后,需要将READ事件从通道身上移除掉
// key.interestOps() - 获取到所有事件
sc.register(selc,
// key.interestOps() - SelectionKey.OP_READ);
key.interestOps() ^ SelectionKey.OP_READ);
}
if (key.isWritable()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.write(ByteBuffer.wrap("收到数据啦~~~".getBytes()));
sc.register(selc,
key.interestOps() - SelectionKey.OP_WRITE);
}
// 处理完成之后,需要将这一大类事件移除掉
it.remove();
}
}
}
}
客户端:
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(
new InetSocketAddress("localhost", 8090));
sc.write(ByteBuffer.wrap("hello server".getBytes()));
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(
new String(buffer.array(), 0, buffer.position()));
sc.close();
}
}