一、NIO
1 - NIO概述
什么是NIO :No Blocking IO,非阻塞型IO
Java NIO全称java non-blocking IO, 是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的 什么是BIO :Blocking IO,阻塞型IO
当客户端有连接请求时,服务端就会需要启动一个线程对客户端的连接进行数据读写,如果客户端不进行读写了,那么这个线程也会等着,这样就会造成阻塞 阻塞IO的弊端 :在等待的过程中,什么事也做不了非阻塞IO的好处 :不需要一直等待,当一切就绪了再去做NIO与BIO的区别
BIO是阻塞的,NIO是非阻塞的 BIO是面向流的,NIO是面向缓冲区的 BIO中数据传输是单向的,NIO中的缓冲区是双向的
2 - NIO三大模块
二、NIO缓冲区
1 - NIO缓冲区创建
方法名 说明 static ByteBuffer allocate(长度) 创建byte类型的缓冲区 static ByteBuffer wrap(byte[] array) 创建一个有内容的byte类型缓冲区
private static void method1 ( ) {
ByteBuffer byteBuffer1 = ByteBuffer . allocate ( 5 ) ;
for ( int i = 0 ; i < 5 ; i++ ) {
System . out. println ( byteBuffer1. get ( ) ) ;
}
}
}
private static void method2 ( ) {
byte [ ] bytes = { 97 , 98 , 99 } ;
ByteBuffer byteBuffer2 = ByteBuffer . wrap ( bytes) ;
for ( int i = 0 ; i < 3 ; i++ ) {
System . out. println ( byteBuffer2. get ( ) ) ;
}
ByteBuffer wrap = ByteBuffer . wrap ( "aaa" . getBytes ( ) ) ;
for ( int i = 0 ; i < 3 ; i++ ) {
System . out. println ( wrap. get ( ) ) ;
}
}
2 - NIO缓冲区添加数据
方法介绍
public class Test {
public static void main ( String [ ] args) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( 10 ) ;
System . out. println ( byteBuffer. position ( ) ) ;
System . out. println ( byteBuffer. limit ( ) ) ;
System . out. println ( byteBuffer. capacity ( ) ) ;
System . out. println ( "-------------------------------" ) ;
byteBuffer. put ( ( byte ) 97 ) ;
System . out. println ( byteBuffer. position ( ) ) ;
System . out. println ( byteBuffer. limit ( ) ) ;
System . out. println ( byteBuffer. capacity ( ) ) ;
System . out. println ( "-------------------------------" ) ;
byteBuffer. put ( "aaa" . getBytes ( ) ) ;
System . out. println ( byteBuffer. position ( ) ) ;
System . out. println ( byteBuffer. limit ( ) ) ;
System . out. println ( byteBuffer. capacity ( ) ) ;
System . out. println ( "-------------------------------" ) ;
byteBuffer. position ( 1 ) ;
byteBuffer. limit ( 5 ) ;
System . out. println ( byteBuffer. position ( ) ) ;
System . out. println ( byteBuffer. limit ( ) ) ;
System . out. println ( byteBuffer. capacity ( ) ) ;
System . out. println ( "-------------------------------" ) ;
byteBuffer. put ( "23" . getBytes ( ) ) ;
System . out. println ( byteBuffer. remaining ( ) ) ;
System . out. println ( byteBuffer. hasRemaining ( ) ) ;
}
}
3 - NIO缓冲区获取数据
方法名 介绍 flip() 切换读写模式(写à读) get() 读一个字节 get(byte[] dst) 读多个字节 get(int index) 读指定索引的字节 rewind() 将position设置为0,可以重复读 clear() 数据读写完毕(读->写) array() 将缓冲区转换成字节数组返回
flip的理解 :Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式
也就是说调用flip()之后,读/写指针position指到缓冲区头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)
private static void method1 ( ) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( 10 ) ;
byteBuffer. put ( "abc" . getBytes ( ) ) ;
byteBuffer. flip ( ) ;
while ( byteBuffer. limit ( ) != byteBuffer. position ( ) ) {
System . out. println ( ( char ) byteBuffer. get ( ) ) ;
}
}
private static void method2 ( ) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( 10 ) ;
byteBuffer. put ( "abc" . getBytes ( ) ) ;
byteBuffer. flip ( ) ;
byte [ ] bytes = new byte [ byteBuffer. limit ( ) ] ;
byteBuffer. get ( bytes) ;
System . out. println ( new String ( bytes) ) ;
}
private static void method3 ( ) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( 10 ) ;
byteBuffer. put ( "abc" . getBytes ( ) ) ;
byteBuffer. flip ( ) ;
System . out. println ( ( char ) byteBuffer. get ( 0 ) ) ;
byteBuffer. rewind ( ) ;
for ( int i = 0 ; i < byteBuffer. limit ( ) ; i++ ) {
System . out. println ( ( char ) byteBuffer. get ( ) ) ;
}
byteBuffer. clear ( ) ;
byteBuffer. put ( "qqq" . getBytes ( ) ) ;
byte [ ] bytes = byteBuffer. array ( ) ;
System . out. println ( new String ( bytes) ) ;
}
flip和clear总结
获取缓冲区里面数据之前,需要调用flip方法 再次写数据之前,需要调用clear方法(但是数据还未消失,等再次写入数据,被覆盖了才会消失)
三、NIO通道
1 - NIO通道简单实现
NIO通道
服务端通道:只负责建立,不负责传递数据 客户端通道:建立并将数据传递给服务端 缓冲区:客户端发送的数据都在缓冲区中 服务端通道内部创建出来的客户端通道:相当于客户端通道的延伸用来传递数据 NIO通道客户端简单实现
①.打开通道 ②.指定ip和端口号 ③.写出数据 ④.释放资源
public class NIOClient {
public static void main ( String [ ] args) throws IOException {
SocketChannel socketChannel = SocketChannel . open ( ) ;
socketChannel. connect ( new InetSocketAddress ( "127.0.0.1" , 10000 ) ) ;
ByteBuffer byteBuffer = ByteBuffer . wrap ( "一点寒毛先制" . getBytes ( ) ) ;
socketChannel. write ( byteBuffer) ;
socketChannel. close ( ) ;
}
}
NIO通道服务端简单实现
①.打开一个服务端通道 ②.绑定对应的端口号 ③.通道默认是阻塞的,需要设置为非阻塞 ④.此时没有门卫大爷,所以需要经常看一下有没有连接发过来没 ⑤.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸 ⑥.获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中 ⑦.给客户端回写数据 ⑧.释放资源
public class NIOServer {
public static void main ( String [ ] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel . open ( ) ;
serverSocketChannel. bind ( new InetSocketAddress ( 10000 ) ) ;
serverSocketChannel. configureBlocking ( false ) ;
while ( true ) {
SocketChannel socketChannel = serverSocketChannel. accept ( ) ;
if ( socketChannel != null ) {
ByteBuffer byteBuffer = ByteBuffer . allocate ( 1024 ) ;
int len = socketChannel. read ( byteBuffer) ;
System . out. println ( new String ( byteBuffer. array ( ) , 0 , len) ) ;
socketChannel. close ( ) ;
}
}
}
}
2 - NIO通道-服务器回写数据
public class Clinet {
public static void main ( String [ ] args) throws IOException {
SocketChannel socketChannel = SocketChannel . open ( ) ;
socketChannel. connect ( new InetSocketAddress ( "127.0.0.1" , 10000 ) ) ;
ByteBuffer byteBuffer1 = ByteBuffer . wrap ( "吃俺老孙一棒棒" . getBytes ( ) ) ;
socketChannel. write ( byteBuffer1) ;
System . out. println ( "数据已经写给服务器" ) ;
ByteBuffer byteBuffer2 = ByteBuffer . allocate ( 1024 ) ;
int len;
while ( ( len = socketChannel. read ( byteBuffer2) ) != - 1 ) {
System . out. println ( "客户端接收回写数据" ) ;
byteBuffer2. flip ( ) ;
System . out. println ( new String ( byteBuffer2. array ( ) , 0 , len) ) ;
byteBuffer2. clear ( ) ;
}
socketChannel. close ( ) ;
}
}
public class Sever {
public static void main ( String [ ] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel . open ( ) ;
serverSocketChannel. bind ( new InetSocketAddress ( 10000 ) ) ;
serverSocketChannel. configureBlocking ( false ) ;
while ( true ) {
SocketChannel socketChannel = serverSocketChannel. accept ( ) ;
if ( socketChannel != null ) {
System . out. println ( "此时有客户端来连接了" ) ;
socketChannel. configureBlocking ( false ) ;
ByteBuffer byteBuffer1 = ByteBuffer . allocate ( 1024 ) ;
int len;
while ( ( len = socketChannel. read ( byteBuffer1) ) > 0 ) {
System . out. println ( "服务端接收发送数据" ) ;
byteBuffer1. flip ( ) ;
System . out. println ( new String ( byteBuffer1. array ( ) , 0 , len) ) ;
byteBuffer1. clear ( ) ;
}
System . out. println ( "接收数据完毕,准备开始往客户端回写数据" ) ;
ByteBuffer byteBuffer2 = ByteBuffer . wrap ( "哎哟,真疼啊!!!" . getBytes ( ) ) ;
socketChannel. write ( byteBuffer2) ;
socketChannel. close ( ) ;
}
}
}
}
四、NIO选择器
NIO选择器概述 :从底层看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。单个线程通过Selector可以管理多个SelectableChannel,实际应用中管理多个请求连接。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源,比如内存,因此使用的线程越少越好。
选择器对象 :
Selector:选择器对象 SelectionKey:绑定的key SelectableChannel:能使用选择器的通道
SocketChannel ServerSocketChannel NIO选择器改写服务端
public class Server {
public static void main ( String [ ] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel . open ( ) ;
serverSocketChannel. bind ( new InetSocketAddress ( 10000 ) ) ;
serverSocketChannel. configureBlocking ( false ) ;
Selector selector = Selector . open ( ) ;
serverSocketChannel. register ( selector, SelectionKey . OP_ACCEPT) ;
while ( true ) {
System . out. println ( "11" ) ;
int count = selector. select ( ) ;
System . out. println ( "222" ) ;
if ( count != 0 ) {
System . out. println ( "有客户端来连接了" ) ;
Set < SelectionKey > selectionKeys = selector. selectedKeys ( ) ;
Iterator < SelectionKey > iterator = selectionKeys. iterator ( ) ;
while ( iterator. hasNext ( ) ) {
SelectionKey selectionKey = iterator. next ( ) ;
if ( selectionKey. isAcceptable ( ) ) {
ServerSocketChannel ssc = ( ServerSocketChannel ) selectionKey. channel ( ) ;
SocketChannel socketChannel = ssc. accept ( ) ;
socketChannel. configureBlocking ( false ) ;
socketChannel. register ( selector, SelectionKey . OP_READ) ;
} else if ( selectionKey. isReadable ( ) ) {
SocketChannel socketChannel = ( SocketChannel ) selectionKey. channel ( ) ;
ByteBuffer byteBuffer1 = ByteBuffer . allocate ( 1024 ) ;
int len;
while ( ( len = socketChannel. read ( byteBuffer1) ) > 0 ) {
byteBuffer1. flip ( ) ;
System . out. println ( new String ( byteBuffer1. array ( ) , 0 , len) ) ;
byteBuffer1. clear ( ) ;
}
socketChannel. write ( ByteBuffer . wrap ( "哎哟喂好疼啊!!!" . getBytes ( ) ) ) ;
socketChannel. close ( ) ;
}
iterator. remove ( ) ;
}
}
}
}
}
public class Clinet {
public static void main ( String [ ] args) throws IOException {
SocketChannel socketChannel = SocketChannel . open ( ) ;
socketChannel. connect ( new InetSocketAddress ( "127.0.0.1" , 10000 ) ) ;
ByteBuffer byteBuffer1 = ByteBuffer . wrap ( "吃俺老孙一棒棒" . getBytes ( ) ) ;
socketChannel. write ( byteBuffer1) ;
System . out. println ( "数据已经写给服务器" ) ;
ByteBuffer byteBuffer2 = ByteBuffer . allocate ( 1024 ) ;
int len;
while ( ( len = socketChannel. read ( byteBuffer2) ) != - 1 ) {
System . out. println ( "客户端接收回写数据" ) ;
byteBuffer2. flip ( ) ;
System . out. println ( new String ( byteBuffer2. array ( ) , 0 , len) ) ;
byteBuffer2. clear ( ) ;
}
socketChannel. close ( ) ;
}
}