BIO & NIO
IO模型
IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO
BIO(Blocking IO) : 同步阻塞模型,一个客户端连接对应一个处理线程
缺点:
1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题
应用场景:
BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000); while(true) { System.out.println("等待链接。。。"); //阻塞方法 Socket clientSocket = serverSocket.accept(); System.out.println("有客户端链接了。。。"); // handler(clientSocket);
new Thread(()->{
try {
handler(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} } public static void handler(Socket clientSocket) throws IOException { byte[] bytes = new byte[1024]; System.out.println("准备 Read.."); //接收客户端的数据,阻塞方法,没有数据可读时就阻塞 int read = clientSocket.getInputStream().read(bytes); System.out.println("read 完毕"); if( read != -1) { System.out.println("接收到客户的数据:" + new String(bytes,0,read)); } clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); } }
import java.io.IOException; import java.net.Socket; public class SocketClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 9000); // 向服务端发送数据 socket.getOutputStream().write("HelloServer".getBytes()); socket.getOutputStream().flush(); System.out.println("向服务端发送数据结束"); byte[] bytes = new byte[1024]; int read = socket.getInputStream().read(bytes); //接收服务端回收的数据 System.out.println("接收到服务端到数据:" + new String(bytes,0,read)); socket.close(); } }
NIO(Non Blocking IO)
同步非阻塞,服务器实现模式为 一个线程可以处理多个请求(链接), 客户端发送到链接请求都会注册到 多路复用器selector 上, 多路复用器轮询到链接有 IO 请求就进行处理, JDK1.4 开始引入。
应用场景: NIO 方式适用于链接数目多且链接比较短(轻操作)的架构, 比如聊天服务器, 弹幕系统,服务器间通讯, 编程比较复杂
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * 此方式链接太多,会有大量无效遍历,加入 10000个链接,只有1000个有写数据,其他9000个没有断开,每次还是要轮询一万次,十分之九的遍历都是无效的 */ public class NioServer { //保存客户端链接 static List<SocketChannel> channelList = new ArrayList<>(); public static void main(String[] args) throws IOException { //创建 NIO ServerSocketChannel, 与 BIO 的ServerSocket 类似 ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //设置ServerSocketChannel 为 非阻塞 serverSocket.configureBlocking(false); System.out.println("服务启动成功"); while (true) { // 非阻塞模式 accept 方法不会阻塞, 否则会阻塞 // NIO 的非阻塞是有操作系统内部实现的, 底层调用了 linux 内核的 accept 函数 SocketChannel socketChannel = serverSocket.accept(); if (socketChannel != null) { //如果有客户端链接 System.out.println("链接成功"); // 设置SocketChannel 为非阻塞 socketChannel.configureBlocking(false); //保存客户端链接在 list 中 channelList.add(socketChannel); } //遍历链接进行数据读取 Iterator<SocketChannel> iterator = channelList.iterator(); while(iterator.hasNext()) { SocketChannel sc = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); // 非阻塞模式 read 方法不会阻塞,否则会阻塞 int len = sc.read(byteBuffer); //如果有数据,把数据打印出来 if(len > 0) { System.out.println("接收到消息:" + new String(byteBuffer.array(),0,len)); }else if(len == -1) { iterator.remove(); System.out.println("客户端断开连接"); } } } } }
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.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * 引入 多路复用器 selector */ public class NioSelectorServer { public static void main(String[] args) throws IOException { //创建NIO ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); // 设置ServerSocketChannel 为 非阻塞 serverSocket.configureBlocking(false); //打开Selector 处理 channel, 即创建 epoll Selector selector = Selector.open(); //把 ServerSocketChannel 注册到Servlet 上,并且 selector 对客户端 accept 链接操作感兴趣 serverSocket.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务启动成功"); while(true) { //阻塞等待需要处理的事件发生 selector.select(); // 获取selector 中注册的全部事件的 SelectionKey 实例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); // 遍历 SelectionKey 对事件进行处理 while(iterator.hasNext()) { SelectionKey key = iterator.next(); // 如果是 OP_ACCEPT 事件, 则进行链接获取和事件注册 if(key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = server.accept(); socketChannel.configureBlocking(false); //这里只注册了读事件, 如果需要给客户端发送数据可以注册写事件 socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客户端链接成功"); }else if(key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = socketChannel.read(byteBuffer); // 如果有数据,把数据打印出来 if(len > 0) { System.out.println("接收到消息:" + new String(byteBuffer.array(),0,len)); }else if(len == -1) { System.out.println("客户端断开链接"); socketChannel.close(); } } // 从事件集合里删除本次处理到 key, 防止下次 select 重复处理 iterator.remove(); } } } }
NIO 有 三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)
1、channel 类似于流, 每个 channel 对应一个 buffer 缓冲区, buffer 底层就是个数组
2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
3、NIO 的 Buffer 和 channel 都是既可以读也可以写
分类:
IO&NIO&Netty
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!