学习NIO之前我们先看下面几个知识点:
1.阻塞和非阻塞:阻塞和非阻塞是进程在访问数据的时候,数据内是否准备就绪的一种处理方式,
当数据没有准备的时候:
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,数据没有准备好的时候直接返回,
不需要等待,数据有的时候,也直接返回。
2.同步和异步的方式
同步和异步都是基于应用程序和操作系统处理IO时间锁采用的方式,比如同步应用程序直接参与IO读写的操作。
2.1异步:所有的IO读写交给操作系统去处理同步的方式在处理IO事件的时候,
必须阻塞在某个方法上面等待我们的IO时间完成(阻塞IO事件或则通过轮询IO时间的方式)
对于异步来说,所有的IO读写都交给了操作系统,这个时候,我们可以去其他的事情,并不
需要去完成真正的IO操作,当操作完成IO后给我们的应用程序一个通知就可以。
2.2同步:1.阻塞到IO事件,阻塞到read或则write。这个时候我们就不能完全做自己的事情。
让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。
3.IO事件的轮询,多路复用技术(select模式)
3.1读写事件交给一个单独线程来处理,这个完成IO事件的注册功能,还有就是不断的去轮询。
我们的读写缓冲区,看是否有数据准备好。通知我们相应的读写线程。这样的话,以前的读写线程就可以
做其他的事情,这个时候阻塞的不是所有的IO线程。阻塞的select这个线程。
Cilent ----- ----> Select管家 ---- ------> Server
解释:当客人来的时候,就给管家说,我来了,管家得到这个注册信息后,给Server说,我这里
有一个或则多个客人,Server你去给某人A这件东西,给另外人B这样东西,这个时候客人是可以
去做自己的事情,比如看看花园等等,当管家知道Server给他任务后,他就去找对应的某人,告诉他
Server给他某样东西。(根据我们的注册信息)
3.2Java IO模型
BIO :jdk1.4以前我们使用都是BIO,阻塞IO。阻塞到我们的读写方法,阻塞到线程上提供线程
对于线程的开销本来就是性能的浪费。
NIO:jdk1.4 Linux多路复用技术(selet模式),实现IO事件的轮询方式 :同步非阻塞的模式
对于这种方式目前是主流的网络通信模式。
Mina,netty mina2.0 netty5.0–网络通信框架。比我们直接写NIO要容易些,并且代码可读性更好。
AIO:jdk1.7后(NIO2)才是实现真正的异步Aio,学习linux epoll模式
Aio使用的比较少。
小结: 1.BIO阻塞的IO
2.NIO select+非阻塞 实现同步非阻塞
3.AIOA 异步非阻塞IO
4.NIO AIO 原理的解读
对于网络通信而言NIO,AIO并没有改变网络通信的基本步骤,只是在其原来的基础上(serverscoket,socket)做了一个改进。
3.3Socket <–建立连接需要三次握手–> ServerSocket
对于三次握手的方式,建立稳定的连接性能开销比较大,解决方案从思想上来说比较容易
就是减少连接的次数,对我们的读写通信管道进行一个抽象。
对于读和写采用抽象的管道的概念:
3.4Channel:是一个在TCP连接之间抽象,一个TCP连接可以对应多个管道,而不是以前的方式只有
一个通信信道,减少了TCP连接的次数。
UDP:采用相同方式,也是抽象成管道。
3.5NIO模型原理:
通过我们的selctor(选择器)就相对于一个管家,管理所有的IO事件,
connection accept 客户端和服务端的读写。–IO事件都交给我们的selctor管理
selctor(选择器)如何进行管理IO事件
当IO事件注册给我们的选择器的时候,选择器会给他们分配一个ket(可以简单的理解成一个事件的标签)
当我们的IO事件完成后悔通过key值来找到相应的管道,然后通过管道发送数据和接受数据等操作。
对于读和写数据的缓冲区:
通过byteBuffer类,提供很多读写的方法:put() get()方法
服务端:ServerSocketChannel
客户端:SocketChanner
选择器:Selector selector=Select.open();这样我们就打开了我们的选择器
4.Selectionkey:可以通过它来判断IO事件是否已经就绪。
key.Accptable:是否可以接受客户端的连接
key.connctionable:是否可以连接服务端
key.isreadable():缓冲区是否可读
key.iswriteable:缓冲区是否可写
5.如何获得事件keys值
Selecionkey keys=selector.selectedkeys();
6.如何注册
channel.regist(Selctor,Selectionkey_OP_Write);
channel.regist(Selctor,Selectionkey_OP_Read);
7.AIO
服务端:AsynchronousServerSocketChannel
客户端:AsynchronousSocketChannel
用户处理器:CompletionHandler接口,这个接口实现应用程序向操作系统发起IO请求
当完成后去处理具体的逻辑,否则做自己该做的事情。
下面来看一下应用:
服务端程序代码:
package com.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Set;
import java.util.Iterator;
public class NIOServer {
private int flag=1;
//缓冲区大小
private int blockSize=4096;
//发送数据缓冲区
private ByteBuffer sendbuffer=ByteBuffer.allocate(blockSize);
//接受数据缓冲区
private ByteBuffer receivebuffer=ByteBuffer.allocate(blockSize);
//选择器
private Selector selector;
//初始化服务端对象
public NIOServer(int port) throws IOException {
//ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//设置是否 为非阻塞
serverSocketChannel.configureBlocking(false);
//获取Socket
ServerSocket serverSocket=serverSocketChannel.socket();
//绑定ip和端口
serverSocket.bind(new InetSocketAddress(port));
//打开选择器
selector=Selector.open();
//注册 打开与接受客户端的连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server start->"+port);
}
//监听客服端连接
public void listen() throws IOException {
while(true) {
//轮询选择器
selector.select();
//遍历选择器 返回一个Set集合
Set selectedKeys=selector.selectedKeys();
Iterator itetor=selectedKeys.iterator();
while(itetor.hasNext()) {
SelectionKey selectionKey=itetor.next();
itetor.remove();
//业务逻辑
handleKey(selectionKey);
}
}
}
public void handleKey(SelectionKey selectionKey) throws IOException {
//服务端
ServerSocketChannel server=null;
//客户端
SocketChannel client=null;
String reciveText;
String sendText;
int count=0;
//是否与客服端连接
if(selectionKey.isAcceptable()) {
server=(ServerSocketChannel)selectionKey.channel();
client=server.accept();
client.configureBlocking(false);
//读事件
client.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()) {
client=(SocketChannel) selectionKey.channel();
//内容读到缓冲区
receivebuffer.clear();
count=client.read(receivebuffer);
if(count>0) {
//从缓存中取出内容体
reciveText=new String(receivebuffer.array(),0,count);
System.out.println("服务端接受到客服端的信息:"+reciveText);
//注册 写事件
client.register(selector,SelectionKey.OP_WRITE);
}
selectionKey.cancel();
}else if(selectionKey.isWritable()) {
//清除缓冲区
sendbuffer.clear();
client=(SocketChannel) selectionKey.channel();
sendText="msg send to client"+flag++;
sendbuffer.put(sendText.getBytes());
//flip()使缓冲区为一系列新的通道写入或相对获取 操作做好准备
sendbuffer.flip();
//发送数据
client.write(sendbuffer);
System.out.println("服务端发送数据给客服端:"+sendText);
}
}
public static void main(String[] args) throws IOException {
int port=6005;
NIOServer server=new NIOServer(port);
server.listen();
}
}
客户端代码:
package com.nio;
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.SocketChannel;
import java.util.Set;
import java.util.Iterator;
public class NIOClient {
private static int flag=1;
//缓冲区大小
private static int blockSize=4096;
//发送数据缓冲区
private static ByteBuffer sendbuffer=ByteBuffer.allocate(blockSize);
//接受数据缓冲区
private static ByteBuffer receivebuffer=ByteBuffer.allocate(blockSize);
//设置ip和地址
private final static InetSocketAddress serverAddress=new InetSocketAddress("127.0.0.1", 6005);
public static void main(String[] args) throws IOException {
//打开服务端Channel
SocketChannel socketChannel=SocketChannel.open();
//设置为非阻塞
socketChannel.configureBlocking(false);
//连接这个通道的插座
socketChannel.connect(serverAddress);
//打开选择器
Selector selector=Selector.open();
//把channel注册到选择器上 是否与服务端连接
socketChannel.register(selector, SelectionKey.OP_CONNECT);
Set selectionKeys;
Iterator iterator;
SelectionKey selectionKey = null;
SocketChannel client = null;
int count=0;
//接受数据
String receiveTest;
//发送数据
String sendText;
while(true) {
//
selector.select();
selectionKeys=selector.selectedKeys();
iterator=selectionKeys.iterator();
while(iterator.hasNext()) {
selectionKey=iterator.next();
//是否可以连接服务器
if(selectionKey.isConnectable()) {
System.out.println("clent connet");
client=(SocketChannel) selectionKey.channel();
System.out.println("starting 。。。。。");
//告诉是否该通道的连接操作正在进行中。
if(client.finishConnect()) {
// //完成了一个套接字通道的连接的过程。
// client.finishConnect();
System.out.println("客服端完成连接操作");
//清空缓存
sendbuffer.clear();
//将内容写到缓存
sendbuffer.put("Hello ,Server".getBytes());
//flip()使缓冲区为一系列新的通道写入或相对获取 操作做好准备
sendbuffer.flip();
//从给定的缓冲区写入该通道的一个字节序列。
client.write(sendbuffer);
client.register(selector, SelectionKey.OP_READ);
}
//
} if(selectionKey.isWritable()) {
sendbuffer.clear();
client=(SocketChannel) selectionKey.channel();
sendText="Msg send to Server"+flag++;
sendbuffer.put(sendText.getBytes());
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客服端发送数据给服务端"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
if(selectionKey.isReadable()) {
client=(SocketChannel) selectionKey.channel();
receivebuffer.clear();
client.read(receivebuffer);
count=client.read(receivebuffer);
if(count>0) {
receiveTest=new String(receivebuffer.array(), 0, count);
System.out.println("客户端接受到服务端的数据"+receiveTest);
}
client.register(selector,SelectionKey.OP_WRITE);
}
selectionKeys.clear();
//iterator.remove();
}
}
}
}