1.背景

  数据在网络中传输,必然回遇到读写问题....

2.比较NIO与IO

  

3.案例演示

3.1.缓冲区演示

package com.wfd360.nio;

import org.junit.Test;

import java.nio.ByteBuffer;

public class BufferDemo {
/**
* 缓冲区(Buffer)
* Buffer在Java NIO 中负责数据的存取,缓冲区就是数组,用于存储不同数据类型的数据。
* <p>
* 缓冲区类型
* 根据数据类型的不同(boolean除外),提供了相应类型的缓冲区。
* <p>
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
* 上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区。
* ByteBuffer最为常用
* ————————————————
* 缓冲区存取数据的两个核心方法
* put():存入数据到缓冲区中
* flip():切换到读取数据的模式
* get():获取缓冲区中的数据
* rewind():重复读,使position归0
* clear():清空缓冲区,但是缓冲区中的数据依然存在,只是处于一种“被遗忘“的状态。只是不知道位置界限等,读取会有困难。
* mark():标记。mark会记录当前的position,limit,capacity
* reset():position,limit,capacity恢复到mark记录的位置
* -------------------
* 缓冲区的四个核心属性
* capacity: 容量,表示缓冲区中最大存储数据的容量,一但声明不能改变。(因为底层是数组,数组一但被创建就不能被改变)
* limit: 界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写)
* position: 位置,表示缓冲区中正在操作数据的位置
* position <= limit <= capacity
* mark:标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置。
* ———————————————
*/
@Test
public void test1() {
//1.allocate():分配缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
//2.当前缓存区当前属性
System.out.println("-----allocate(128)-------");
print(byteBuffer);
//3.放入数据
String put = "123456";
byteBuffer.put(put.getBytes());
System.out.println("-----put()-------");
print(byteBuffer);
//4.flip():切换到读取数据的模式
byteBuffer.flip();
System.out.println("-----flip()-------");
print(byteBuffer);
//5.读取数据
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println("读到的数据:" + new String(bytes));
System.out.println("-----get()-------");
print(byteBuffer);
//6.rewind():重复读,使position归0
byteBuffer.rewind();
System.out.println("-----rewind()-------");
print(byteBuffer);
//7.clear():清空缓冲区,但是缓冲区中的数据依然存在,只是处于一种“被遗忘“的状态。只是不知道位置界限等,读取会有困难
byteBuffer.clear();
System.out.println("-----clear()-------");
print(byteBuffer);
//8.清理后仍然可以读取
char aChar = byteBuffer.getChar();
System.out.println("aChar=" + aChar);
System.out.println("---清理后读取--getChar()-------");
print(byteBuffer);
} /**
* 演示
* mark:标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置。
*/
@Test
public void test2() {
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.put("123".getBytes());
System.out.println("-----put()--1-----");
print(buffer);
//标记位置
buffer.mark();
System.out.println("-----mark()-------");
print(buffer);
//继续放入
buffer.put("456".getBytes());
System.out.println("-----put()--2-----");
print(buffer);
//返回到标记处
buffer.reset();
System.out.println("-----reset()-------");
print(buffer); } /**
* 直接缓冲区与非直接缓冲区
* 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JVM内存开销,处理过程中有复制操作。
* 非直接缓冲区的写入步骤:
* 创建一个临时的ByteBuffer对象。
* 将非直接缓冲区的内容复制到临时缓冲中。
* 使用临时缓冲区执行低层次I/O操作。
* 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
* ————————————————
* 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。
* 直接缓冲区在JVM内存外开辟内存,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会避免将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在物理内存内,会少一次复制过程,如果需要循环使用缓冲区,用直接缓冲区可以很大地提高性能。虽然直接缓冲区使JVM可以进行高效的I/O操作,但它使用的内存是操作系统分配的,绕过了JVM堆栈,建立和销毁比堆栈上的缓冲区要更大的开销
* ————————————————
*/
@Test
public void test3() {
//直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(128);
System.out.println(buffer.isDirect());
//非直接缓冲区
ByteBuffer buffer2 = ByteBuffer.allocate(128);
System.out.println(buffer2.isDirect());
} /**
* // Invariants: mark <= position <= limit <= capacity
* private int mark = -1;
* private int position = 0;
* private int limit;
* private int capacity;
*
* @param byteBuffer
*/
public void print(ByteBuffer byteBuffer) {
System.out.println("capacity=" + byteBuffer.capacity());
System.out.println("limit=" + byteBuffer.limit());
System.out.println("position=" + byteBuffer.position());
}
}

