一、NIO 同步非阻塞IO
案例1:实现服务器端和客户端之间的数据通信(非阻塞)
1.nio网络服务端程序,能不断接受客户端连接并读取客户端发来的数据
package com.tenpower.nio.socket; 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; //网络服务器端程序 public class NIOServer { public static void main(String[] args) throws Exception{ //1. 得到一个ServerSocketChannel对象 老大 ServerSocketChannel serverSocketChannel=ServerSocketChannel.open(); //2. 得到一个Selector对象 间谍 Selector selector=Selector.open(); //3. 绑定一个端口号 serverSocketChannel.bind(new InetSocketAddress(9999)); //4. 设置非阻塞方式 serverSocketChannel.configureBlocking(false); //5. 把ServerSocketChannel对象注册给Selector对象 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //6. 干活 while(true){ //6.1 监控客户端 if(selector.select(2000)==0){ //nio非阻塞式的优势 System.out.println("Server:没有客户端搭理我,我就干点别的事"); continue; } //6.2 得到SelectionKey,判断通道里的事件 Iterator<SelectionKey> keyIterator=selector.selectedKeys().iterator(); while(keyIterator.hasNext()){ SelectionKey key=keyIterator.next(); if(key.isAcceptable()){ //客户端连接请求事件 System.out.println("OP_ACCEPT"); SocketChannel socketChannel=serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if(key.isReadable()){ //读取客户端数据事件 SocketChannel channel=(SocketChannel) key.channel(); ByteBuffer buffer=(ByteBuffer) key.attachment(); channel.read(buffer); System.out.println("客户端发来数据:"+new String(buffer.array())); } // 6.3 手动从集合中移除当前key,防止重复处理 keyIterator.remove(); } } } }
2.nio客户端程序
package com.tenpower.nio.socket; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; //网络客户端程序 public class NIOClient { public static void main(String[] args) throws Exception{ //1. 得到一个网络通道 SocketChannel channel=SocketChannel.open(); //2. 设置非阻塞方式 channel.configureBlocking(false); //3. 提供服务器端的IP地址和端口号 InetSocketAddress address=new InetSocketAddress("127.0.0.1",9999); //4. 连接服务器端 if(!channel.connect(address)){ while(!channel.finishConnect()){ //nio作为非阻塞式的优势 System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情"); } } //5. 得到一个缓冲区并存入数据 String msg="hello,Server"; ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes()); //6. 发送数据 channel.write(writeBuf); System.in.read(); } }
连接上服务端后发送了一条数据,效果如下:
案例2:多人聊天
1.多人聊天服务端,接收客户端发来的数据,并能把数据广播给所有客户端
package com.tenpower.nio.chat; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.text.SimpleDateFormat; import java.util.*; //聊天程序服务器端 public class ChatServer { private ServerSocketChannel listenerChannel; //监听通道 老大 private Selector selector;//选择器对象 间谍 private static final int PORT = 9999; //服务器端口 public ChatServer() { try { // 1. 得到监听通道 老大 listenerChannel = ServerSocketChannel.open(); // 2. 得到选择器 间谍 selector = Selector.open(); // 3. 绑定端口 listenerChannel.bind(new InetSocketAddress(PORT)); // 4. 设置为非阻塞模式 listenerChannel.configureBlocking(false); // 5. 将选择器绑定到监听通道并监听accept事件 listenerChannel.register(selector, SelectionKey.OP_ACCEPT); printInfo("Chat Server is ready......."); } catch (IOException e) { e.printStackTrace(); } } //6. 干活儿 public void start() throws Exception{ try { while (true) { //不停监控 if (selector.select(2000) == 0) { System.out.println("Server:没有客户端找我, 我就干别的事情"); continue; } Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { //连接请求事件 SocketChannel sc=listenerChannel.accept(); sc.configureBlocking(false); sc.register(selector,SelectionKey.OP_READ); System.out.println(sc.getRemoteAddress().toString().substring(1)+"上线了..."); } if (key.isReadable()) { //读取数据事件 readMsg(key); } //一定要把当前key删掉,防止重复处理 iterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } } //读取客户端发来的消息并广播出去 public void readMsg(SelectionKey key) throws Exception{ SocketChannel channel=(SocketChannel) key.channel(); ByteBuffer buffer=ByteBuffer.allocate(1024); int count=channel.read(buffer); if(count>0){ String msg=new String(buffer.array()); printInfo(msg); //发广播 broadCast(channel,msg); } } //给所有的客户端发广播 public void broadCast(SocketChannel except,String msg) throws Exception{ System.out.println("服务器发送了广播..."); for(SelectionKey key:selector.keys()){ Channel targetChannel=key.channel(); if(targetChannel instanceof SocketChannel && targetChannel!=except){ SocketChannel destChannel=(SocketChannel)targetChannel; ByteBuffer buffer=ByteBuffer.wrap(msg.getBytes()); destChannel.write(buffer); } } } private void printInfo(String str) { //往控制台打印消息 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("[" + sdf.format(new Date()) + "] -> " + str); } public static void main(String[] args) throws Exception { new ChatServer().start();; } }
2.多人聊天客户端,可向服务端发送数据,并能接受服务器广播的数据
package com.tenpower.nio.chat; 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.Iterator; import java.util.Set; //聊天程序客户端 public class ChatClient { private final String HOST = "127.0.0.1"; //服务器地址 private int PORT = 9999; //服务器端口 private SocketChannel socketChannel; //网络通道 private String userName; //聊天用户名 public ChatClient() throws IOException { //1. 得到一个网络通道 socketChannel=SocketChannel.open(); //2. 设置非阻塞方式 socketChannel.configureBlocking(false); //3. 提供服务器端的IP地址和端口号 InetSocketAddress address=new InetSocketAddress(HOST,PORT); //4. 连接服务器端 if(!socketChannel.connect(address)){ while(!socketChannel.finishConnect()){ //nio作为非阻塞式的优势 System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情"); } } //5. 得到客户端IP地址和端口信息,作为聊天用户名使用 userName = socketChannel.getLocalAddress().toString().substring(1); System.out.println("---------------Client(" + userName + ") is ready---------------"); } //向服务器端发送数据 public void sendMsg(String msg) throws Exception{ if(msg.equalsIgnoreCase("bye")){ socketChannel.close(); return; } msg = userName + "说:"+ msg; ByteBuffer buffer=ByteBuffer.wrap(msg.getBytes()); socketChannel.write(buffer); } //从服务器端接收数据 public void receiveMsg() throws Exception{ ByteBuffer buffer = ByteBuffer.allocate(1024); int size=socketChannel.read(buffer); if(size>0){ String msg=new String(buffer.array()); System.out.println(msg.trim()); } } }
3.运行客户端,并在主线程中发送数据,在另一个线程不断接受服务端的广播数据
package com.tenpower.nio.chat; import java.io.IOException; import java.util.Scanner; //启动聊天程序客户端 public class TestChat { public static void main(String[] args) throws Exception { ChatClient chatClient=new ChatClient(); new Thread(){ public void run(){ while(true){ try { chatClient.receiveMsg(); Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } } } }.start(); Scanner scanner=new Scanner(System.in); while (scanner.hasNextLine()){ String msg=scanner.nextLine(); chatClient.sendMsg(msg); } } }
上述代码运行一次就是一个聊天客户端,可同时运行多个聊天客户端,聊天效果如下:
二、AIO(NIO2.0) 异步非阻塞
引入异步通道的概念,采用了Proactor模式,简化了程序编写,一个有效的请求才启动一个线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用