比喻:餐厅
单线程NIO和单线程BIO一样,它只有一个员工,这个员工有分流(Select)的能力
- 他负责接待新来的客人,安排客人坐下,给客人菜单(安顿客人 事件)
- 客人要点单了,他过去帮客人下单(帮客人下单 事件)
BIO处理的单元是用户;而NIO处理的单元是事件
- BIO,有客人来,我去跟踪这个客人为它服务(普通函数的思想)
- NIO,安顿客人事件发生,我触发这个事件,并处理这个事件(Reactor模式)
代码
服务端代码
package NIO;
//NIO的服务器(餐厅)
public class NIOServer{
//通道管理器(服务员)
private Selector selector;
//(餐厅开门)
//获得一个ServerSocket通道,并对该通道做一些初始化的工作
public void initServer(int port) throws IOException{
//获得一个ServerSocket通道(开一扇门)
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); //设置通道为非阻塞
serverChannel.socket().bind(new InetSocketAddress(port)); //将该通道对应的ServerSocket绑定到port端口
//获得一个通道管理器(服务员)
this.selector = Selector.open();
//(将此服务员安排在这个餐厅;并将餐厅的门打开)
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件
//当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
//(服务员在这里领任务:看是否有客人来;是否有人点餐;……)
//采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
public void listen() throws IOException{
System.out.println("服务器启动成功!");
//轮询访问selector
while(true) {
//(服务员看看有没有事情要处理
// 1. 如果没事情,就会堵塞在这一句,等待事情来;
// 2. 如果有事情,就继续下面的代码
// )
//当注册的事件到达时,方法返回;否则,该方法会一直阻塞
selector.select();
//(取出所有的事情,一件一件做)
//获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<?> ite = this.selector.selectedKeys().iterator();
while(ite.hasNext()){
//(服务员要处理的事情名称)
SelectionKey key = (SelectionKey)ite.next();
//(删除这个事情,表示已经做了)
//删除已选的key,以防重复处理
ite.remove();
//(去处理这个事情)
//处理这个key
handler(key);
}
}
}
//(服务员干活)
//处理请求
public void handler(SelectionKey key) throws IOException{
if(key.isAcceptable()) {
//(有客人来)
//客户端请求连接事件
handlerAccept(key);
} else if(key.isReadable()){
//(客人要点餐)
//获得可读的事件
handelerRead(key);
}
}
//(服务员如何安排客人坐下)
//处理连接请求
public void handlerAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept(); //获得和客户端连接的通道
channel.configureBlocking(false); //设置成非阻塞
System.out.println("新的客户端连接");
//在这里可以给客户端发送信息
//...
//(客户坐下以后,把菜单给他,给他点菜的权限)
//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限
channel.register(this.selector, SelectionKey.OP_READ);
}
//(服务员如何取得客人的菜单)
//处理读的事件
public void handelerRead(SelectionKey key) throws IOException {
//服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
//创建读取缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到的信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer); //将消息送回给客户端
}
//(正式开门)
//启动服务端测试
public static void main(String[] args) throws IOException{
NIOServer server = new NIOServer();
server.initServer(8000);
server.listen();
}
}
客户端:可以使用windows的telnet
# 1. 打开Windows命令行
# 2. 使用telnet连接服务端
> telnet 127.0.0.1 8000
# 3. 给服务端发送消息
> send wo yao chifan