3.2.通道案例演示

package com.wfd360.nio;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; /**
* 通道(Channel)表示IO源与目标打开的连接。Channel类似于传统的”流“,只不过Channel本身不能直接访问数据,
* Channel只能与Buffer进行交互。
* -------------------------
* Channel是一个独立的处理器,专门用于IO操作,附属于CPU。
* 在提出IO请求的时候,CPU不需要进行干预,也就提高了效率。
* --------------------------
* 作用
* 用于源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。
* Channel本身并不存储数据,因此需要配合Buffer一起使用
* 主要实现类
* java.nio.channels.Channel接口:
* 用于本地数据传输:
* ​ |-- FileChannel
* 用于网络数据传输:
* ​ |-- SocketChannel
* ​ |-- ServerSocketChannel
* ​ |-- DatagramChannel
* <p>
* 获取通道
* 1.Java 针对支持通道的类提供了一个 getChannel() 方法。
* 本地IO操作
* <p>
* FileInputStream/FileOutputStream
* RandomAccessFile
* 网络IO
* <p>
* Socket
* ServerSocket
* DatagramSocket
* 2.在JDK1.7中的NIO.2 针对各个通道提供了静态方法 open();
* 3.在JDK1.7中的NIO.2 的Files工具类的 newByteChannel();
* ————————————————
*/
public class ChannelDemo {
// String path = "E:\\test\\t.rar";
// String path2 = "E:\\test\\t2.rar";
String path = "E:\\test\\1.docx";
String path2 = "E:\\test\\2.docx"; /**
* 如果存在者删除在创建,如果存在则直接创建
* file.createNewFile();
* ----------------------------
* 利用通道完成文件复制(getChannel()下-非直接缓冲区与直接缓冲区)
* 1.创建输入输出流FileInputStream\FileOutputStream
* 2.利用输入输出流创建管道 getChannel()
* 3.创建缓冲区
* 4.利用管道读写缓存区的数据(循环读取)
* 5.关闭资源
*/
@Test
public void test1() throws Exception {
long start = System.currentTimeMillis();
//1.创建输入输出流FileInputStream\FileOutputStream
FileInputStream fileInputStream = new FileInputStream(path);
File file = new File(path2);
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file); //2.利用输入输出流创建管道 getChannel()
FileChannel channelIn = fileInputStream.getChannel();
FileChannel channelOut = fileOutputStream.getChannel(); //3.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);//耗时:55018-耗时:52585
// ByteBuffer buffer = ByteBuffer.allocateDirect(1024);//耗时:65347-耗时:51880
int read = channelIn.read(buffer);
//4.利用管道循环读写缓存区的数据
while (read != -1) {
//取出
buffer.flip();
channelOut.write(buffer);
buffer.clear();
read = channelIn.read(buffer);
}
//5.关闭资源
channelIn.close();
channelOut.close();
fileInputStream.close();
fileOutputStream.close();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} /**
* 直接缓冲区读取(open()开启通道)
* 1.利用静态方法创建输入输出通道(FileChannel)
* 2.创建内存映射文件缓冲区(MapByteBuffer)
* 3.利用get\put读写文件
* 4.关闭资源
* ---------
* 耗时:25717-耗时:31151
*/
@Test
public void test2() throws Exception {
long start = System.currentTimeMillis();
//1. 利用静态方法创建输入输出通道(FileChannel)
FileChannel channelIn = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
FileChannel channelOut = FileChannel.open(Paths.get(path2), StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2. 创建内存映射文件缓冲区(map())
MappedByteBuffer bufferIn = channelIn.map(FileChannel.MapMode.READ_ONLY, 0, channelIn.size());
MappedByteBuffer bufferOut = channelOut.map(FileChannel.MapMode.READ_WRITE, 0, channelIn.size());
//3. 利用get\put读写文件
byte[] bytes = new byte[bufferIn.limit()];
bufferIn.get(bytes);
bufferOut.put(bytes);
//4. 关闭资源
channelIn.close();
channelOut.close();
//耗时统计
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
} /**
* 通道之间的数据传输(也是利用的直接缓冲器的方式)
* 1.利用静态方法创建输入输出通道(FileChannel)
* 2.利用transferTo()或者 transferFrom()进行文件传输
* 3.关闭资源
* ---------------
* 耗时:9812-耗时:10131
*/
@Test
public void test3() throws Exception {
long start = System.currentTimeMillis();
//1.利用静态方法创建输入输出通道(FileChannel)
FileChannel channelIn = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
FileChannel channelOut = FileChannel.open(Paths.get(path2), StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.利用transferTo()或者 transferFrom()进行文件传输
channelOut.transferFrom(channelIn, 0, channelIn.size());
//channelIn.transferTo(0,channelIn.size(),channelOut);
//3.关闭资源
channelIn.close();
channelOut.close();
//统计耗时
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start)); } /**
* 分散(Scatter)与聚集(Gather)
* 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
* 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
* ----------------------------------
* 1.创建写文件对象
* 2.获取通道
* 3.创建分散的缓冲区
* 4.分散读取文件
* 5.创建写入文件对象 和 写入通道
* 5.聚集写入
*/
@Test
public void test4() throws Exception {
System.out.println("---------开始-----------");
//编码处理
Charset charset = Charset.forName("gbk");
//解码器
CharsetDecoder decoder = charset.newDecoder();
//编码器
CharsetEncoder encoder = charset.newEncoder(); //1.创建写文件对象
RandomAccessFile randomAccessFileIn = new RandomAccessFile(path, "rw");
//2.获取通道
FileChannel channelIn = randomAccessFileIn.getChannel();
//3.创建分散的缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(128);
ByteBuffer buffer2 = ByteBuffer.allocate(256);
ByteBuffer[] buffers = {buffer1, buffer2};
//4.分散读取文件
channelIn.read(buffers);
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
//5.创建写入文件对象 和 写入通道
RandomAccessFile randomAccessFileOut = new RandomAccessFile(path2, "rw");
FileChannel channelOut = randomAccessFileOut.getChannel();
//5.聚集写入
channelOut.write(buffers);
//6.关闭资源
channelIn.close();
channelOut.close();
randomAccessFileIn.close();
randomAccessFileOut.close();
System.out.println("---------完成----------");
} /**
* 字符集Charset
* 设置字符集,解决乱码问题
* 编码:字符串->字节数组
* 解码:字节数组->字符串
*---------------------------
* 思路
* 用Charset.forName(String)构造一个编码器或解码器,利用编码器和解码器来对CharBuffer编码,对ByteBuffer解码。
* 需要注意的是,在对CharBuffer编码之前、对ByteBuffer解码之前,请记得对CharBuffer、ByteBuffer进行flip()切换到读模式。
* 如果编码和解码的格式不同,则会出现乱码。
* ————————————————
*/
@Test
public void test5() throws CharacterCodingException {
//创建编码对象
Charset charset1 = Charset.forName("gbk");
//获取编码器
CharsetEncoder encoder1 = charset1.newEncoder();
//获取解码器
CharsetDecoder decoder1 = charset1.newDecoder();
//创建缓冲区,并放入数据
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("你好世界我们");
buffer.flip();
//编码
ByteBuffer byteBuffer = encoder1.encode(buffer);
for (int i = 0; i < 4; i++) {
System.out.println(" --"+byteBuffer.get());
}
System.out.println("-----------------");
//解码
byteBuffer.flip();
CharBuffer charBuffer = decoder1.decode(byteBuffer);
System.out.println("---"+charBuffer.toString());
}
@Test
public void CharacterEncodingTest() throws CharacterCodingException {
Charset charset = Charset.forName("utf-8");
Charset charset1 = Charset.forName("gbk"); // 获取编码器 utf-8
CharsetEncoder encoder = charset.newEncoder(); // 获得解码器 gbk
CharsetDecoder decoder = charset1.newDecoder(); CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("绝不敷衍,从不懈怠!");
buffer.flip(); // 编码
ByteBuffer byteBuffer = encoder.encode(buffer);
for (int i = 0; i < 20; i++) {
System.out.println(byteBuffer.get());
} // 解码
byteBuffer.flip();
CharBuffer charBuffer = decoder.decode(byteBuffer);
System.out.println(charBuffer.toString());
} }

