写在前面
关于NIO的介绍和NIO的基础知识,请看第一篇https://www.jianshu.com/p/ff7f90f320e4
阻塞与非阻塞
阻塞与非阻塞:阻塞与非阻塞是进程在访问数据的时候,数据内是否准备就绪的一种处理方式,当数据没有准备好的时候,
阻塞:往往需要等待缓冲区中的数据准备好之后才能处理其他的事情,
否则一直等待在哪里
非阻塞:当我们的进程访问我们的数据缓冲区的时候,数据没有准备好
的时候,直接返回,不需要等待,数据有的时候也直接返回,
1.阻塞式NIO
和传统IO差不多,进行文本复制,客户端发送给服务端数据,服务端接收,开辟通道,使用缓冲区进行数据存取
@Test
public void client() throws IOException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost",8888));
//分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//3.读取本地文件,发送给服务端
FileChannel fileChannel = FileChannel.open(Paths.get("1.txt"),StandardOpenOption.READ);
while (fileChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
fileChannel.close();
socketChannel.close();
}
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8888));
//客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
//分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
FileChannel outChannel = FileChannel.open(Paths.get("server.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.CREATE);
while (socketChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
outChannel.write(byteBuffer);
System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
byteBuffer.clear();
}
//关闭通道
outChannel.close();
socketChannel.close();
serverSocketChannel.close();
}
重点:当服务端接收到客户端的文件,想给客户端返回信息,说已经收到了,该怎么做呢,
改进:
在客户端添加如下代码,当客户端发送完数据之后,读取服务端返回的数据

在服务端添加如下代码,当服务端接收完数据之后,发送数据给客户端

理论上没问题,但是,程序停不下来了,什么意思,客户端发送给服务端数据,服务端接收,客户端没停,服务端就一直在读取客户端发来的数据,但是客户端的数据已经发完了,在等待服务端返回数据,然后服务端就卡在了read方法,线程卡死,
2.非阻塞式NIO
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8888));
//2.设置通道为非阻塞模式
socketChannel.configureBlocking(false);
//3.分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//4.填充数据
byteBuffer = byteBuffer.put((LocalDateTime.now().toString().trim()+"\n"+"hello").getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
//关闭通道
socketChannel.close();
}
@Test
public void server() throws IOException {
//1.打开通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.切换成非阻塞模式
serverSocketChannel.configureBlocking(false);
//3.绑定端口
serverSocketChannel.bind(new InetSocketAddress(8888));
//4.得到选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上
/**
*功能描述
* @date 2018/6/8
* @param 选择器
* @param 监控通道的事件 OP_WRITE写 OP_ACCEPT接收 OP_CONNECT链接 OP_READ读
* @return void
*/
//指定监听接收事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经准备就绪的事件
while (selector.select()>0){
//7.得到当前选择器中的所有的选择键(已就绪的事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//8.获取已经准备就绪的事件
SelectionKey next = iterator.next();
//9.判断具体是什么事件准备就绪
if (next.isAcceptable()){//链接事件就绪
//10.得到客户端通道
SocketChannel socketChannel = serverSocketChannel.accept();
//11.切换通道为非阻塞模式
socketChannel.configureBlocking(false);
//12.将通道注册到选择器中,监听客户端的读就绪事件
socketChannel.register(selector,SelectionKey.OP_READ);
}if (next.isReadable()){//读就绪
//13.得到读就绪的通道
SocketChannel channel = (SocketChannel)next.channel();
//14.分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//15.读取数据
while (channel.read(byteBuffer)!=-1){
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
byteBuffer.clear();
}
}
iterator.remove();
}
}
}
服务端:
- 打开通道
- 将通道切换成非阻塞模式
- 绑定端口
- 得到选择器
- 把通道注册到选择器上(指定触发事件)OP_WRITE写 OP_ACCEPT接收 OP_CONNECT链接 OP_READ读
- 轮询式查看选择器上已经准备就绪的事件
- 得到所有已经准备好的事件
- 判断是什么事件
- 进行相关处理
只有当某个事件准备就绪之后,才会处理,不会造成线程卡主的问题
个人见解,有错误请指正
QQ群:552113611
网友评论