io与select介绍

文章目录

  • 什么是`IO`
  • 观察程序启动流程
    • `strace 可执行文件`
    • **调用`main`函数前操作系统会进行什么操作**
    • **`Linux`内核`kernel`没有`main`函数,他是如何执行的**
    • 服务器常用`IP`地址`IADDR_ANY`、`127.0.0.1`、虚拟机网卡指定`IP`之间的区别
  • 服务器基本框架的简单理解
  • 注意一种情况
  • 阻塞与非阻塞
  • 实现多个客户端连接,并且发送数据
    • 多线程与多进程实现
    • `io`多路复用
      • `select`
        • 函数原型
        • 小`demo`
  • **细节注意点:**

什么是IO

  • IO:指既能收数据也能发数据。

    Linuxsocketfd(文件描述符)就是一种IO

观察程序启动流程

strace 可执行文件

  • 使用该条命令可以查看到进程是如何启动的

    是由bash所在的进程调用execve来启动的进程

调用main函数前操作系统会进行什么操作

  • 加载可执行文件:操作系统会将程序的可执行文件从磁盘读取到内存中,并进行一些必要的校验工作,例如校验文件格式、校验文件权限等。

  • 分配内存:操作系统会为程序分配内存空间,包括代码段、数据段、堆和栈等。代码段存储程序的指令,数据段存储程序的全局变量和静态变量,堆和栈用于动态内存分配和函数调用。

  • 解析动态链接库:如果程序使用了动态链接库,操作系统会在加载程序时解析这些库,并将它们链接到程序中。

  • 初始化进程环境:操作系统会初始化进程的环境,包括设置进程的根目录、文件描述符、信号处理器等。

  • 启动程序:最后,操作系统会定位main函数的入口地址,并跳转到这个地址开始执行程序。

Linux内核kernel没有main函数,他是如何执行的

通过硬件引导程序(bootloader)启动

  • 在计算机启动时,硬件会首先加载硬件引导程序(bootloader),它通常位于硬盘的第一个扇区(也称为主引导记录)。硬件引导程序的作用是加载内核映像(kernel image)到内存中,并跳转到内核的入口点开始执行。
  • Linux内核中,入口点的符号名为_start。当硬件引导程序将内核映像加载到内存中后,会将控制权转移给_start符号所在的地址,从而开始执行内核的初始化过程。
  • 在内核的初始化过程中,会进行各种硬件初始化、内存管理、进程管理等操作,最终启动一个称为init的用户进程作为系统的第一个进程,并将控制权转交给它,让它继续进行系统初始化和启动其他进程。

服务器常用IP地址IADDR_ANY127.0.0.1、虚拟机网卡指定IP之间的区别

  • IADDR_ANY:值为0,用点分十表达则为0.0.0.0,表示绑定本机随便选一个网卡的ip
  • 127.0.0.1:表示监听监听本地回环接口,只能用于本机访问
  • 指定IP:绑定指定的本地网卡IP地址

例子:远程访问非本机的Mysql的服务失败,在确定了其防火墙是关闭的情况,可以考虑是其配置文件的bind-address默认是设置的127.0.0.1只允许其本机访问,所以可以将其设置为0.0.0.0,监听所有可用的网络接口,这样也许就能解决无法访问远程mysql服务问题

服务器基本框架的简单理解

socket、bind、listen

  • 去吃饭,进门找到接引人(listen),他会将你带去找到点菜(发送数据)的人(accept

注意一种情况

send返回>0不等于发送成功,(只是代表将数据发送到了协议栈)

阻塞与非阻塞

简单的服务器框架socket、bind、listen、accept阻塞在accept阻塞的主要原因是监听的文件描述符默认是阻塞的

实现多个客户端连接,并且发送数据

多线程与多进程实现

来一个请求accept之后开辟一个线程去处理任务

**好处:**逻辑简单,线程只为一个fd服务,不用担心其他线程or进程来处理

**缺点:**代价太大

io多路复用

如何单线程实现多个客户端同时连接?

  • 不采取任何措施下多个客户端的连接listenfdio有效)是没有问题的

    但是不知道什么时候执行accept

    (后续的业务处理)不知道什么时候recv,不知道什么时候send

  • io多路复用就是检测io是否有事件(事件:描述符上是否有可读可写)

  • 如何标识一个IO的事件->可读(有还是没有)可写(有还是没有)->select用一个bit位表示