3.3.阻塞NIO案例演示

package com.wfd360.nio;

import org.junit.Test;

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; /**
* 网络阻塞IO与非阻塞IO
* 1.了解
* 1.1.传统IO是阻塞式的,也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
* 1.2.NIO是非阻塞式的,当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO操作,所以单独的线程可以管理多个输入和输出通道。因此, NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
* 2.阻塞模式与非阻塞模式
* 2.1.传统阻塞IO方式:客户端向服务器端发送请求,服务器端便开始进行监听客户端的数据是否传过来。这时候客户端在准备自己的数据,而服务器端就需要干等着。即使服务器端是多线程的,但有时一味增加线程数,只会让阻塞的线程越来越多。
* <p>
* 2.2.NIO的非阻塞方式:将用于传输的通道全部注册到选择器上。
* <p>
* 2.3.选择器的作用是监控这些通道的IO状况(读,写,连接,接收数据的情况等状况)。
* <p>
* 选择器与通道之间的联系:
* <p>
* 通道注册到选择器上
* 选择器监控通道
* 当某一通道,某一个事件就绪之后,选择器才会将这个通道分配到服务器端的一个或多个线程上,再继续运行。例如客户端需要发送数据给服务器端,只当客户端所有的数据都准备完毕后,选择器才会将这个注册的通道分配到服务器端的一个或多个线程上。而在客户端准备数据的这段时间,服务器端的线程可以执行别的任务。
* <p>
* ————————————————
* 使用NIO完成网络通信的三个核心
* 1.通道(Channel):负责连接
* <p>
* java.mio.channels.Channel 接口:
* |-- SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
* <p>
* |--Pipe.SinkChannel
* |--Pipe.sourceChannel
* 2.缓冲区(Buffer):负责数据的存取
* <p>
* 3.选择器(Select):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
* ————————————————
* 需求:
* 将文件a.txt从客户端传到服务端
* 阻塞模式完成客户端向服务器端传输数据
*/
public class BlockNioDemo {
String path = "E:\\test\\a.txt";
String path2 = "E:\\test\\b.txt";
String host = "192.168.0.103";
Integer port = 1001; /**
* 客户端:
*/
@Test
public void client() throws Exception {
//1.创建本地文件读取管道
FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
//2.创建网络写管道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
//3.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//4.读取本地文件管道中的数据
while (fileChannel.read(buffer) != -1) {
//5.写入网络管道
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
//6.告知服务端已完成传输
socketChannel.shutdownOutput();
System.out.println("--------客户端传输完成-------------");
//7.接收服务端接收结果
int len = socketChannel.read(buffer);
buffer.flip();
System.out.println("<---" + new String(buffer.array(), 0, len));
buffer.flip();
//7.关闭资源
socketChannel.close();
fileChannel.close();
System.out.println("--------客户端传输完成-------------");
} /**
* 塞模式完成客户端向服务器端传输数据
* 服务端:
*/
@Test
public void server() throws Exception {
//1.创建本地文件写入管道
FileChannel fileChannel = FileChannel.open(Paths.get(path2), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2.创建网络读取管道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//绑定端口号
ssChannel.bind(new InetSocketAddress(host, port));
//3.监听链接
System.out.println("--------服务端已开启等待连接------------");
SocketChannel socketChannel = ssChannel.accept();
//4.创建缓存区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//5.读取网络管道中的文件
int len;
while ((len = (socketChannel.read(buffer))) != -1) {
//6.写入本地文件管道
buffer.flip();
System.out.println("-->" + new String(buffer.array(), 0, len));
fileChannel.write(buffer);
buffer.clear();
}
//7.关闭读取(可以不写这步)
socketChannel.shutdownInput();
System.out.println("-------------服务端接收完成--------------------");
//8.响应客户端,文件传输完成
buffer.put("文件接收完成,ok".getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
//9.关闭资源
fileChannel.close();
socketChannel.close();
ssChannel.close();
System.out.println("-------------接收完成--------------------");
}
}

3.4.非阻塞NIO案例演示

package com.wfd360.nio;

import org.junit.Test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;
import java.util.Set; /**
* 需求:
* 以非阻塞的方式将文件从客户端传到服务端
*/
public class NoBlockNioDemo {
String path = "E:\\test\\a.txt";
String path2 = "E:\\test\\" + System.currentTimeMillis() + "-b.txt";
String host = "192.168.0.103";
Integer port = 1001; @Test
public void client() throws Exception {
//创建本地文件管道
FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ);
//创建网络传输管道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
//将网络传输管道设置为非阻塞模式
socketChannel.configureBlocking(false);
//创建缓存区
//文件管道读取文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (fileChannel.read(buffer) != -1) {
//网络管道写文件
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
//关闭传输
System.out.println("---------客户端传输完成---------");
//关闭资源
socketChannel.close();
fileChannel.close();
} @Test
public void server() throws Exception {
//创建网络服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(host, port));
//设置非阻塞模式
serverSocketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//注册选择器(读,写,链接..)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮询监听是否与准备就绪的请求
SocketChannel socketChannel = null;
System.out.println("-------服务端准备就绪等待连接----------");
//统计接收次数 和 读取次数
int sumAccept = 0;
int sumRead = 0;
while (selector.select() > 0) {
//如果有,获取已就绪的监听事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历监听事件
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
//判断监听事件是什么类型,根据类型做响应的处理
if (next.isAcceptable()) {
++sumAccept;
System.out.println("--->连接操作-就绪-" + sumAccept);
//如果是接收
// 获取连接
socketChannel = serverSocketChannel.accept();
// 切换非阻塞模式
socketChannel.configureBlocking(false);
// 注册连接器
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("--->连接操作-注册完成");
} else if (next.isReadable()) {
++sumRead;
//如果是读取
System.out.println("--->读取操作-就绪-" + sumRead);
// 获取就绪通道
SocketChannel readChannel = (SocketChannel) next.channel();
// 切换非阻塞模式
readChannel.configureBlocking(false);
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
//创建文件写入通道
path2 = "E:\\test\\" + System.currentTimeMillis() + "-b.txt";
FileChannel fileChannel = FileChannel.open(Paths.get(path2), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
while (readChannel.read(buffer) != -1) {
buffer.flip();
fileChannel.write(buffer);
buffer.clear();
}
System.out.println("------文件接收完成----");
readChannel.close();
fileChannel.close();
System.out.println("------关闭文件写通道,关闭网络读通道----");
} else {
System.out.println("-------其他非法操作-------");
}
//关闭就绪渠道
System.out.println("--->移除已经处理了的");
iterator.remove();
}
}
//关闭资源
serverSocketChannel.close();
socketChannel.close();
System.out.println("------服务端操作完成-----");
}
}

3.5.简易聊天室

  服务端

package com.wfd360.nio.chat;

import java.net.InetSocketAddress;
import java.net.Socket;
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.Iterator; /**
* 简易聊天室服务端
*/
public class ChatServer {
static String host = "192.168.0.103";
static Integer port = 1001; public static void main(String[] args) throws Exception {
//建立服务端网络通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(host, port));
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//注册选择器
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环检查是否有准备就绪的状态
System.out.println("-------服务端准备就绪-----------");
int sumAccept = 0;
int sumRead = 0;
while (selector.select() > 0) {
//获取当前所有准备就绪的状态,并循环依次处理
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey selectionKey = keys.next();
if (selectionKey.isAcceptable()) {
++sumAccept;
//如果是接收状态 就收当前通道 设置为非阻塞模式 注册通道
SocketChannel acceptChannel = serverSocketChannel.accept();
Socket socket = acceptChannel.socket();
System.out.println("ip=" + socket.getInetAddress() + ",port=" + socket.getPort() + ",=" + sumAccept);
acceptChannel.configureBlocking(false);
acceptChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
++sumRead;
//如果是读取状态 获取当前通道 设置非阻塞模式
SocketChannel readChannel = (SocketChannel) selectionKey.channel();
readChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int len = readChannel.read(buffer);
buffer.flip();
String recive = new String(buffer.array(), 0, len);
System.out.println("<--" + recive + ",=" + sumRead);
buffer.clear();
//向客户端响应数据
String send = "服务端已收到数据[" + recive + "]";
buffer.put(send.getBytes());
buffer.flip();
readChannel.write(buffer);
buffer.clear();
//关闭已处理的当前 通道
System.out.println("==========关闭已处理的当前 通道 ");
readChannel.close();
}
//移除以处理的key
System.out.println("==========移除以处理的key ");
keys.remove();
}
}
//关闭资源
serverSocketChannel.close();
}
}

