网络侧IO,通过网络来通信(偏向内核方面)
C10K问题:http://www.kegel.com/c10k.html#frameworks
什么是NIO
操作系统角度:NIO表示非阻塞IO,nonblocking;
JDK角度:N表示New IO(要掌握nonblocking,channel,buffer,selector),内存、文件系统
要想明白JDK的NIO,要先明白操作系统的NIO
程序不能直接访问内核,用户程序想要调用内核的方法,需要使用int 80
中断,CPU从用户态切换到内核态。操作系统给内核暴露了几百个系统调用syscall
。
IDT:中断描述符表,记录0~255中断号,在BIOS就加载了,内核进行callback,比如int 0x80,这里的80是中断号
GDT:全局描述符表。
关于中断的概念:使用晶振(将直流电流转换为…打点),实现CPU的时间分片。中断分为软中断(多个进程的切换,让他们看似同时在执行)、硬件中断(接受键盘、鼠标输入)。进程想要访问硬件,需要触发int 0x80
中断
宏观并行、微观串行
新版图:
举例
来看一段简单的可以进行网络通信的java代码(JDK1.4),来说明计算机的通信是怎么完成的
抓取程序和内核之间的系统调用,追踪每一个线程,并输出到一个文件中:
strace
用来监控指定的程序(字节码)发生了哪些系统调用
运行后生成的输出文件:
JVM是C语言开发的,跑在操作系统之上,进行了系统调用。
下面是输出了java
代码在内核中实际进行的系统调用:
2541行的3
就是java代码中 new 出的ServerSocket()
,在3
上绑定了端口号
最后一行看到accept(3,
证明调用被阻塞了
使用tail
一直盯着这个输出,同时:开一个窗口,建立连接、发数据
使用nc
手工建立一个新的连接:
查看输出:
recv(5,
是再一次阻塞,
再用客户端 随意输入一些数据,再查看输出:
查看server端的端口监听
关于文件描述符:linux一切皆文件
文件描述符3:用来代表监听
文件描述符3,4:一个是ipv4,一个是ipv6
建立客户端连接之后,fd文件描述符里面多出一个5
,代表新建立的这个连接:
上述这种情况,如果一个客户端连接之后一直不发数据,会一直阻塞。别人无法连接进来。
那么,如何实现能够接受多个客户端?
传统的BIO模型:让一个连接对应一个线程,每新开一个连接,就给它新开一个线程。
弊端:线程过多
如何解决C10K问题?
C10K问题:用户态和内核态之间频繁切换。CPU在一个时间片只能处理一个程序(线程),线程具有独立的栈,当线程过多时,调度的时候成本较高。
问题的本质:因为阻塞(BIO)。为了解决阻塞,才开启的多线程。想解决这个问题,从内核的角度考虑,可以考虑让系统调用得到的文件描述符3
,5
变成非阻塞。
解决问题的思路:将阻塞变为不阻塞。这种功能应该在内核实现,而不是用程序实现。
不要受到一些文章的影响,简单的用“加机器”的方式解决问题。
将单击性能已经压榨到极限时,再考虑加机器,分布式/用集群。
我们看内核的系统调用:
内核有一种非阻塞模式:在accept(3,
中空转,要么返回client的描述符,要么返回-1
使用了NIO的新代码:
(补充代码图)
用了NIO的api,不能在JDK1.4中使用。可以在JDK1.8中使用。
ss.configBlocking(false);
设置非阻塞
后面用一个循环,每一次循环中,如果没有连接,则打印null;如果有连接,则打印端口号。这样
运行后,看循环输出的null可以判断,没有进入阻塞状态
accept一直都是-1,没有阻塞。代表没有连接。
连接进来一个客户端之后:
用一个线程可以接受很多客户端。(NIO:NonBlocking)
放大:
C10K当并发量很大的时候,文件描述符会很多,每循环一次,都要调用一次recv系统调用(复杂度O(n)
),进行用户态到内核态的切换,切换时性能损耗大。
缩小:
当C10K只有1个C发来了数据,只使用到了1个有用的系统调用,剩余n-1次的监听都是无效的->技术选型失败。(架构师进行技术选型:要懂原理)
多条路复用了一次调用得到具体的到来情况
实现方式:内核
允许程序去调用内核,来监控更多的客户端,直到一个或多个文件描述符是可用的状态,再返回。减少了用户态、内核态的无用切换。
select多路复用器返回给程序一个list,告诉程序哪些可以读取,然后程序要自己读取。这是同步模型。
如果程序自己读取,程序在IO上的模型就叫同步模型。
如果程序把读取的过程交给内核,自己做自己的事情,叫异步模型。
多路复用的问题:如果有很多长连接,内核每次都要给程序传递很多连接对象
解决方式:epoll
Epoll也是多路复用器。它不负责读取IO,只关心返回结果。Epoll是Event poll,把有数据这个事件通知给程序,还需要程序自己取读取数据。
man epoll
查看命令帮助文档
epoll_create, epoll_ctl, epoll_wait