select

如何理解select

  • 一个人(server)喜欢去东莞,他跟很多技师(fd)关系很好,经常去,但是每次去的时候都要访问所有技师(fd)关于以下信息

    1. 晚上有没有时间?(io可读)
    2. 是否还愿意?(io可写)
  • 后来他找了一个秘书(select),他会叫她先去东莞采集完这些信息后再去,这个秘书的主要职责就是看这些技师(fd)是否可以办正事(recv\send

select接口介绍

IO多路复用是一种同时监控多个文件描述符(包括套接字)的技术,它允许一个进程可以同时等待多个IO操作完成,而不是阻塞在单个IO操作上。其中一个常用的IO多路复用技术是select

select函数可以同时监视多个文件描述符,等待其中任何一个文件描述符就绪(可读、可写或异常),然后通知应用程序进行相应的操作。使用select可以避免阻塞在单个IO操作上,提高程序的效率和响应速度。

在使用select函数时,需要准备一个文件描述符集合,包括需要监视的所有文件描述符,然后将该集合传递给select函数。select函数会不断地监视这些文件描述符,直到其中任何一个文件描述符就绪,然后返回就绪文件描述符的数量,并更新原始的文件描述符集合。

在编写基于select的程序时,需要注意以下几点:

  1. 设置文件描述符为非阻塞模式,以避免阻塞在单个IO操作上。
  2. 每次调用select时,需要重新设置原始的文件描述符集合,以避免之前的就绪文件描述符被遗漏。
  3. 在处理就绪文件描述符时,需要注意其对应的IO操作是否已经完成,以避免出现错误的操作。

函数原型

#include 

int select(int nfds, 				
           fd_set *readfds, 
           fd_set *writefds, 
           fd_set *exceptfds, 
           struct timeval *timeout
          );

  • nfds:需要监视的文件描述符集合中所有文件描述符的最大值加1。
  • readfds:监视可读性的文件描述符集合。
  • writefds:监视可写性的文件描述符集合。
  • exceptfds:监视异常性的文件描述符集合。
  • timeoutselect() 超时时间,设置为 NULL 表示阻塞等待,设置为 0 表示立即返回,设置为大于 0 的值表示等待指定时间后返回。

demo

只判断了io是否可读

  • 注册select读检测

    // socket bind listen..
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    
    fd_set rfds, rset;
    FD_ZERO(&rfds);   		// 清空bit位
    FD_SET(sockfd, &rfds);  // 设置listenfd位
    int maxfd = sockfd; 	// 设置最大fd
    int clientfd = 0;		
    
  • select

    while (1) {  // master
        rset = rfds;  // 设置副本 保证同一循环下检测队列不会改变
        int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
    
        if (FD_ISSET(sockfd, &rset)) { // 判断是否有客户端连接
            clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
            printf("accept: %d\n", clientfd);
            FD_SET(clientfd, &rfds);
            if (clientfd > maxfd) maxfd = clientfd;
    
            if (-- nready == 0) continue;
        }
    
        int i = 0;
        for (i = sockfd+1; i <= maxfd;i ++) {  
    
            if (FD_ISSET(i, &rset)) {  // 判断客户端io是否可读
    
                char buffer[BUFFER_LENGTH] = {0};
                int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
                if (ret == 0) {
                    close(clientfd);
                    break; 
                }
    
                printf("ret: %d, buffer: %s\n", ret, buffer);
    
                send(clientfd, buffer, ret, 0); 
    
        	}
    
    	}
    }
    

细节注意点:

关于accept所做的事情

  • listenfd在监听到客户端连接后执行accept会清空掉其描述符上客户端写进来的数据。这样只有在下次客户端到来的时候listenfd描述符上才会有io提示,select才会刚好在客户端连接的时候检测到listenfd的动静。

    如果使用了select来检测listenfd,之后没有写accept,则select每次循环都会检测listenfd有数据

关于select的读写集合设置副本的原因

  • 是为了在同一循环中保证一开始想要检测的固定数量的文件描述符io不会变多或变少

maxfd设置为什么要+1

  • 内核中轮询的代码就类似于下面这种

    for(int i = 0; i < maxfd_; i++){
        //
    }
    

    这种判断是<要想包含maxfd那就必须+1

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务

你可能感兴趣的:(服务器,linux,bash,运维)