  客户端

package com.wfd360.nio.chat;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner; public class ChatClient {
static String host = "192.168.0.103";
static Integer port = 1001; public static void main(String[] args) throws Exception {
while (true) {
//创建网络客户端通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(host, port));
//切换为非阻塞模式
//socketChannel.configureBlocking(false);
System.out.println("请输入要发送的数据:");
Scanner scanner = new Scanner(System.in); String next = scanner.next();
//发送数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(next.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
//接收数据
socketChannel.read(buffer);
buffer.flip();
System.out.println("<---" + new String(buffer.array()));
buffer.clear();
//关闭资源
socketChannel.close();
}
}
}

3.6.UDP非阻塞NIO案例演示

package com.wfd360.nio;

import org.junit.Test;

import java.io.IOException;
import java.net.DatagramSocket;
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.Iterator; /**
* DatagramChannel
* 实现发送数据与接收数据
*/
public class UDPChannelDemo {
String host = "192.168.0.103";
Integer port = 1001; @Test
public void send() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.bind(new InetSocketAddress(host, 1002));
datagramChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("测试发送".getBytes());
buffer.flip();
datagramChannel.send(buffer, new InetSocketAddress(host, port));
buffer.clear();
datagramChannel.close();
System.out.println("===客户端发送完成==");
} @Test
public void receive() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.bind(new InetSocketAddress(host, port));
Selector selector = Selector.open();
datagramChannel.register(selector, SelectionKey.OP_READ);
System.out.println("-----服务端准备就绪----------");
while (selector.select() > 0) {
DatagramSocket socket = datagramChannel.socket();
System.out.println("==>" + socket.getInetAddress() + ",==" + socket.getPort());
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
datagramChannel.receive(buffer);
buffer.flip();
System.out.println("<--" + new String(buffer.array()));
buffer.clear();
} else {
System.out.println("-------未知类型-----");
}
keyIterator.remove();
}
}
datagramChannel.close();
}
}

