1).BIO:Block(阻塞的) IO——。 【同步、阻塞】
2).NIO:Non-Block(非阻塞的(同步)IO——JDK1.4开始的。 【同步、非阻塞】
3).AIO:Asynchronous(异步-非阻塞)IO——JDK1.7开始 【异步、非阻塞】
举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片
同步+阻塞
这个时候如果我们一直在店里面啥都不干,一直等待商家面前等待它洗完照片,这个过程就叫同步阻塞。
同步+非阻塞
当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问(轮询),直到照片洗完,这个过程就叫同步非阻塞。
异步+阻塞
因为店里生意太好了,越来越多的人过来拍,店里面快没地方坐了,老板说你把你手机号留下,我一会洗好了就打电话告诉你过来取,然后你去外面找了一个长凳开始躺着睡觉等待老板打电话,啥不都干,这个过程就叫异步阻塞。
异步+非阻塞
当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,同时等待老板打电话,这样以来两不耽误,这个过程就叫异步非阻塞。
总结
从上面的描述中我们其实能够看到阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果(阻塞),还是继续做其他的任务(非阻塞)。
而异步和同步,则是对于请求结果的获取是客户端主动等待获取(同步),还是由服务端来通知消息结果(异步)。
从这一点来看同步和阻塞其实描述的两个不同角度的事情,阻塞和非阻塞指的一个是客户端等待消息处理时的本身的状态,是挂起还是继续干别的。同步和异步指的对于消息结果的获取是客户端主动获取,还是由服务端间接推送。
NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程
首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)
import java.nio.ByteBuffer;
/*
java.nio.ByteBuffer:字节缓冲区。
创建对象的方式:使用静态方法获取
*/
public class Demo01ByteBuffer {
public static void main(String[] args) {
ByteBuffer buffer1 = ByteBuffer.allocate(100);//里边包含一个byte[100] 间接
ByteBuffer buffer2 = ByteBuffer.allocateDirect(100);//里边包含一个byte[100] 直接
ByteBuffer buffer3 = ByteBuffer.wrap("我爱java".getBytes());//里边包含一个byte内容是"我爱java" 间接
}
}
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
向ByteBuffer中添加数据
- public ByteBuffer put(byte b):向当前可用位置添加数据。
- public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
- public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
*/
public class Demo02ByteBuffer {
public static void main(String[] args) {
//创建一个长度为10的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(Arrays.toString(buffer.array()));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//public ByteBuffer put(byte b):向当前可用位置添加数据。
buffer.put((byte)1);
buffer.put((byte)2);
System.out.println(Arrays.toString(buffer.array()));//[1, 2, 0, 0, 0, 0, 0, 0, 0, 0]
//public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
byte[] bytes = {
10,20,30,40,50};
buffer.put(bytes);
System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 0, 0, 0]
buffer.put(bytes,3,2);
System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 0]
//put(int index, byte b) 往指定索引处添加一个byte字节
buffer.put(9,(byte)88);
System.out.println(Arrays.toString(buffer.array()));//[1, 2, 10, 20, 30, 40, 50, 40, 50, 88]
}
}
import java.nio.ByteBuffer;
public class Demo03capacity {
public static void main(String[] args) {
//创建一个长度为10的ByteBuffer,创建完之后长度不能改变,底层是数组
ByteBuffer buffer1 = ByteBuffer.allocate(10);
System.out.println("容量:"+buffer1.capacity());//容量:10
ByteBuffer buffer2 = ByteBuffer.wrap("你好".getBytes());
System.out.println("容量:"+buffer2.capacity());//容量:6
}
}
import java.nio.ByteBuffer;
public class Demo04Limit {
public static void main(String[] args) {
ByteBuffer buf = ByteBuffer.allocate(10);
System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:10
buf.limit(3);//显示3索引位置之后将不可用,包含3
buf.put((byte)0);
buf.put((byte)1);
buf.put((byte)2);
System.out.println("容量:" + buf.capacity() + " 限制:" + buf.limit());//容量:10 限制:3
//buf.put((byte)3);//BufferOverflowException
}
}
public class Demo05Position {
public static void main(String[] args) {
ByteBuffer buf = ByteBuffer.allocate(10);
System.out.println("初始容量:" + buf.capacity() +//10
" 初始限制:" + buf.limit() +//10
" 当前位置:" + buf.position());//0
buf.put((byte) 10);//position = 1
buf.put((byte) 20);//position = 2
buf.put((byte) 30);//position = 3
System.out.println("当前容量:" + buf.capacity() +
" 初始限制:" + buf.limit() +
" 当前位置:" + buf.position());//3
System.out.println(Arrays.toString(buf.array()));//[10, 20, 30, 0, 0, 0, 0, 0, 0, 0]
buf.position(1);//当position改为:1
buf.put((byte) 2);//添加到索引:1
buf.put((byte) 3);//添加到索引:2
System.out.println(Arrays.toString(buf.array()));//[10, 2, 3, 0, 0, 0, 0, 0, 0, 0]
buf.limit(3);
buf.put((byte) 4);//添加到索引:3 BufferOverflowException position
}
}
import java.nio.ByteBuffer;
import java.util.Arrays;
public class Demo06mark {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)0);//position=1
buffer.mark();//设置标记为1
buffer.put((byte)1);//position=2
buffer.put((byte)2);//position=3
System.out.println("position:"+buffer.position());//position:3
System.out.println(Arrays.toString(buffer.array()));//[0, 1, 2, 0, 0, 0, 0, 0, 0, 0]
buffer.reset();//设置position的位置为mark标记的位置
System.out.println("position:"+buffer.position());//position:1
buffer.put((byte)100);
System.out.println(Arrays.toString(buffer.array()));//[0, 100, 2, 0, 0, 0, 0, 0, 0, 0]
}
}
import java.nio.ByteBuffer;
public class Demo07other {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)0);//position=1
buffer.put((byte)1);//position=2
buffer.put((byte)2);//position=3
//- public int remaining():获取position与limit之间的元素数。
System.out.println(buffer.remaining());//7[3-9]
//- public boolean isReadOnly():获取当前缓冲区是否只读。
System.out.println(buffer.isReadOnly());//false 既可以读,有可以写
//- public boolean isDirect():获取当前缓冲区是否为直接缓冲区。
System.out.println(buffer.isDirect());//false
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
System.out.println(buffer2.isDirect());//true
}
}
1).java.nio.channels.Channel(接口):用于 I/O 操作的连接。
2).Channel全部使用Buffer实现读、写。read(ByteBuffer) write(ByteBuffer)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*
java.nio.channels.FileChannel:用于读取、写入、映射和操作文件的通道。
获取对象的方式:
FileInputStream类中的方法:
FileChannel getChannel() 返回与此文件输入流有关的唯一 FileChannel 对象。
FileOutputStream类中的方法:
FileChannel getChannel() 返回与此文件输出流有关的唯一 FileChannel 对象。
成员方法:
int read(ByteBuffer dst) :读取多个字节到ByteBuffer中,相当于与FileInputStream中的方法read(byte[])
int write(ByteBuffer src) : 将ByteBuffer中的数据写入到文件中,相当于FileOutputStream类中的方法write(byte[])
实现步骤:
1.创建FileInputStream对象,绑定要读取的数据源
2.创建FileOutputStream对象,绑定要输出的目的地
3.使用FileInputStream对象中的方法getChannel,获取输入的FileChannel对象
4.使用FileOutputStream对象中的方法getChannel,获取输出的FileChannel对象
5.使用FileChannel对象中的方法read,读取文件
6.使用FileChannel对象中的方法write,把读取到的数据写入到文件中
7.释放资源
*/
public class Demo01FileChannel {
public static void main(String[] args) throws IOException {
//1.创建FileInputStream对象,绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2.创建FileOutputStream对象,绑定要输出的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
//3.使用FileInputStream对象中的方法getChannel,获取输入的FileChannel对象
FileChannel fisChannel = fis.getChannel();
//4.使用FileOutputStream对象中的方法getChannel,获取输出的FileChannel对象
FileChannel fosChannel = fos.getChannel();
//5.使用FileChannel对象中的方法read,读取文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = fisChannel.read(buffer))!=-1){
//使用flip方法缩小limit的范围:最后一次读取不一定读取1024个
buffer.flip();
//6.使用FileChannel对象中的方法write,把读取到的数据写入到文件中
fosChannel.write(buffer);//注:写的时候会修改position的位置
//初始化ByteBuffer的状态
buffer.clear();
}
//7.释放资源
fosChannel.close();
fisChannel.close();
fos.close();
fis.close();
}
}
获取FileChannel需要使用RandomAccessFile类,可以创建流对象的同时设置读写模式
java.io.RandomAccessFile类,可以设置读、写模式的IO流类
构造方法:
RandomAccessFile(String name, String mode)
参数:
String name:要读取的数据源,或者写入的目的地
String mode:设置流的读写模式
"r":只读,必须是小写
"rw":读写,必须是小写
成员方法:
FileChannel getChannel() 返回与此文件关联的唯一 FileChannel 对象。
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) 将此通道的文件区域直接映射到内存中。
参数:
FileChannel.MapMode mode:设置读写的模式
READ_ONLY:只读映射模式。
READ_WRITE:读取/写入映射模式。
long position:文件中的位置,映射区域从此位置开始,一般都是从0开始
size - 要映射的区域大小,就是要复制文件的大小,单位字节
java.nio.MappedByteBuffer:它可以创建“直接缓存区”,将文件的磁盘数据映射到内存。
注意:它最大可以映射:Integer.MAX_VALUE个字节(2G)左右。
eg:磁盘和内存实时映射 硬盘(abc) 内存(abc) 内存修改为(ab) 磁盘也跟着修改(ab)
MappedByteBuffer中的方法:
byte get(int index) 获取缓冲区中指定索引处的字节
ByteBuffer put(int index, byte b) 把字节写入到指定的索引处
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/*
FileChannel结合MappedByteBuffer实现高效读写
注意:
MappedByteBuffer:直接缓存区,最大设置缓冲区大小为2G(2048M)
复制的文件不能超过2G的大小,超过了就需要分块复制
需求:
使用MappedByteBuffer复制2g以下的文件
实现步骤:
1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和只读模式
2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和读写模式
3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel
4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel
5.使用读取文件的FileChannel中的方法size,获取要读取文件的大小(字节)
6.使用读取文件的FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
7.使用写入文件的FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
8.创建for循环,循环size次
9.使用读取文件的直接缓冲区MappedByteBuffer中的方法get,读取指定索引处的字节
10.使用写入文件的直接缓冲区MappedByteBuffer中的方法put,把读取到的字节写入到指定索引处
11.释放资源
*/
public class Demo01MappedByteBuffer {
public static void main(String[] args) throws Exception {
long s = System.currentTimeMillis();
//1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和只读模式(模式必须写小写字母)
RandomAccessFile inRAF = new RandomAccessFile("c:\\748m.rar","r");
//2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和读写模式
RandomAccessFile outRAF = new RandomAccessFile("d:\\748m.rar","rw");
//3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel
FileChannel inChannel = inRAF.getChannel();
//4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel
FileChannel outChannel = outRAF.getChannel();
//5.使用读取文件的FileChannel中的方法size,获取要读取文件的大小(字节)
long size = inChannel.size();
System.out.println("要复制的文件大小:"+size);//要复制的文件大小:785042177
//6.使用读取文件的FileChannel中的方法map,创建读取文件的直接缓冲区MappedByteBuffer
MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
//7.使用写入文件的FileChannel中的方法map,创建写入文件的直接缓冲区MappedByteBuffer
MappedByteBuffer outMap = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
//8.创建for循环,循环size次
for (int i = 0; i < size; i++) {
//9.使用读取文件的直接缓冲区MappedByteBuffer中的方法get,读取指定索引处的字节
byte b = inMap.get(i);
//10.使用写入文件的直接缓冲区MappedByteBuffer中的方法put,把读取到的字节写入到指定索引处
outMap.put(i,b);
}
//11.释放资源
outRAF.close();
inRAF.close();
long e = System.currentTimeMillis();
System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:2224毫秒!
}
}
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/*
使用MappedByteBuffer复制2G以上的文件
分析:
需要分块复制,每块大小512M
*/
public class Demo03MappedByteBuffer {
public static void main(String[] args) throws Exception {
long s = System.currentTimeMillis();
//1.创建读取的RandomAccessFile对象,绑定要读取的数据源和设置只读模式
RandomAccessFile inraf = new RandomAccessFile("c:\\2g.rar","r");
//2.创建写入的RandomAccessFile对象,绑定要写入的目的地和设置读写模式
RandomAccessFile outraf = new RandomAccessFile("d:\\2g.rar","rw");
//3.使用读取的RandomAccessFile对象中的方法getChannel获取可以读取的FileChannel对象
FileChannel inchannel = inraf.getChannel();
//4.使用写入的RandomAccessFile对象中的方法getChannel获取可以写入的FileChannel对象
FileChannel outchannel = outraf.getChannel();
//5.使用FileChannel对象中的方法size,获取要读取文件的大小
long size = inchannel.size();
//System.out.println(size);
long count= 1;//共复制的块数,默认值为1
long startIndex = 0;//复制文件的开始索引
long copySize = size;//复制每块文件大小,默认等于总大小
long everSize = 1024*1024*512;//每块文件大小,默认512M
//判断要复制的文件大小是否大于每块文件的大小
if(size>everSize){
//计算共需要复制的总块数
count = size%everSize==0 ? size/everSize : size/everSize+1;
//第一次复制文件的大小等于每块大小
copySize=everSize;
}
//6.使用读取的FileChannel对象中的方法map,获取读取的MappedByteBuffer对象
MappedByteBuffer inmbb = null;
//7.使用写入的FileChannel对象中的方法map,获取写入的MappedByteBuffer对象
MappedByteBuffer outmbb = null;
//分几块就复制几次
for (int i = 0; i < count; i++) {
inmbb = inchannel.map(FileChannel.MapMode.READ_ONLY, startIndex, copySize);
outmbb = outchannel.map(FileChannel.MapMode.READ_WRITE, startIndex, copySize);
System.out.println("每次复制文件的开始索引:"+startIndex);
System.out.println("每次复制文件的大小:"+copySize);
//复制文件
for (int j = 0; j < copySize; j++) {
byte b = inmbb.get(j);
outmbb.put(j,b);
}
//计算下一块的起始索引
startIndex +=copySize;
//计算下一快复制文件的大小
copySize = size-startIndex>everSize ? everSize : size-startIndex;
}
outraf.close();
inraf.close();
long e = System.currentTimeMillis();
System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:4611毫秒!
}
}
同步阻塞实现
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
实现同步非阻塞的服务器
相关的类:
java.nio.channels.ServerSocketChannel:针对面向流的侦听套接字的可选择通道。
获取对象的方式:
static ServerSocketChannel open() 打开服务器套接字通道。
成员方法:
ServerSocketChannel bind(SocketAddress local) 给服务器绑定端口号
SocketChannel accept() 监听客户端的请求。
SelectableChannel configureBlocking(boolean block) 设置阻塞模式,true:阻塞; false:非阻塞
实现步骤:
1.获取ServerSocketChannel对象
2.使用ServerSocketChannel对象中的bind绑定指定的端口号
3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的bind绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
System.out.println("服务器等待客户端连接...");
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("有客户端连接服务器...");
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
实现同步非阻塞的客户端
相关的类:
java.nio.channels.SocketChannel
获取对象的方法:
static SocketChannel open() 打开套接字通道。
成员方法:
boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
configureBlocking(boolean block)设置阻塞模式,true:阻塞; false:非阻塞
实现步骤:
1.获取SocketChannel对象
2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.获取SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
服务器同步非阻塞实现
public class TCPServer {
public static void main(String[] args) throws IOException {
//设置服务器非阻塞模式
serverSocketChannel.configureBlocking(false);
System.out.println("服务器等待客户端连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
System.out.println("有客户端连接服务器...");
}
}
服务器轮询监听客户端
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的bind绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
//设置服务器非阻塞模式
serverSocketChannel.configureBlocking(false);
//轮询监听客户端
while (true){
System.out.println("服务器等待客户端连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
//对象SocketChannel进行判断,没有客户端连接返回null
if(socketChannel!=null){
System.out.println("有客户端连接服务器...");
break;//结束轮询
}else{
System.out.println("没有客户端连接服务器,休息2秒,干点其他事,继续轮询...");
Thread.sleep(2000);
}
}
}
}
执行结果:
服务器等待客户端连接...
没有客户端连接服务器,休息2秒,继续轮询...
服务器等待客户端连接...
没有客户端连接服务器,休息2秒,继续轮询...
服务器等待客户端连接...
有客户端连接服务器...
客户端实现同步非阻塞:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
实现同步非阻塞的客户端
相关的类:
java.nio.channels.SocketChannel
获取对象的方法:
static SocketChannel open() 打开套接字通道。
成员方法:
boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
configureBlocking(boolean block)设置阻塞模式,true:阻塞; false:非阻塞
实现步骤:
1.获取SocketChannel对象
2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
注意:
客户端使用connect连接服务器
客户端设置为阻塞模式,会多次尝试连接,连接成功connect方法返回true连接不成功抛出连接异常
客户端设置为非阻塞模式,只会连接一次服务器,connect方法返回false
*/
public class TCPClient {
public static void main(String[] args) throws InterruptedException {
//阻塞轮询连接服务器
while (true){
try{
//1.获取SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(true);//默认true:阻塞
//socketChannel.configureBlocking(false);//false:非阻塞
//2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
System.out.println("连接服务器成功,结束轮询...");
break;//结束轮询
}catch (Exception e){
System.out.println("连接服务器失败,休息2秒,干点别的...");
Thread.sleep(2000);
}
}
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
客户端给服务器发送数据,接收服务器回写的数据
int write(ByteBuffer src) 给服务器发送数据
long read(ByteBuffer[] dsts) 读取服务器发送的数据
*/
public class TCPClient {
public static void main(String[] args) throws InterruptedException {
//ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
//System.out.println("容量:"+buffer.capacity());//15
//System.out.println("索引:"+buffer.position());//0
//System.out.println("限定:"+buffer.limit());//15
//阻塞轮询连接服务器
while (true){
try{
//1.获取SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(true);//默认true:阻塞
//socketChannel.configureBlocking(false);//false:非阻塞
//2.使用SocketChannel对象中的方法connect根据服务器的ip地址和端口号连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
//int write(ByteBuffer src) 给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
System.out.println("容量:"+buffer.capacity());//15
System.out.println("索引:"+buffer.position());//0
System.out.println("限定:"+buffer.limit());//15
socketChannel.write(buffer);
//long read(ByteBuffer[] dsts) 读取服务器发送的数据
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
int read = socketChannel.read(buffer2);
buffer2.flip();//缩小limit的范围
String msg = new String(buffer2.array(),0,buffer2.limit());
System.out.println("客户端收到服务器发送的数据:"+msg);
break;//结束轮询
}catch (Exception e){
System.out.println("连接服务器失败,休息2秒,干点别的...");
Thread.sleep(2000);
}
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器接收客户端发送的数据,给客户端回写数据
long read(ByteBuffer[] dsts) 读取客户端发送的数据
int write(ByteBuffer src) 给客户端发送数据
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.获取ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的bind绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用ServerSocketChannel对象中的accept方法监听客户端的请求
//设置服务器非阻塞模式
serverSocketChannel.configureBlocking(false);
//轮询监听客户端
while (true){
System.out.println("服务器等待客户端连接...");
SocketChannel socketChannel = serverSocketChannel.accept();//默认阻塞
//对象SocketChannel进行判断,没有客户端连接返回null
if(socketChannel!=null){
System.out.println("有客户端连接服务器...");
//long read(ByteBuffer[] dsts) 读取客户端发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
buffer.flip();//缩小limit的范围
String msg = new String(buffer.array(),0,buffer.limit());
System.out.println("服务器接收到客户端发送的信息:"+msg);
//int write(ByteBuffer src) 给客户端发送数据
ByteBuffer buffer2 = ByteBuffer.wrap("收到谢谢".getBytes());
socketChannel.write(buffer2);
break;//结束轮询
}else{
System.out.println("没有客户端连接服务器,休息2秒,干点其他事,继续轮询...");
Thread.sleep(2000);
}
}
}
}
选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。
如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。
使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
/*
选择器Selector_服务器端实现多路注册
java.nio.channels.Selector:SelectableChannel对象的多路复用器。
获取Selector对象的方式:
static Selector open() 打开选择器。
注册Channel到Selector:ServerSocketChannel中的方法
SelectionKey register(Selector,SelectionKey.OP_READ);
参数:
SelectionKey:意思是在通过Selector监听Channel时对什么事件感兴趣
接收就绪--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
实现步骤:
1.创建3个ServerSocketChannel对象
2.分别给3个ServerSocketChannel对象绑定不同的端口号
3.设置3个ServerSocketChannel对象为非阻塞
4.获取Selector对象
5.把3个ServerSocketChannel对象注册到Selector对象
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建3个ServerSocketChannel对象
ServerSocketChannel channel = ServerSocketChannel.open();
ServerSocketChannel channe2 = ServerSocketChannel.open();
ServerSocketChannel channe3 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel对象绑定不同的端口号
channel.bind(new InetSocketAddress(7777));
channe2.bind(new InetSocketAddress(8889));
channe3.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞
channel.configureBlocking(false);
channe2.configureBlocking(false);
channe3.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.把3个ServerSocketChannel对象注册到Selector对象
channel.register(selector, SelectionKey.OP_ACCEPT);
channe2.register(selector, SelectionKey.OP_ACCEPT);
channe3.register(selector, SelectionKey.OP_ACCEPT);
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Set;
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建3个ServerSocketChannel对象
ServerSocketChannel channel = ServerSocketChannel.open();
ServerSocketChannel channe2 = ServerSocketChannel.open();
ServerSocketChannel channe3 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel对象绑定不同的端口号
channel.bind(new InetSocketAddress(7777));
channe2.bind(new InetSocketAddress(8888));
channe3.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞
channel.configureBlocking(false);
channe2.configureBlocking(false);
channe3.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.把3个ServerSocketChannel对象注册到Selector对象
channel.register(selector, SelectionKey.OP_ACCEPT);
channe2.register(selector, SelectionKey.OP_ACCEPT);
channe3.register(selector, SelectionKey.OP_ACCEPT);
//循环接收客户端的连接
while (true){
//Selector的select()方法:获取连接客户端的数量,没有获取到客户端的连接对象,此方法将阻塞
int count = selector.select();
System.out.println("连接客户端的数量:"+count);
//Selector的keys()方法:获取已经注册的通道集合
Set<SelectionKey> keys = selector.keys();
System.out.println("已经注册的通道的数量:"+keys.size());
//Selector的selectedKeys()方法:获取已经连接的通道集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("已经连接的通道的数量:"+selectionKeys.size());
//获取完一个客户端的连接,睡眠2秒
Thread.sleep(2000);
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
创建两个线程,连接服务器
*/
public class TCPClient {
public static void main(String[] args) {
//启动两个线程,分别连接7777和8888端口
new Thread(()->{
while (true) {
try (SocketChannel socket = SocketChannel.open()) {
System.out.println("7777客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 7777));
System.out.println("7777客户端连接成功....");
break;
} catch (IOException e) {
System.out.println("7777异常重连");
}
}
}).start();
new Thread(()->{
while (true) {
try (SocketChannel socket = SocketChannel.open()) {
System.out.println("8888客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 8888));
System.out.println("8888客户端连接成功....");
break;
} catch (IOException e) {
System.out.println("8888异常重连");
}
}
}).start();
}
}
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.Iterator;
import java.util.Set;
/*
选择器Selector_服务器端实现多路注册
java.nio.channels.Selector:SelectableChannel对象的多路复用器。
获取Selector对象的方式:
static Selector open() 打开选择器。
注册Channel到Selector:ServerSocketChannel中的方法
SelectionKey register(Selector,SelectionKey.OP_READ);
参数:
SelectionKey:意思是在通过Selector监听Channel时对什么事件感兴趣
接收就绪--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
实现步骤:
1.创建3个ServerSocketChannel对象
2.分别给3个ServerSocketChannel对象绑定不同的端口号
3.设置3个ServerSocketChannel对象为非阻塞
4.获取Selector对象
5.把3个ServerSocketChannel对象注册到Selector对象
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建3个ServerSocketChannel对象
ServerSocketChannel channell = ServerSocketChannel.open();
ServerSocketChannel channel2 = ServerSocketChannel.open();
ServerSocketChannel channel3 = ServerSocketChannel.open();
//2.分别给3个ServerSocketChannel对象绑定不同的端口号
channell.bind(new InetSocketAddress(7777));
channel2.bind(new InetSocketAddress(8888));
channel3.bind(new InetSocketAddress(9999));
//3.设置3个ServerSocketChannel对象为非阻塞
channell.configureBlocking(false);
channel2.configureBlocking(false);
channel3.configureBlocking(false);
//4.获取Selector对象
Selector selector = Selector.open();
//5.把3个ServerSocketChannel对象注册到Selector对象
channell.register(selector, SelectionKey.OP_ACCEPT);
channel2.register(selector, SelectionKey.OP_ACCEPT);
channel3.register(selector, SelectionKey.OP_ACCEPT);
//循环接收客户端的连接
while (true){
//Selector的select()方法:获取连接客户端的数量,没有获取到客户端的连接对象,此方法将阻塞
System.out.println("服务器等待客户端连接....");
int count = selector.select();
System.out.println("连接客户端的数量:"+count);
//Selector的selectedKeys()方法:获取已经连接的通道集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("已经连接的通道的数量:"+selectionKeys.size());
//处理Selector监听到的事件,遍历Set集合,获取每一个SelectionKey对象
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
SelectionKey selectionKey = it.next();
//获取SelectionKey中封装的ServerSocketChannel对象
ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
//获取此通道监听的端口号
System.out.println("监听的端口:"+channel.getLocalAddress());
//处理accept事件
SocketChannel socketChannel = channel.accept();
//读取SocketChannel发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socketChannel.read(buffer);
buffer.flip();//缩小limit的范围
String msg = new String(buffer.array(),0,buffer.limit());
System.out.println("接收到客户端的数据为:"+msg);
//处理完SelectionKey监听到的事件,要移除处理完的SelectionKey,防止空指针异常
it.remove();
}
//获取完一个客户端的连接,睡眠2秒
Thread.sleep(2000);
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
创建两个线程,连接服务器
*/
public class TCPClient {
public static void main(String[] args) {
//启动两个线程,分别连接7777和8888端口
new Thread(()->{
while (true) {
try (SocketChannel socket = SocketChannel.open()) {
System.out.println("7777客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 7777));
System.out.println("7777客户端连接成功....");
//给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器,我是7777端口的客户端!".getBytes());
socket.write(buffer);
break;
} catch (IOException e) {
System.out.println("7777异常重连");
}
}
}).start();
new Thread(()->{
while (true) {
try (SocketChannel socket = SocketChannel.open()) {
System.out.println("8888客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 8888));
System.out.println("8888客户端连接成功....");
//给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器,我是8888端口的客户端!".getBytes());
socket.write(buffer);
break;
} catch (IOException e) {
System.out.println("8888异常重连");
}
}
}).start();
}
}
JDK7新增的:AsynchronousIO:异步、非阻塞IO
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/*
创建AIO的服务器端:
相关的类:
java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
获取对象的方法:
static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
成员方法:
void accept(A attachment, CompletionHandler handler)
参数:
A attachment:附件,可以传递null
CompletionHandler handler:事件处理类,用于处理accept方法监听到的事件
CompletionHandler也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
CompletionHandler接口中的方法:
void completed(V result, A attachment);客户端连接成功执行的方法
void failed(Throwable exc, A attachment);客户端连接失败执行的方法
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建异步非阻塞服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
//给AsynchronousServerSocketChannel对象绑定指定的端口号
channel.bind(new InetSocketAddress(8888));
System.out.println("accept方法开始执行....");
//使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
//客户端连接成功执行的方法
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("有客户端连接成功!");
}
//客户端连接失败执行的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("有客户端连接失败!");
}
});
System.out.println("accept方法执行结束....");
while (true){
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousSocketChannel;
/*
创建AIO的客户端:
和客户端相关的类:
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
static AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
Future connect(SocketAddress remote) 连接此频道。
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//创建异步非阻塞客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//使用AsynchronousSocketChannel对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建异步非阻塞服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
//给AsynchronousServerSocketChannel对象绑定指定的端口号
channel.bind(new InetSocketAddress(8888));
System.out.println("accept方法开始执行....");
//使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
//客户端连接成功执行的方法
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("有客户端连接成功!");
//获取客户端中发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = result.read(buffer);//同步非阻塞的read方法
Integer len = null;
try {
len = future.get();//读取客户端中发送的信息
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
String msg = new String(buffer.array(),0,len);
System.out.println("服务器读取到客户端的信息:"+msg);
}
//客户端连接失败执行的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("有客户端连接失败!");
}
});
System.out.println("accept方法执行结束....");
while (true){
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
/*
创建AIO的客户端:
和客户端相关的类:
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
static AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
Future connect(SocketAddress remote) 连接此频道。
Future read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
Future write(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//创建异步非阻塞客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//使用AsynchronousSocketChannel对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//连接服务器成功,给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
socketChannel.write(buffer);
}
}
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建异步非阻塞的服务器
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//给服务器绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(7777));
System.out.println("开始执行accpet方法....");
//使用accept监听客户端的请求:CompletionHandler叫回调函数,有客户端请求服务器,就会执行CompletionHandler里边的方法
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接成功执行的方法!");
//使用read方法读取客户端发送的数据
//Future read(ByteBuffer dst)
ByteBuffer buffer = ByteBuffer.allocate(1024);
//Future future = result.read(buffer);//read是一个阻塞的方法
/*
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) //是一个非阻塞的方法
参数
dst - 要传输字节的缓冲区
timeout - 完成I / O操作的最长时间
unit - timeout参数的时间单位
attachment - 要附加到I / O操作的对象; 可以是null
handler - 消费结果的处理程序
*/
result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("读取客户端发送数据成功执行的方法");
buffer.flip();
String msg = new String(buffer.array(),0,buffer.limit());
System.out.println("服务器接收到客户端的信息:"+msg);
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("读取客户端发送数据失败,执行的方法!");
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接失败执行的方法!");
}
});
System.out.println("结束执行accpet方法....");
//去做别的事情
while (true){
}
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//创建异步非阻塞客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//使用AsynchronousSocketChannel对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//连接服务器成功,给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
Thread.sleep(1000*11);
socketChannel.write(buffer);
}
}
执行结果:
accept方法开始执行....
accept方法执行结束....
有客户端连接成功!
read方法开始执行.....
read方法执行结束.....
服务器读取到客户端的信息:你好服务器