以下是本人所理解的原理图:
用代码来解释吧:
/**
* NIO 非阻塞时IO
* 阻塞式 IO问题:
* 1、客户端过多时,要对每一个client都要创建线程 ServerSocket.accept()处理,会导致创建大量线程,每个线程都要占用占空间和cpu时间
* 2、阻塞可能导致上下文切换,且大部分切换无意义。
*
* NIO特点:
* 1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
*
*/
/**
* 原理:
* 服务端和客户端各维护一个管理通道的对象selector,监听各个通道上的事件
* 服务端:
* 如果selector注册了读事件,客户端发数据过来,NIO服务端就会在selector添加一个读事件。
* 服务端的处理线程会轮询访问selector,如果访问selector时发现有感兴趣的时间到达,则处理这些事件
* 如果没有感兴趣的事件,则处理线程会一直阻塞直到感兴趣的事件到达为止。
*
*
通道上可以注册的事件:
key.isAcceptable()
key.isConnectable()
key.isReadable()
key.isWritable()
key.isValid()
*
*/
/**
* NIO服务端
*/
class NIOServer{
//通道管理器
private Selector selector;
/**
* 获得一个ServerSocket通道,并对该通道做一些初始化工作
* @param port
*/
public void initServer(int port) throws IOException {
/**
* 1、打开一个ServerSocketChannel
* channel是client和server对接的通道,client selector一旦接到其注册的事件,就会推给channel;
* server selector从channel拿到的事件如果是其之前注册的,则推给处理线程进行处理
* 2、设置非阻塞
* 3、将sockect绑定到指定端口
* 4、打开通道管理器
* 5、管理器注册事件:服务端接收客户端连接事件SelectionKey.OP_ACCEPT
*/
//1
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2
serverSocketChannel.configureBlocking(false);
//3
serverSocketChannel.bind(new InetSocketAddress(port));
//4
this.selector = Selector.open();
//5
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 处理线程组采用轮询的方式监听selector是否存在需要处理的事件,有则处理
*/
public void selectorListener() throws IOException {
System.out.println("========== start selector ");
//轮询处理线程访问selector,注册的事件没到达时,一直阻塞
while(true){
//select其实就是监控所有的处理线程
selector.select();
//遍历注册事件
Iterator i = this.selector.selectedKeys().iterator();
while (i.hasNext()){
SelectionKey key = (SelectionKey) i.next();
//删除已选key防止重复处理
i.remove();
//判断是否是客户端请求连接事件 SelectionKey.OP_ACCEPT
if (key.isAcceptable()){
//获取服务器上当前处理线程的channel
ServerSocketChannel server = (ServerSocketChannel) key.channel();
//获取和客户端的连接通道
SocketChannel channel = server.accept();
//设置成非阻塞式
channel.configureBlocking(false);
//像客户端发送一条消息
channel.write(ByteBuffer.wrap(new String("send to client....").getBytes()));
//连接成功后为了收到client的消息,需要给通道设置读权限
channel.register(this.selector,SelectionKey.OP_READ);
}else if (key.isReadable()){
//此时的selector监控到的处理线程是进行处理读客户端的处理事件
read(key);
}
}
}
}
//读取client发来的消息
public void read(SelectionKey key) throws IOException {
//获取事件发生的连接通道
SocketChannel channel = (SocketChannel) key.channel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(50);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("====server接收到的消息: "+msg);
ByteBuffer out = ByteBuffer.wrap(msg.getBytes());
//将消息回送到客户端
channel.write(out);
}
/**
* 服务端启动
* @param str
* @throws IOException
*/
public static void main(String[] str) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8000);
server.selectorListener();
}
}
class NIOClient{
//通道管理器,监听客户端线程事件
private Selector selector;
//对selector初始化,注册监听事件
public void initClient(String ip,int port) throws IOException {
// 获得一个Socket通道
SocketChannel channel = SocketChannel.open();
// 设置通道为非阻塞
channel.configureBlocking(false);
// 获得一个通道管理器
this.selector = Selector.open();
// 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
//用channel.finishConnect();才能完成连接
channel.connect(new InetSocketAddress(ip,port));
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
}
/**
* 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
* @throws IOException
*/
public void listen() throws IOException {
System.out.println("=======client start to listen");
// 轮询访问selector
while (true) {
selector.select();
// 获得selector中选中的项的迭代器
Iterator ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理
ite.remove();
// 连接事件发生
if (key.isConnectable()) {
SocketChannel channel = (SocketChannel) key
.channel();
// 如果正在连接,则完成连接
if(channel.isConnectionPending()){
channel.finishConnect();
}
// 设置成非阻塞
channel.configureBlocking(false);
//在这里可以给服务端发送信息哦
channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));
//在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
channel.register(this.selector, SelectionKey.OP_READ);
// 获得了可读的事件
} else if (key.isReadable()) {
read(key);
}
}
}
}
/**
* 处理读取服务端发来的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
//获取事件发生的连接通道
SocketChannel channel = (SocketChannel) key.channel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(50);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("====client接收到的消息: "+msg);
}
/**
* 启动客户端测试
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOClient client = new NIOClient();
client.initClient("localhost",8000);
client.listen();
}
}