3.7.管道案例演示

package com.wfd360.nio;

import org.junit.Test;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe; /**
* 管道(Pipe)
* Java NIO 管道是两个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
* ThreadA-->sink--->source--->ThreadB
*/
public class PipeDemo {
@Test
public void test() throws IOException {
//创建管道
Pipe pipe = Pipe.open();
//获取写入管道
Pipe.SinkChannel sinkChannel = pipe.sink();
//写入数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("管道测试".getBytes());
buffer.flip();
sinkChannel.write(buffer);
buffer.clear();
//获取读管道
Pipe.SourceChannel sourceChannel = pipe.source();
//读取数据
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
sourceChannel.read(buffer1);
buffer1.flip();
System.out.println("====>" + new String(buffer1.array()));
//关闭资源
sourceChannel.close();
sinkChannel.close();
}
}

  完美!

NIO与网络编程系统化学习的更多相关文章

  1. 网络编程系统化学习(1.1.)--socket基础

    大纲 学完该阶段内容,你将会很好的完成如下的面试题 socket面试试题相关: 1.写一个简单的socket程序,实现客户端发送数据,服务端收到数据后,将该内容转发给客户端 2.简要概述一下socke ...

  2. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  3. JAVA第十周《网络编程》学习内容总结

    JAVA第十周<网络编程>学习内容总结 学习内容总结 1.初听到网络编程四个字可能会觉得很困难,实际上网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据,把数据发送到指定的位置, ...

  4. Linux网络编程&内核学习

    c语言: 基础篇 1.<写给大家看的C语言书(第2版)> 原书名: Absolute Beginner's Guide to C (2nd Edition) 原出版社: Sams 作者: ...

  5. Linux网络编程一站式学习

    提要 学过非常多遍计算机网络,依旧不会网络编程. 看完这篇文章之后就不会是这样了. 环境:Ubuntu14.04 64bit 何为Socket 是基于TCP/IP的网络应用编程中使用的有关数据通信的概 ...

  6. Java NIO之网络编程

    最近在研究Java NIO和netty,曾经一度感觉很吃力,根本原因还是对操作系统.TCP/IP.socket编程的理解不到位. 不禁感叹,还是当初逃的课太多. 假如上天给我一次机会,能够再回到意气风 ...

  7. 《TCP/IP网络编程》学习笔记整理

    简介 本笔记目前已包含 <TCP/IP网络编程>中的前 5 章,后续章节会在近期内补充完整. 我在整理笔记时所考虑的是:在笔记记完后,当我需要查找某个知识点时,不需要到书中去找,只需查看笔 ...

  8. 《Python网络编程》学习笔记--UDP协议

    第二章中主要介绍了UDP协议 UDP协议的定义(转自百度百科) UDP是OSI参考模型中一种无连接的传输层协议,它主要用于不要求分组顺序到达的传输中,分组传输顺序的检查与排序由应用层完成,提供面向事务 ...

  9. 《Python网络编程》学习笔记--使用谷歌地理编码API获取一个JSON文档

    Foundations of Python Network Programing,Third Edition <python网络编程>,本书中的代码可在Github上搜索fopnp下载 本 ...

