重学系列之内存和IO

文件类型

man [man,1,tcp…]:系统文档
前缀

  • -普通文件(图片,视频,可执行文件)

  • d:目录

  • b:块设备 硬盘驱动器,光驱或软驱等设备

  • l:连接

    • 软连接,类似于windows的快捷方式,实际上是通过ln命令将某一文件链接到另外一个文件,但是两个文件Inode号是相同的,删除指向文件就会报错 ln -s /aa/a.txt /bb/b.txt
    • 硬链接:磁盘中实际存在文件,虚拟地址中和原始的文件Inode号不同但是实际物理地址指向同一个地方,但是在删除指向文件的时候并不会报错。 ln /aa/a.txt /bb/b.txt

stat /b.txt 可以看到文件的元数据

  • c:字符设备 终端,里面存储不能被切割的字符型数据

  • s:socket

  • p:管道
    前面的输出是后面的输入,| 的前后都会单独启动子进程,将子进程的执行结果交给| 后面的命令中,当做输入。可以用块命令实现({echo $BASHPID ; read x;} | {cat ; echo $BASHPID; read y})
    $$ 优先级比 | 高,所以解释器先翻译执行$$,而$BASHPID也是代表当前Bash的进程ID,但是在解释器执行的时候优先级要比 | 低。

  • lsof -p / / / /(进程号) 查看某个进程打开的哪些文件($ 当前bash),也可以看到对应文件的文件描述符。

  • pcstat -pid $$ :内存中缓存页的状态(大小,编号等等)

  • read x:通过键盘数据复制变量

  • dd:拷贝文件到固定地址或者文件中 dd if=/dev/zero of=mydisk.img bs=1048576 count=100

  • proc(目录):映射的内核的虚拟文件系统,其中包含变量和所有程序的文件信息包括所有文件描述,开启的文件等等。

  • pcstat /bin/bash :可以看某个文件夹下被缓存数据页的数量和比例 佛挡杀佛

  • strace -ff -o out java : 追踪线程的内核操作指令[strace -ff -o out java OSFileIO 0
    指定文件名称可以跟踪这个文件所有相关的线程并打印其命令到文件中]

  • ldd:分析文件所依赖的所有源文件

  • 挂载:将文件挂载到另一个文件目录下,那对于当前目录的一个拓展,再不改变其文件结构的基础下,将新挂载的目录就会成为文件内容的新区域。
    losetup:losetup /dev/loop0 mydisk.image (把文件虚拟成块设备,模拟整个文件系统)
    mount:它用于挂载Linux系统外的文件

  • 重定向:Linux万物皆文件,所以对于每个命令和应用程序其实都是文件,而其中文件描述符(fd)可以用来该进程下不同线程对于IO的操作不同,0代表标准输入,1代表标准输出,2代表标准报错。每个进程或程序在运行的时候都有属于自己的VFS,所以进程之间是互相隔离的,只能通过内核来访问其他进程的信息。(read x 0> test.txt)
    重定向则是通过劫持每个进程fd来重新制定数据或者结果的来源或方向。<代表读,>代表写,<>代表读写。 重定向符号和fd中间不能有空白符或者其他符号。

追踪线程的内核指令生成的文件
线程内核操作指令

磁盘IO操作

  • OutputStream:在每次写入时,如果有新数据产生就会调用内核命令write写入内存。
  • BufferOutputStream:会存在一个8K的缓冲区,只有当8K的缓冲区存满后才会调用内核命令write,减少内核态和用户态的切换次数。

pageCache

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

网络IO

  • tcpdump -nn -i etho port 9090 :是一个用于截取网络分组,并输出分组内容的工具。
  • netstat inatp:查看网络端口状态
  • tcp:面向连接的可靠地传输协议
    • 连接:三次握手,内核开辟资源
  • socket:四元组(CIP CPORT + SIP SPORT),内核级的。
  • 在这里插入图片描述
    在这里插入图片描述
    ifconfig:可以看到某个网卡对于网络通信的限制,
    MTU:数据包大小
    MSS:数据内容大小

socket建立连接,服务端内核指令
socket建立连接,内核指令

BIO

模型图

BIO下的socket

socket 建立连接步骤

1.new Socket对象(fd3 = new Socket()),向内核请求生成一个文件描述符
2.进行本机端口号的bind(bingd(fd3,8090)),fd和Port
3.监听(listen fd3) 该端口上所有的事件
PS:以上步骤进行完才能通过netstat -natp 看到本机端口的状态,使用状态还是监听状态
4.阻塞式接受客户端连接(accept)

NIO

非阻塞IO模型,在服务端accept连接的时候不会像BIO一样,阻塞线程直到有客户端建立连接。而是返回数值为-1的文件描述符来表示此时未有需要建立建立连接的客户端,如果有则返回大于0的文件描述符,并保存该链接到集合中,后面可以循环遍历连接来读取数据。
在这里插入图片描述

多路复用器

同步:app自己进行IO 的RW
异步:kenel完成RW,程序没有访问IO,只访问了buffer。目前只有Windows上的IOCP是纯异步的
阻塞:BIO
非阻塞:NIO
无论什么多路复用器都要遍历所有IO,询问。只不过NIO是每次遍历都要用户态内核态的进行切换。
不负责读写,只负责记录是否有可读可写的状态,可读(客户端信息发送),可写(send_que这个队列是空的,能往里写)

select

