网络IO——BIO、NIO、多路复用器

这篇博客深入探讨了网络IO的三种模型:BIO、NIO和多路复用器,详细阐述了它们的工作原理、优缺点以及在Linux系统下的实现。BIO在连接请求时会产生阻塞,而NIO通过非阻塞IO避免了这个问题,但需要循环检查数据。多路复用器如select、poll、epoll则进一步减少了系统调用的次数,提高效率。同时,文章也涵盖了进程、线程和文件描述符的基础知识,并提供了IO面试的相关问题解析。
摘要由CSDN通过智能技术生成

IO

计算机硬件和运行原理

  • 计算机启动,首先启动内核程序;
  • 内存分为“内核空间”、“用户空间”,内核程序为其他程序提供系统调用;
  • 在保护模式下,用户空间中的程序不能调用内核程序,同时CPU中定义了部分内核专用寄存器,用户程序不能调用;
  • 只有通过中断,实现系统调用:将内核程序中的部分功能暴漏给用户程序;

在这里插入图片描述

  • 当程序使用系统调用时,编译器会自动将调用函数转译成:int x80,的形式,X80是system call的调用入口

    cpu在执行用户程序,读到INT指令后,自动将原程序的现场存入内存—现场保护——>

    调用对应的内核程序——>

    实现用户态和内核态切换。

Linux 进程、线程、文件描述符的底层原理

进程

  • 对于Linux操作系统而言,进程是一个数据结构,主要组成为:
struct task_struct{
   
    long state;     //进程状态
    struct mm_struct *mm;   //虚拟内存结构体【重要】---载入资源和可执行文件的地方
    pid_t pid;  //进程PID
    struct task_struct *parent;  //指向父进程的指针
    struct list_head childern;   //子进程列表
    struct fs_struct *fs;        //曾放文件系统信息的指针
    struct files_struct *files;  //一个数组,包含所有该进程打开的文件指针【重要】文件描述符
}

文件描述符

  • 在linux下一切皆文件,文件描述符是内核为了高效的管理已经被打开的文件所创建的索引,它是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都是通过文件描述符完成的。
  • 进程是通过文件描述符(file descriptors 简称fd)来访问文件的,整数。每个进程刚启动的时候,默认有三个文件描述符,分别是:files[0] 标准输入流,files[1] 标准输出流,files[2] 标准错误流。再打开一个新的文件的话,它的文件描述符就是3。
    在这里插入图片描述
  • 输入重定向、输出重定向、错误重定向:将file[0]指向一个文件,程序就会从指定文件中读取;将file[1]指向一个文件,程序就会将数据输出到指定文件中;
  • 管道符:把进程输出流和另一个进程的输入流连接在一起,形成“管道”;
    在这里插入图片描述
  • 文件描述符的创建:进程获取文件描述符最常见的方法就是通过系统函数open或create获取,或者是从父进程继承;
  • 进程之间的文件描述符互不干扰!进程和内部多个线程共用文件描述符
  • 在网络IO中,可以将fd理解为一个指向内存空间的指针,系统方法依靠fd,通过register等方法找到目标和要保存目标的内核空间。

线程

  • 无论线程还是进程,都是用task_struct结构表示的,唯一的区别就是共享的数据区域不同


在这里插入图片描述

  • 由于父进程和子线程之间共享部分内存空间,所以多线程需要使用,避免同时对一块内存区域进行操作,造成数据混乱;
  • 使用多线程优势:现实中数据共享并发的情况更常见~

网络IO的演变过程

BIO

1、服务端代码

public class TestSocket {
    public static void main(String[] args) throws IOException {
        //服务端监听端口
        ServerSocket server = new ServerSocket(8090);
        System.out.println("step1");
        while(true){
            //可能产生阻塞:用户端一直不发信息
            Socket client = server.accept();
            System.out.println("step2 "+client.getPort());

            //将client读取抛到另外一个线程中,避免当前client一直不发数据,
            //下面while(true)中readLine阻塞,导致无法和其他client建立连接
            new Thread(new Runnable() {
                //用于接收client传入数据
                Socket ss;
                //读入client的输入
                public Runnable setSS(Socket s){
                    ss = s;
                    return this;
                }
                @Override
                public void run() {
                    try {
                        //获取输入流
                        InputStream in = ss.getInputStream();
                        BufferedReader reader = new BufferedReader((new InputStreamReader(in)));
                        while(true){
                            //用户一直不发东西,可能阻塞
                            System.out.println(reader.readLine());
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.setSS(client)
            ).start();
        }
    }
}

2、用strace追踪java程序有多少线程,每个线程对内核产生了哪些系统调用

strace -ff -o out /usr/java/j2sdk1.4.2_18/bin/java  TestSocket  //追踪java程序中有多少线程
//后面是要启动的java程序
//启动java程序后,查看文件详情
ll

从8211~8218都是java底层线程(多线程),8211是主线程main,out.8211文件中记录了java程序在系统调用过程中和内核的交互记录

3、主线程执行过程

vi out.8211    //查看8211主线程的输出内容,如下图

阻塞在accept处,等待client连接到达。下图,此时8211端口状态为listen,Foreign Address可以是任何IP地址任何port。
在这里插入图片描述
4、用nc模拟client连接

nc localhost 8090
tail -f out.8211   //查看out.8211文件记录

accept拿到tcp连接,继续向下执行;通过clone,启动一个线程–8281,主线程8211继续等待下一个client的连接请求;
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值