什么是IO模型
IO分为:网络IO和文件系统IO
计算机组成
内核调度不同线程的情况
1. 网络IO模型
1.1BIO
1.1.1BIO的实现和内核调用
a.BIO程序Java代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketBIO {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9090);
System.out.println("step1: new ServerSocket(9090)");
while(true){
//等待建立Socket连接
final Socket client = server.accept();//阻塞1
System.out.println("step2:client\t" + client.getPort());
new Thread(new Runnable() {
public void run() {
InputStream in = null;
try{
in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while(true){
//从Socket输出流中读取数据
String dataline = reader.readLine();//阻塞2
if(null != dataline){
System.out.println(dataline);
}else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
b.编译
命令:/usr/java/j2sdk1.4.2_15/bin/javac SocketBIO.java
注意:使用Java1.4进行编译,1.4中的ServerSocket还是用的BIO。
c.监控程序运行
strace可以用来监控网络IO交互过程中,不同线程的系统调用。
命令:strace -ff -o out /usr/java/j2sdk1.4.2_15/bin/java SocketBIO
使用jps查看当前主机下运行的Java程序
d.查看SocketBIO调用过程中,产生的所有系统调用文件
注意:out.1573是main线程系统调用记录文件,线程号与jps命令查询的SocketBIO的线程号相同。
查看out.1573内容最后
e.监控out.1573新追加的内容
tail监控指定文件的追加内容,并输出到窗口。
命令:tail -f out.1573
f.用nc建立连接和变化
-
运行程序的变化:
image.png
-
main线程out.1573变化
image.png -
线程对应系统命令调用文件数量的变化
原因:client与server新建立一个连接,新的连接是一个单独的线程,需要文件记录。
image.png -
查看新建立连接线程系统调用记录文件的内容
image.png
g.端口连接情况
Local Address:当前线程的端口
Foreign Address:连接到的端口
netstat:查看当前计算机端口的连接情况
命令: netstat -natp
-
client端口连接
client使用nc与server建立连接,client端口使用情况如下。
image.png -
server端口连接
Java进程1573监控9090端口,如果有client连接9090端口,交给Java程序处理。
image.png
h.nc发送数据和变化
-
应用程序变化
image.png -
out.1636数据变化
image.png
1.1.2 BIO的缺点和引发的问题
缺点
每一个连接都交给一个线程处理,线程里面接收数据存在阻塞,主线线程中server等待客户端的连接也存在阻塞。
引发的问题
- 1.很多的线程,资源,创建线程clone,池
- 2.真正的问题:阻塞
- 3.cpu执行被中断后,这些线程有可能获取到执行权,单线程中存在阻塞,效率低,频繁切换线程浪费CPU资源。
1.1.3 BIO的整体流程图
1.2 NIO
1.2.1 BIO和NIO的不同点
1.2.2 NIO的整体流程图
1.2.3 NIO代码实现
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
public class SocketNIO {
public static void main(String[] args) throws Exception{
LinkedList<SocketChannel> clients = new LinkedList<>();
ServerSocketChannel ss = ServerSocketChannel.open();//服务器开启监听,接受客户端
ss.bind(new InetSocketAddress(9090));
ss.configureBlocking(false);//中点 OS NONBLOCKING //只让接受客户端
//fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0
while(true){ //接受客户端的连接
Thread.sleep(1000);
SocketChannel client = ss.accept();//不会阻塞? -1 NULL
/**
* accept 调用内核了:1.没有客户端连接进来,返回值?在BIO的时候一直卡着,但是在NIO,不卡着,返回-1
* 如果来客户端的连接,accept返回的是这个客户端的fd 5. client object
* NONBLOCKING 就是代码能往下走了,只不过有不同的情况
*/
if(client == null){
}else{
client.configureBlocking(false);//重点 socket(服务端的listen socket 连接请求三次握手后,
int port = client.socket().getPort();
System.out.println("client..port:" + port);
clients.add(client);
}
ByteBuffer buffer = ByteBuffer.allocate(4096); //可以在堆里,堆外
/**
* 遍历已经连接进来的客户端能不能读写数据
*/
for(SocketChannel c : clients){ //串行化, 多线程
int num = c.read(buffer); // > 0 , -1, 0 不会堵塞
if(num > 0){
buffer.flip();
byte[] aaa = new byte[buffer.limit()];
buffer.get(aaa);
String b = new String(aaa);
System.out.println(c.socket().getPort() + " : " + b);
buffer.clear();
}
}
}
}
}
-
监控Socket连接情况
命令: strace -ff -o out java SocketNIO
image.png -
查看主函数所在配置文件
image.png
image.png -
nc连接
image.png -
socket端口连接情况
image.png -
主线程文件变化
image.png -
客户端
image.png
1.2.4 NIO的实现
注意:NIO并不是Java代码实现,而是内核实现。Java中调用了内核实现。
命令: man 2 socket
查看内核中关于socket的实现和相关操作
1.3 select,poll
1.3.1 select,poll图解
-
BIO
image.png
-
NIO
image.png -
select,poll
image.png
1.3.2 select,poll的流程
- 1.在server的main线程中,缓存了所有的客户端连接,在NIO中是逐个遍历客户端,如果客户端有发送数据,则server处理客户端发送的数据。
- 2.在select,poll中不需逐个遍历客户端。将客户端作为参数传递给“电话机”,“电话机”会将客户端发送数据连接返回给server,server遍历这些发送数据的客户端,减少系统调用的时间浪费。
- 3.多路复用器。
1.3.3 select,poll的实现
命令:man 2 select
man 2 poll
查看函数的实现。
select,poll都不是Java来实现的,都是由c语言来实现的。
1.3.4 select,poll解决的问题
server不需要每次都遍历所有的客户端连接。
1.4 epoll
1.4.1 epoll图解
1.4.2 epoll原理
epoll只是在poll的基础上,添加了一个记事本,记事本可以记录当前所有连接,不需要每次都将所有的客户端连接作为参数传递过去。