你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生
在网络的初期网民很少服务器完全无压力那时的技术也没有现在先进通常用一个线程来全程跟踪处理一个请求。因为这样最简单。其实代码实现大家都知道就是服务器上有个ServerSocket在某个端口监听接收到客户端的连接后会创建一个Socket并把它交给一个线程进行后续处理。线程主要从Socket读取客户端传过来的数据然后进行业务处理并把结果再写入Socket传回客户端。由于网络的原因Socket创建后并不一定能立刻从它上面读取数据可能需要等一段时间此时线程也必须一直阻塞着。在向Socket写入数据时也可能会使线程阻塞。这里准备了一个示例主要逻辑如下客户端创建20个Socket并连接到服务器上再创建20个线程每个线程负责一个Socket。服务器端接收到这20个连接创建20个Socket接着创建20个线程每个线程负责一个Socket。为了模拟服务器端的Socket在创建后不能立马读取数据让客户端的20个线程分别休眠5-10之间的一个随机秒数。客户端的20个线程会在第5秒到第10秒这段时间内陆陆续续的向服务器端发送数据服务器端的20个线程也会陆陆续续接收到数据。/** * author lixinjie * since 2019-05-07 */ public class BioServer { static AtomicInteger counter new AtomicInteger(0); static SimpleDateFormat sdf new SimpleDateFormat(“HH:mm:ss”); public static void main(String[] args) { try { ServerSocket ss new ServerSocket(); ss.bind(new InetSocketAddress(“localhost”, 8080)); while (true) { Socket s ss.accept(); processWithNewThread(s); } } catch (Exception e) { e.printStackTrace(); } } static void processWithNewThread(Socket s) { Runnable run () - { InetSocketAddress rsa (InetSocketAddress)s.getRemoteSocketAddress(); System.out.println(time() “-” rsa.getHostName() “:” rsa.getPort() “-” Thread.currentThread().getId() “:” counter.incrementAndGet()); try { String result readBytes(s.getInputStream()); System.out.println(time() “-” result “-” Thread.currentThread().getId() “:” counter.getAndDecrement()); s.close(); } catch (Exception e) { e.printStackTrace(); } }; new Thread(run).start(); } static String readBytes(InputStream is) throws Exception { long start 0; int total 0; int count 0; byte[] bytes new byte[1024]; //开始读数据的时间 long begin System.currentTimeMillis(); while ((count is.read(bytes)) -1) { if (start 1) { //第一次读到数据的时间 start System.currentTimeMillis(); } total count; } //读完数据的时间 long end System.currentTimeMillis(); return “wait” (start - begin) “ms,read” (end - start) “ms,total” total “bs”; } static String time() { return sdf.format(new Date()); } }/** * author lixinjie * since 2019-05-07/ public class Client { public static void main(String[] args) { try { for (int i 0; i 20; i) { Socket s new Socket(); s.connect(new InetSocketAddress(“localhost”, 8080)); processWithNewThread(s, i); } } catch (IOException e) { e.printStackTrace(); } } static void processWithNewThread(Socket s, int i) { Runnable run () - { try { //睡眠随机的5-10秒模拟数据尚未就绪 Thread.sleep((new Random().nextInt(6) 5) * 1000); //写1M数据为了拉长服务器端读数据的过程 s.getOutputStream().write(prepareBytes()); //睡眠1秒让服务器端把数据读完 Thread.sleep(1000); s.close(); } catch (Exception e) { e.printStackTrace(); } }; new Thread(run).start(); } static byte[] prepareBytes() { byte[] bytes new byte[10241024*1]; for (int i 0; i bytes.length; i) { bytes[i] 1; } return bytes; } }执行结果如下时间-IP:Port-线程Id:当前线程数 15:11:52-127.0.0.1:55201-10:1 15:11:52-127.0.0.1:55203-12:2 15:11:52-127.0.0.1:55204-13:3 15:11:52-127.0.0.1:55207-16:4 15:11:52-127.0.0.1:55208-17:5 15:11:52-127.0.0.1:55202-11:6 15:11:52-127.0.0.1:55205-14:7 15:11:52-127.0.0.1:55206-15:8 15:11:52-127.0.0.1:55209-18:9 15:11:52-127.0.0.1:55210-19:10 15:11:52-127.0.0.1:55213-22:11 15:11:52-127.0.0.1:55214-23:12 15:11:52-127.0.0.1:55217-26:13 15:11:52-127.0.0.1:55211-20:14 15:11:52-127.0.0.1:55218-27:15 15:11:52-127.0.0.1:55212-21:16 15:11:52-127.0.0.1:55215-24:17 15:11:52-127.0.0.1:55216-25:18 15:11:52-127.0.0.1:55219-28:19 15:11:52-127.0.0.1:55220-29:20 时间-等待数据的时间,读取数据的时间,总共读取的字节数-线程Id:当前线程数 15:11:58-wait5012ms,read1022ms,total1048576bs-17:20 15:11:58-wait5021ms,read1022ms,total1048576bs-13:19 15:11:58-wait5034ms,read1008ms,total1048576bs-11:18 15:11:58-wait5046ms,read1003ms,total1048576bs-12:17 15:11:58-wait5038ms,read1005ms,total1048576bs-23:16 15:11:58-wait5037ms,read1010ms,total1048576bs-22:15 15:11:59-wait6001ms,read1017ms,total1048576bs-15:14 15:11:59-wait6016ms,read1013ms,total1048576bs-27:13 15:11:59-wait6011ms,read1018ms,total1048576bs-24:12 15:12:00-wait7005ms,read1008ms,total1048576bs-20:11 15:12:00-wait6999ms,read1020ms,total1048576bs-14:10 15:12:00-wait7019ms,read1007ms,total1048576bs-26:9 15:12:00-wait7012ms,read1015ms,total1048576bs-21:8 15:12:00-wait7023ms,read1008ms,total1048576bs-25:7 15:12:01-wait7999ms,read1011ms,total1048576bs-18:6 15:12:02-wait9026ms,read1014ms,total1048576bs-10:5 15:12:02-wait9005ms,read1031ms,total1048576bs-19:4 15:12:03-wait10007ms,read1011ms,total1048576bs-16:3 15:12:03-wait10006ms,read1017ms,total1048576bs-29:2 15:12:03-wait10010ms,read1022ms,total1048576bs-28:1可以看到服务器端确实为每个连接创建一个线程共创建了20个线程。客户端进入休眠约5-10秒模拟连接上数据不就绪服务器端线程在等待等待时间约5-10秒。客户端陆续结束休眠往连接上写入1M数据服务器端开始读取数据整个读取过程约1秒。可以看到服务器端的工作线程会把时间花在“等待数据”和“读取数据”这两个过程上。这有两个不好的地方一是有很多客户端同时发起请求的话服务器端要创建很多的线程可能会因为超过了上限而造成崩溃。二是每个线程的大部分时光中都是在阻塞着无事可干造成极大的资源浪费。开头已经说了那个年代网民很少所以不可能会有大量请求同时过来。至于资源浪费就浪费吧反正闲着也是闲着。来个简单的小例子饭店共有10张桌子且配备了10位服务员。只要有客人来了大堂经理就把客人带到一张桌子并安排一位服务员全程陪同。即使客人暂时不需要服务服务员也一直在旁边站着。可能觉着是一种浪费其实非也这就是尊贵的VIP服务。其实VIP映射的是一对一的模型主要体现在“专用”上或“私有”上。真正的多路复用技术多路复用技术原本指的是在通信方面多种信号或数据从宏观上看交织在一起使用同一条传输通道进行传输。这样做的目的一方面可以充分利用通道的传输能力另一方面自然是省时省力省钱啦。其实这个概念非常的“生活化”随手就可以举个例子一条小水渠里水在流在一端往里倒入大量乒乓球在另一端用网进行过滤把乒乓球和水流分开。这就是一个比较“土”的多路复用首先在发射端把多种信号或数据进行“混合”接着是在通道上进行传输最后在接收端“分离”出自己需要的信号或数据。相信大家都看出来了这里的重点其实就是处理好“混合”和“分离”对于不同的信号或数据有不同的处理方法。比如以前的有线电视是模拟信号即电磁波。一家一般只有一根信号线但可以同时接多个电视每个电视任意换台互不影响。这是由于不同频率的波可以混合和分离。当然可能不是十分准确明白意思就行了。再比如城市的高铁站一般都有数个站台供高铁同时停靠但城市间的高铁轨道单方向只有一条如何保证那么多趟高铁安全运行呢很明显是分时使用每趟高铁都有自己的时刻。多趟高铁按不同的时刻出站相当于混合按不同的时刻进站相当于分离。总结一下多路指的是多种不同的信号或数据或其它事物复用指的是共用同一个物理链路或通道或载体。可见多路复用技术是一种一对多的模型“多”的这一方复用了“一”的这一方。其实一对多的模型主要体现在“公用”上或“共享”上。您先看着我一会再过来一对一服务是典型的有钱任性虽然响应及时、服务周到但不是每个人都能享受的毕竟还是“屌丝”多嘛那就来个共享服务吧。所以实际当中更多的情况是客人坐下后会给他一个菜单让他先看着反正也不可能立马点餐服务员就去忙别的了。可能不时的会有服务员从客人身旁经过发现客人还没有点餐就会主动去询问现在需要点餐吗如果需要服务员就给你写菜单如果不需要服务员就继续往前走了。这种情况饭店整体运行的也很好但是服务员人数少多了。现在服务10桌客人4个服务员绰绰有余。这节省的可都是纯利润呀。)因为10桌客人同时需要服务的情况几乎是不会发生的绝大部分情况都是错开的。如果真有的话那就等会好了又不是120/119人命关天的。回到代码里情况与之非常相似完全可以采用相同的理论去处理。连接建立后找个地方把它放到那里可以暂时先不管它反正此时也没有数据可读。但是数据早晚会到来的所以要不时的去询问每个连接有数据没有有的话就读取数据没有的话就继续不管它。其实这个模式在Java里早就有了就是Java NIO这里的大写字母“N”是单词“New”即“新”的意思主要是为了和上面的“一对一”进行区分。先铺垫一下吧现在需要把Socket交互的过程再稍微细化一些。客户端先请求连接connect服务器端然后接受连接accept然后客户端再向连接写入数据write接着服务器端从连接上读出数据read。和打电话的场景一样主叫拨号connect被叫接听accept主叫说话speak被叫聆听listen。主叫给被叫打电话说明主叫找被叫有事所以被叫关注的是接通电话听对方说。客户端主动向服务器端发起请求说明客户端找服务器端有事所以服务器端关注的是接受请求读取对方传来的数据。这里把接受请求读取数据称为服务器端感兴趣的操作。在Java NIO中接受请求的操作用OP_ACCEPT表示读取数据的操作用OP_READ表示。我决定先过一遍饭店的场景让首次接触Java NIO的同学不那么迷茫。就是把常规的场景进行了定向整理稍微有点刻意明白意思就行了。1、专门设立一个“跑腿”服务员工作职责单一就是问问客人是否需要服务。2、站在门口接待客人本来是大堂经理的工作但是他不愿意在门口盯着于是就委托给跑腿服务员你帮我盯着有人来了告诉我。于是跑腿服务员就有了一个任务替大堂经理盯梢。终于来客人了跑腿服务员赶紧告诉了大堂经理。3、大堂经理把客人带到座位上对跑腿服务员说客人接下来肯定是要点餐的但是现在在看菜单不知道什么时候能看好所以你不时的过来问问看需不需要点餐需要的话就再喊来一个“点餐”服务员给客人写菜单。于是跑腿服务员就又多了一个任务就是盯着这桌客人不时来问问如果需要服务的话就叫点餐服务员过来服务。4、跑腿服务员在某次询问中客人终于决定点餐了跑题服务员赶紧找来一个点餐服务员为客人写菜单。5、就这样跑腿服务员既要盯着门外新过来的客人也要盯着门内已经就坐的客人。新客人来了通知大堂经理去接待。就坐的客人决定点餐了通知点餐服务员去写菜单。事情就这样一直循环的持续下去一切都挺好。角色明确职责单一配合很好。大堂经理和点餐服务员是需求的提供者或实现者跑腿服务员是需求的发现者并识别出需求的种类需要接待的交给大堂经理需要点餐的交给点餐服务员。哈哈Java NIO来啦代码的写法非常的固定可以配合着后面的解说来看这样就好理解了如下/** * author lixinjie * since 2019-05-07 */ public class NioServer { static int clientCount 0; static AtomicInteger counter new AtomicInteger(0); static SimpleDateFormat sdf new SimpleDateFormat(“HH:mm:ss”); public static void main(String[] args) { try { Selector selector Selector.open(); ServerSocketChannel ssc ServerSocketChannel.open(); ssc.configureBlocking(false); ssc.register(selector, SelectionKey.OP_ACCEPT); ssc.bind(new InetSocketAddress(“localhost”, 8080)); while (true) { selector.select(); Set keys selector.selectedKeys(); Iterator iterator keys.iterator(); while (iterator.hasNext()) { SelectionKey key iterator.next(); iterator.remove(); if (key.isAcceptable()) { ServerSocketChannel ssc1 (ServerSocketChannel)key.channel(); SocketChannel sc null; while ((sc ssc1.accept()) ! null) { sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); InetSocketAddress rsa (InetSocketAddress)sc.socket().getRemoteSocketAddress(); System.out.println(time() “-” rsa.getHostName() “:” rsa.getPort() “-” Thread.currentThread().getId() “:” (clientCount)); } } else if (key.isReadable()) { //先将“读”从感兴趣操作移出待把数据从通道中读完后再把“读”添加到感兴趣操作中 //否则该通道会一直被选出来 key.interestOps(key.interestOps() (~ SelectionKey.OP_READ)); processWithNewThread((SocketChannel)key.channel(), key); } } } } catch (Exception e) { e.printStackTrace(); } } static void processWithNewThread(SocketChannel sc, SelectionKey key) { Runnable run () - { counter.incrementAndGet(); try { String result readBytes(sc); //把“读”加进去 key.interestOps(key.interestOps() | SelectionKey.OP_READ); System.out.println(time() “-” result “-” Thread.currentThread().getId() “:” counter.get()); sc.close(); } catch (Exception e) { e.printStackTrace(); } counter.decrementAndGet(); }; new Thread(run).start(); } static String readBytes(SocketChannel sc) throws Exception { long start 0; int total 0; int count 0; ByteBuffer bb ByteBuffer.allocate(1024); //开始读数据的时间 long begin System.currentTimeMillis(); while ((count sc.read(bb)) -1) { if (start 1) { //第一次读到数据的时间 start System.currentTimeMillis(); } total count; bb.clear(); } //读完数据的时间 long end System.currentTimeMillis(); return “wait” (start - begin) “ms,read” (end - start) “ms,total” total “bs”; } static String time() { return sdf.format(new Date()); } }

相关新闻

HarmonyOS开发实战:小分享-从EntryAbility看UIAbility生命周期

HarmonyOS开发实战:小分享-从EntryAbility看UIAbility生命周期

前言 在 HarmonyOS Stage 模型中,UIAbility 是承载 UI 的核心组件,它的生命周期回调决定了 App 启动、前台、后台、销毁的全过程。本篇以小分享 App 的 EntryAbility 为例,逐一拆解这些回调的触发时机与典型用法。掌握 UIAbility 生命周期是…

2026/7/23 9:22:33 阅读更多 →
技术写作规范优化:提升内容质量与SEO收录效果

技术写作规范优化:提升内容质量与SEO收录效果

这次我们来看一个技术写作规范优化的项目。这个项目的核心价值在于帮助技术作者快速掌握专业的内容创作规范,避免常见的写作陷阱,提升文章质量和平台收录效果。对于技术博主来说,最关心的往往是:规范是否清晰可执行?能…

2026/7/23 9:22:33 阅读更多 →
中国机遇2.0:数字经济、绿色科技与高端制造的新引擎

中国机遇2.0:数字经济、绿色科技与高端制造的新引擎

1. 项目概述:解码"中国机遇2.0"的全球价值 世界经济论坛新领军者年会(俗称"夏季达沃斯")今年最引人注目的议题莫过于"中国机遇2.0"概念的提出。这个升级版的发展框架,正在重构跨国企业的战略版图—…

2026/7/23 9:21:32 阅读更多 →

最新新闻

JVS企业文档视角:制造业验厂,你的文档经得起随时查吗?

JVS企业文档视角:制造业验厂,你的文档经得起随时查吗?

聊个制造业里"谁都不想面对但又躲不掉"的事——验厂。不管是客户验厂、第三方审核、体系认证监督审核,还是环保检查、安全检查,核心逻辑都一样:审核员来了,第一句话往往是"请把相关文件拿出来看看"。然后呢&a…

2026/7/23 16:46:57 阅读更多 →
TM4C1294 GPIO复用与电气特性:从寄存器配置到驱动能力设计

TM4C1294 GPIO复用与电气特性:从寄存器配置到驱动能力设计

1. 项目概述与核心价值对于任何一位嵌入式开发者来说,微控制器(MCU)的通用输入输出(GPIO)接口都是我们与物理世界交互的起点。从点亮一个LED到驱动复杂的通信总线,GPIO的灵活性与可靠性直接决定了整个系统的…

2026/7/23 16:46:57 阅读更多 →
TI DS92LV2411/12 SerDes芯片组:高速视频传输的信号完整性与工程实践

TI DS92LV2411/12 SerDes芯片组:高速视频传输的信号完整性与工程实践

1. 项目概述与核心价值在视频监控、医疗影像或者工业相机这类对实时性和画质要求极高的应用里,工程师们常常面临一个头疼的问题:如何把摄像头传感器输出的那几十根并行的、高速的像素数据和同步信号,干净利落地送到几米甚至十几米开外的处理板…

2026/7/23 16:46:57 阅读更多 →
Claude封号问题分析与纯净网络环境配置方案

Claude封号问题分析与纯净网络环境配置方案

最近很多开发者在使用Claude时遇到了账号被封的问题,特别是使用Claude Code等桌面版工具时,经常出现"your organization has disabled claude subscription access"等错误提示。这些问题很大程度上与网络环境的质量有关,本文将详细…

2026/7/23 16:46:57 阅读更多 →
ARM Cortex-M3调试接口深度解析:JTAG与SWD原理、切换及实战避坑指南

ARM Cortex-M3调试接口深度解析:JTAG与SWD原理、切换及实战避坑指南

1. 项目概述 在嵌入式开发领域,调试接口是连接开发者思维与芯片内部世界的桥梁。无论是追踪一个诡异的死机问题,还是单步执行代码观察寄存器变化,都离不开一个可靠、高效的调试通道。对于基于ARM Cortex-M3内核的微控制器,如TI的S…

2026/7/23 16:46:57 阅读更多 →
四大收银软件实测:从大型商超到餐饮小店的选型指南

四大收银软件实测:从大型商超到餐饮小店的选型指南

在实体零售和餐饮行业摸爬滚打多年的朋友都知道,选对一套收银系统不仅仅是买个软件那么简单,它直接关系到门店的运营效率、数据准确性甚至是客户体验。很多店主在创业初期容易陷入一个误区:要么为了省钱用功能极其简陋的免费版,导…

2026/7/23 16:45:57 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击: https://codechina.net 第一章:AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后,我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 12:54:44 阅读更多 →

月新闻