多路复用器,有文件描述符数量限制。在遍历所有IO的时候只进行了一次的用户态内核态的切换,过程中把fds传给内核,内核根据这次传递的fds来遍历和修改状态。
在这里插入图片描述

poll

在遍历所有IO的时候只进行了一次的用户态内核态的切换,过程中把fds传给内核,内核根据这次传递的fds来遍历和修改状态。

epoll

Epoll在IO模型在建立Server端时,首先对于监听的端口申请到一个文件描述符,并使用该文件描述符绑定监听端口。在实现端口的监听后Server会调用epoll_create命令建立epoll相关信息(例如内核中用来保存每个链接FD的红黑树,用来保存红黑树的epoll的文件描述符RPFD等等epoll相关的信息),其中EPFD用来保存被监听的FD所建立的所有新FD的数值,状态等等信息。创建了epoll所必须的信息后,通过epoll_ctl命令将监听端口的FD加入到红黑树中(每当有新的链接建立即分配新的FD时都会建立一个新的FD链表由红黑树进行数据的添加,链表主要是用来表示是否有需要读取的数据)。当Clinent端有新连接是都会在对应的红黑树中增加相应的文件描述符,如果有新数据进行传输时会将实际是数据写入分配给该文件描述符的buffer中还会将该连接的FD保存到对应的链表中,Server在进行读取时不用遍历所有已经建立连接的IO而是遍历链表中的所有IO。在这里插入图片描述
在这里插入图片描述

java 多路复用器封装

package com.bjmashibing.system.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class SocketMultiplexingSingleThreadv1 {

    //马老师的坦克 一 二期
    private ServerSocketChannel server = null;
    private Selector selector = null;   //linux 多路复用器(select poll    epoll kqueue) nginx  event{}
    int port = 9090;

    public void initServer() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(port));


            //如果在epoll模型下,open--》  epoll_create -> fd3
            selector = Selector.open();  //  select  poll  *epoll  优先选择:epoll  但是可以 -D修正

            //server 约等于 listen状态的 fd4
            /*
            register
            如果:
            select,poll:jvm里开辟一个数组 fd4 放进去
            epoll:  epoll_ctl(fd3,ADD,fd4,EPOLLIN
            实际上是懒加载,只有在实际使用的时候才会真的去调用命令进行监听FD的注册
             */
            server.register(selector, SelectionKey.OP_ACCEPT);


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        initServer();
        System.out.println("服务器启动了。。。。。");
        try {
            while (true) {  //死循环

                Set<SelectionKey> keys = selector.keys();
                System.out.println(keys.size()+"   size");


                //1,调用多路复用器(select,poll  or  epoll  (epoll_wait))
                /*
                select()是啥意思:
                1,select,poll  其实  内核的select(fd4)  poll(fd4)
                2,epoll:  其实 内核的 epoll_wait()
                *, 参数可以带时间:没有时间,0  :  阻塞,有时间设置一个超时
                selector.wakeup()  结果返回0

                懒加载:
                其实再触碰到selector.select()调用的时候触发了epoll_ctl的调用

                 */
                while (selector.select() > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();  //返回的有状态的fd集合
                    Iterator<SelectionKey> iter = selectionKeys.iterator();
                    //so,管你啥多路复用器,你呀只能给我状态,我还得一个一个的去处理他们的R/W。同步好辛苦!!!!!!!!
                    //  NIO  自己对着每一个fd调用系统调用,浪费资源,那么你看,这里是不是调用了一次select方法,知道具体的那些可以R/W了?
                    //幕兰,是不是很省力?
                    //我前边可以强调过,socket:  listen   通信 R/W
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove(); //set  不移除会重复循环处理
                        if (key.isAcceptable()) {
                            //看代码的时候,这里是重点,如果要去接受一个新的连接
                            //语义上,accept接受连接且返回新连接的FD对吧?
                            //那新的FD怎么办?
                            //select,poll,因为他们内核没有空间,那么在jvm中保存和前边的fd4那个listen的一起
                            //epoll: 我们希望通过epoll_ctl把新的客户端fd注册到内核空间
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);  //连read 还有 write都处理了
                            //在当前线程,这个方法可能会阻塞  ,如果阻塞了十年,其他的IO早就没电了。。。
                            //所以,为什么提出了 IO THREADS
                            //redis  是不是用了epoll,redis是不是有个io threads的概念 ,redis是不是单线程的
                            //tomcat 8,9  异步的处理方式  IO  和   处理上  解耦
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel client = ssc.accept(); //来啦,目的是调用accept接受客户端  fd7
            client.configureBlocking(false);

            ByteBuffer buffer = ByteBuffer.allocate(8192);  //前边讲过了

            // 0.0  我类个去
            //你看,调用了register
            /*
            select,poll:jvm里开辟一个数组 fd7 放进去
            epoll:  epoll_ctl(fd3,ADD,fd7,EPOLLIN
             */
            client.register(selector, SelectionKey.OP_READ, buffer);
            System.out.println("-------------------------------------------");
            System.out.println("新客户端:" + client.getRemoteAddress());
            System.out.println("-------------------------------------------");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void readHandler(SelectionKey key) {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        int read = 0;
        try {
            while (true) {
                read = client.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else {
                    client.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();

        }
    }

    public static void main(String[] args) {
        SocketMultiplexingSingleThreadv1 service = new SocketMultiplexingSingleThreadv1();
        service.start();
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值