- Linux IO 原理
- 文件系统
- 文件目录
- 文件类型
- 文件描述符
- PageCache
- 网络IO
- 磁盘IO
- 网络IO模型变化
- BIO
- NIO
Linux系统的文件系统目录是稳定的,只不过启动后可以重新挂载到不同设备。
文件目录(稳定)
chroot可以更改用户的根目录位置
系统挂载点
查看文件内存innode位置
文件描述符文件拷贝填充命令 dd if=/dev/zero of=my.txt bs=104857 count=100
查看线程的文件描述符
lsof命令
查看 /proc/pid/fd
pagecache内核维护(内存容量,淘汰策略,数据持久化(延时))
文件丢失:由于内存中脏页还有持久化,系统异常断电,会造成数据丢失。
查看系统参数 sysctl -a|grep dirty
应用调用过程:
网络常用指令:
文件描述符:lsof -p
端口分析: netstat -natp
包分析:tcpdump -nn -i eth0 port xxx网络参数:
- backlog:连接排队等待的数量
- timeout: 连接超时时间
- tcpNoDelay:TCP发送会做缓存优化,会有一定的发生延迟
- keepalive:tcp探活
tcp内核数据丢失
tcp数据接受缓存区的大小是固定,但超过这个大小的数据会丢失。
所以tcp协议有窗口大小控制和拥塞控制
fileoutputstream VS bufferoutputstream
文件的随机读写
系统内核维护文件的pagecache,不同进程持有同一文件的不同文件描述符,文件描述符可以seek定位到文件的随机位置。
MMAP内存映射文件
应用
IO模:同步 异步 阻塞 非阻塞
BIO追踪系统调用 strace -ff -o out java xxx.class
注意:进程可以打开最大的文件描述符 ulimit -n = 1024 (root不受限制)
public class BioServer {
public static void main(String[] args){
try{
//创建serverSocket
//netstat -nalp 和 losf -p pid 可以查看到listern状态
ServerSocket serverSocket = new ServerSocket(9001);
//监听客户端的请求 堵塞
Socket accept = serverSocket.accept();
//获取字节流
InputStream inputStream = accept.getInputStream();
//把字节流 转换成 字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
//把字符流转换成 缓冲区字符流
BufferedReader br = new BufferedReader(inputStreamReader);
//开始读取内容
String msg ;
while ((msg = br.readLine()) != null){
System.out.println("读取内容为:" + msg);
}
System.out.println("读取完毕");
}catch (Exception ex){
log.error("读取异常",ex);
}
}
}
追踪到系统调用
socket=fd3 -> bind(fd3,8090) -> listen(fd3) -> BLOCK accept(fd3)=fd5 -> BLOCK recv(fd5)
public class NIOServer {
// 服务端通道
private ServerSocketChannel serverChannel;
// 选择器
private Selector selector;
// 默认服务绑定端口
private static int DEFAULT_BIND_PORT = 9000;
public NIOServer(int port) {
initServer(port);
}
private void initServer(int port) {
try {
// 开启一个服务通道
this.serverChannel = ServerSocketChannel.open();
// 将通道绑定到指定端口
this.serverChannel.bind( (port < 1 || port > 65535) ?
new InetSocketAddress(DEFAULT_BIND_PORT) :
new InetSocketAddress(port));
// 将通道设置为非阻塞模式 os->nonblocking
this.serverChannel.configureBlocking(false);
// 打开一个 IO 监视器:Selector
this.selector = Selector.open();
// 将服务通道注册到 Selector 上,并在服务端通道注册 OP_ACCEPT 事件
this.serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
} catch (IOException ioException) {
ioException.printStackTrace();
System.out.println("init exception: " + ioException);
}
}
public void startServer() throws InterruptedException {
while (true){
System.out.println("Selector 巡查 IO 事件---------------开始");
try {
int ioEventCount = this.selector.select(); // 此处以收集到所有 IO 事件
System.out.println("Selector 检测到:" + ioEventCount);
} catch (IOException ioException) {
ioException.printStackTrace();
break;
}
// 对各个 IO 事件做出对应的响应
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove(); //通过调用迭代器的 remove() 方法将这个键 key 从已选择键的集合中删除
try {
// 可接收连接 能注册SelectionKey.OP_ACCEPT事件的只有 ServerSocketChannel通道
if (key.isAcceptable()) {
System.out.println("监控到 OP_ACCEPT 连接事件");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 接受客户端连接
SocketChannel client = server.accept();
System.out.println("Accept connection from " + client);
client.configureBlocking(false); // 设置客户端通道非阻塞
// 为客户端通道注册 OP_WRITE 和 OP_READ 事件
SelectionKey clientKey = client.register(selector,
SelectionKey.OP_WRITE |
SelectionKey.OP_READ);
// 为客户端通道添加一个数据缓存区
ByteBuffer buffer = ByteBuffer.allocate(100);
clientKey.attach(buffer);
}
// 可读数据
if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
int read = client.read(output);
System.out.println("Read data from client: " + client);
System.out.println("------------MSG : " + output.toString());
}
// 可写数据
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer output = (ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
System.out.println("Write data to " + client);
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
Thread.sleep(2000);// 为了观察控制台打印数据
System.out.println("Selector 巡查 IO 事件---------------完成");
}
}
}
追踪到系统调用
socket=fd3 -> bind(fd3,8090) -> listen(fd3) -> nonblocking(fd3) -> 不阻塞循环 accept(fd3)=fd5 或则 -1 ->不阻塞循环 recv(fd5)返回-1 或 数据
验证到accept非阻塞
多路复用器:
c10k问题,一个或多个线程既要处理客户端连接,数据接受的工作。每次循环一次 O(n)复杂度,很多调用是无意义,还要涉及到系统调用。