随机推荐

  1. no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]

    今天down了一个开源项目,启动后一直存在如下错误信息: ERROR in ch.qos.logback.core.joran.spi.Interpreter@26:42 - no applicabl ...

  2. Pandas | 25 文件读写

    Pandas I/O API是一套像pd.read_csv()一样返回Pandas对象的顶级读取器函数. 读取文本文件(或平面文件)的两个主要功能是read_csv()和read_table().它们 ...

  3. Layui 在新标签中打开页面 / 模拟点击菜单

    原文:https://blog.csdn.net/sr_www/article/details/81394365 核心代码: //在新标签页中 打开页面 function newTab(url, ti ...

  4. [RN] React Native Image 实现placeholder占位图

    React Native Image 实现placeholder占位图 react-native Image没有placeholder这样的props,但是业务有需要这种场景, 解决方法为: 使用Im ...

  5. centos7中将tomcat注册为系统服务

    一.准备环境 操作系统:CentOS Linux release 7.6.1810 (Core) tomcat:apache-tomcat-8.5.41.tar.gz 二.编辑catalina.sh ...

  6. 第08组 Beta冲刺(2/5)

    队名:955 组长博客:点这里! 作业博客:点这里! 组员情况 组员1(组长):庄锡荣 过去两天完成了哪些任务 文字/口头描述 ?按照时间进度的安排进行相应的检查 展示GitHub当日代码/文档签入记 ...

  7. a标签设置水平右对齐

      1.情景展示 如上图所示,这其实是一个a标签,如何让它右对齐呢? 2.解决方案 第一步:将行内标签转化成块级元素,即display:block: 第二步:文字右对齐,即text-align:rig ...

  8. Centos查看虚拟机IP地址及使用XShell连接

    1.在VMware中安装Centos7系统[1] 2.查看虚拟机里的Centos7的IP[2] 1)查看IP 输入ip查询命名 ip addr 发现 ens33 没有 inet 这个属性,那么就没法通 ...

  9. 7种 JVM 垃圾收集器特点、优劣势及使用场景(多图)

    7种 JVM 垃圾收集器特点.优劣势及使用场景(多图)  mp.weixin.qq.com 点击上方"IT牧场",选择"设为星标"技术干货每日送达! 一.常见垃 ...

  10. IO流——字节流

    文件输出流 FileOutputStream:文件输出流是用于将数据写入 File,每次运行,都会覆盖之前文件中的数据 FileOutputStream(File file):创建一个向指定 File ...