网络编程(3)四种IO模型

1.socket 中有四种IO模型

  • 阻塞型IO
    • 最常用/最简单/效率低
    • 函数本身不具备阻塞属性,而是由于文件描述符本身导致函数阻塞。
    • 在默认情况下Linux建立的socket套接都是阻塞的
  • 非阻塞
    • 可以设置进程非阻塞在IO操作上,需要轮询
    • 占用CPU资源较大
  • 多路复用IO
    • 同时对多个IO进行操作
    • 可以设置在规定的时间内检测数据是否到达
  • 信号驱动型IO
    • 属于一步通信方式
    • 当socket中有数据到达时,通过发送信号告知用户

1.1阻塞型IO

  • 读阻塞
    • 当套接字接收缓冲区中没有数据可以读取时调用 如 read/recv/recvfrom就会导致阻塞
    • 当有数据到达时,内核便会去唤醒进程,通过read等函数来访问数据
    • 如果进程阻塞过程中意外,那么进程将永远阻塞下去。
  • 写阻塞
    • 发生写阻塞的机会比较少,一般出现在写缓冲区无法写入即将写入的数据时
    • 当无法写入数据时便会进入阻塞等待
    • 一旦发送的缓冲区拥有足够的空间,则内核会唤醒对应的进程进行写入操作
    • 而UDP协议中并不存在发送缓冲区满的情况,UDP套接字执行写操作时永远不会发生阻塞。

1.2非阻塞IO

  • 如果有一个IO操作不能马上完成则系统则会让我们的进程进入睡眠状态等待
  • 当我们将一个套接字设置为非阻塞模式时,则系统不会让我们的进程进入睡眠等待而是直接返回错误
  • 当一个应用使用了非阻塞模式的套接字,他需要使用循环来不断检查文件描述如是否有数据可读
  • 应用程序不同循环判断将会消耗非常大的CPU资源,一般不推荐使用

1.3非阻塞实现方法:

当我们一开始建立一个套接字描述符时,系统内核会默认设置为阻塞型IO,我们可以使用函数来设置套接字为非阻塞状态。

  • fcntl
  • fcntl ( 文件描述词操作 )
        头文件:
            #include <unistd.h>
            #include <fcntl.h>
        定义函数 :
            int fcntl(int fd, int cmd);
            int fcntl(int fd, int cmd, long arg);
            int fcntl(int fd, int cmd, struct flock * lock);
        参数分析:
            fd --> 需要设置的文件描述符
            cmd --> 设置的功能
                F_DUPFD 用来查找大于或等于参数 arg 的最小且仍未使用的文件描述词, 并且复制参数 fd 的文件描述词. 执行成功则返回新复制的文件描述词. 请参考 dup2(). 
                F_GETFD 取得 close-on-exec 旗标. 若此旗标的 FD_CLOEXEC 位为 0, 代表在调用 exec()相关函数时文件将不会关闭.
                F_SETFD 设置 close-on-exec 旗标. 该旗标以参数 arg 的 FD_CLOEXEC 位决定.
                F_GETFL 取得文件描述词状态旗标, 此旗标为 open()的参数 flags.
                F_SETFL 设置文件描述词状态旗标, 参数 arg 为新旗标, 但只允许 O_APPEND、O_NONBLOCK 和
                O_ASYNC 位的改变, 其他位的改变将不受影响.
                F_GETLK 取得文件锁定的状态.
                F_SETLK 设置文件锁定的状态. 此时 flcok 结构的 l_type 值必须是 F_RDLCK、F_WRLCK 或
                F_UNLCK. 如果无法建立锁定, 则返回-1, 错误代码为 EACCES 或 EAGAIN.
                F_SETLKW 同 F_SETLK 作用相同, 但是无法建立锁定时, 此调用会一直等到锁定动作成功为止. 若在等待锁定的过程中被信号中断时, 会立即返回-1, 错误代码为 EINTR. 参数 lock 指针为 flock 结构指针
        返回值:
            成功返回 0 
            失败返回 -1

    注意结构体定义:

  • struct flcok
    {
        short int l_type; //锁定的状态
        short int l_whence; //决定 l_start 位置
        off_t l_start; //锁定区域的开头位置
        off_t l_len; //锁定区域的大小
        pid_t l_pid; //锁定动作的进程
    };
    l_type 有三种状态:
        F_RDLCK 建立一个供读取用的锁定
        F_WRLCK 建立一个供写入用的锁定
        F_UNLCK 删除之前建立的锁定
    l_whence 也有三种方式:
        SEEK_SET 以文件开头为锁定的起始位置.
        SEEK_CUR 以目前文件读写位置为锁定的起始位置

    操作例子:

    int socket_fd = socket(...........); // 创建一个套接字描述符
    int state = fcntl(socket_fd , F_GETFL , 0) ; // 获得当前描述符的旗标
    state |= O_NONBLOCK  ;  // 在原基础上增加非阻塞属性
    fcntl(scoket_fd , F_SETFL  ,  state ); // 把配置的好的旗标重新设置回描述符中

2.多路复用

  • 当应用程序同时处理多路数据的输入或输出时,若采用非阻塞模式,将达不到预期的效果
  • 如果采用非阻塞模式,对多个输入进行轮询可以实现,但CPU的消耗非常大
  • 如果使用多进程/多线程,将产生进程与线程同步互斥的问题使得程序变得非常复杂
  • 使用多路复用则是最佳的选择,他的基本思想是:
    • 先把所有需要监听等待的文件描述符添加到一个集合中,
    • 在规定的时间内等待集合中所有描述符数据的变化,如果超时则跳出或进入下一次等待
    • 如果在规定时间内文件描述符的数据有发生变化则把其他没有数据变化的描述符提出到集合之外等待进行下一次的等待状态。

1.接口API

select (I/O 多工机制/用来等待文件描述词状态的改变 )
    头文件:
        #include <sys/time.h>
        #include <sys/types.h>
        #include <unistd.h>
    定义函数:
        int select(int n, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);
    参数说明:
        n --> 当前最大的描述符+1
       readfds -->  读取描述如组 
       writefds --> 写入描述如组 
       exceptfds --> 其他描述如组 
       timeout --> 超时时间
   返回值:
       如果参数 timeout 设为 NULL 则表示 select ()没有 timeout.
       超时 返回 0 
       发生错误 返回 -1 
         
    注意:
    struct timeval
    {
        time_t tv_sec;
        time_t tv_usec;
    };   
    
    FD_CLR(inr fd, fd_set* set); 用来清除描述词组 set 中相关 fd 的位
    FD_ISSET(int fd, fd_set *set);  用来测试描述词组 set 中相关 fd 的位是否为真
    FD_SET(int fd, fd_set*set);  用来设置描述词组 set 中相关 fd 的位
    FD_ZERO(fd_set *set);  用来清除描述词组 set 的全部位

2.多路复用关键代码:

 // 配置超时时间
        struct timeval time_val ;
        time_val.tv_sec = 5 ;
        time_val.tv_usec = 0 ;

        // 设置多路复用集合
        fd_set set ;
        FD_ZERO(&set); // 清空 集合
        FD_SET(connect_fd , &set); // 添加 套记字到集合中
        FD_SET(STDIN_FILENO , &set); // 添加标准输入到集合中

        // 找到描述符最大值
        max_fd = connect_fd > STDIN_FILENO ? connect_fd : STDIN_FILENO ;
        // 等待描述符状态变化并设置超时 5秒
        select(max_fd+1, &set , NULL ,NULL, &time_val);

        // 等待客户端发话 
        bzero(msg , 1024);
        if(FD_ISSET(STDIN_FILENO, &set)) //检查时候标准输入描述符有数据到达
        {
            fgets(msg , 1024 , stdin);  // 获取标准输入数据
            send(connect_fd , msg , strlen(msg), 0 ); // 发送数据
        }

        if(FD_ISSET(connect_fd, &set)) // 检查是否套接字描述符有数据到达
        {
            recv(connect_fd , msg , 1024 , 0); // 从套接字获得数据并发送
            printf("msg :%s \n");
        }

3.信号驱动

信号驱动其实就是涉及到的Systrm_V的信号,通过监听文件描述符的状态(是否有产生信号), 当文件描述符有数据到达时就会产生一个IO信号(SIGIO),来通知用户进行IO操作。

特点:

  • 信号驱动一般用于UDP协议中,很少用于TCP协议中, 因为TCP协议中会有多次IO状态的改变,所以会有非常多的SIGIO信号产生,非常难捕捉到哪一个时数据到达产生的。
  • 由于数据变化时,产生SIGIO信号,所以必须体现设置好信号捕获,并设置其对应的响应函数。
  • 必须给文件描述符设置信号触发模式

3.1操作步骤:

  1. 建立套接字
  2. 绑定端口和地址信息
  3. 设置捕获信号并设置响应函数
  4. 设定套接字拥有者,用于捕获信号的到来
  5. 给套接字添加信号触发模式

3.2关键代码:

    // 设置套接字的拥有者
    fcntl(sock_fd ,F_SETOWN, getpid());

    // 添加信号触发
    int state;
    state = fcntl(sock_fd,F_GETFL);
    state |= O_ASYNC;
    fcntl(sock_fd,F_SETFL,state);
    
    while(1)// 循环挂起不让程序退出
    {   
        printf("__%d__\n" , __LINE__);
        pause();
    }

<think>好的,我现在需要帮助用户了解Android中的WakeLock,包括它的详细用法、原理、使用场景和实现机制。首先,我应该回顾一下用户提供的引用内容,看看里面有哪些关键点需要涵盖。 根据引用1,WakeLock用于在时保持设备唤醒,比如接收微信消息。引用2提到WakeLock阻止AP进入休眠,确保关键代码运行,并给出了代码示例。引用3解释了功耗的来源和WakeLock的作用,以及滥用可能的问题。引用4详细介绍了WakeLock的分类和级别,比如永久锁、超时锁等。 接下来,我需要将这些信息组织成一个结构清晰的回答。按照用户的要求,分点说明原理、使用场景、使用方法和注意事项。同时,确保使用正确的中文和LaTeX格式,比如行内公式用$...$,独立公式用$$...$$。不过,可能WakeLock的内容里数学公式较少,但如果有相关算法或时间计算可能需要应用。 然后,生成相关问题,这需要根据内容提出用户可能感兴趣的问题,比如原理机制、常见问题、优化方法等。确保问题与回答内容相关,并且有助于用户进一步学习。 需要注意的是,用户可能希望了解WakeLock的不同类型和级别,以及如何正确使用避免功耗问题。因此,在回答中要详细说明分类和使用时的注意事项,比如及时释放锁,选择合适的级别。 另外,代码示例是必须的,用户提供的引用中有代码片段,需要正确展示,并解释每个参数的作用,比如PowerManager的不同标志位。 最后,检查引用标识是否正确添加,每个引用段落末尾自然地加上对应的标识符,确保引用来源准确。同时,避免使用Markdown格式,保持回答的纯文本但结构清晰。</think>### Android WakeLock 详解 #### 一、核心原理 WakeLock 是 Android 系统提供的电源管理机制,通过控制硬件(如 CPU、幕)的唤醒状态,确保关键任务在后仍能执行。其核心原理如下: 1. **硬件控制**:通过操作系统内核接口,直接干预硬件电源状态(如保持 CPU 运行或幕常亮)[^3]。 2. **锁分级**:根据需求选择不同级别的锁,例如: - $PowerManager.PARTIAL\_WAKE\_LOCK$:仅保持 CPU 运行 - $PowerManager.FULL\_WAKE\_LOCK$:同时保持幕和键盘亮起[^4] 3. **引用计数机制**:非计数锁(默认)需一次释放,计数锁需多次匹配的 `acquire()` 和 `release()`[^4]。 #### 二、使用场景 | 场景类型 | 具体示例 | 推荐锁类型 | |-------------------|----------------------------|--------------------------| | 后台服务 | 音乐播放、文件下载 | `PARTIAL_WAKE_LOCK` | | 即时通信 | 消息通知点亮屏幕 | `ACQUIRE_CAUSES_WAKEUP` | | 传感器持续监控 | 健康监测、GPS 定位 | `SCREEN_DIM_WAKE_LOCK` | #### 三、代码实现 ```java // 1. 获取 PowerManager 实例 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 2. 创建 WakeLock 对象(带超时自动释放) WakeLock wakeLock = pm.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "MyApp::MyWakeLockTag"); // 3. 获取锁(设置 10 分钟超时) wakeLock.acquire(10 * 60 * 1000); // 4. 关键任务执行完成后释放 if (wakeLock.isHeld()) { wakeLock.release(); } ``` **关键参数说明**: - `ON_AFTER_RELEASE`:释放后短暂保持幕亮起[^2] - 超时时间单位:毫秒(建议不超过 30 分钟) #### 四、实现机制 Android 通过以下层级实现 WakeLock: $$ \text{应用层} \xrightarrow{Binder} \text{PowerManagerService} \xrightarrow{JNI} \text{内核层(/sys/power/wake_lock)} $$ 当锁被持有时,系统会向 `/sys/power/wake_lock` 写入标记,阻止进入低功耗状态[^3]。 #### 五、注意事项 1. **功耗控制**:滥用 WakeLock 可能导致待机功耗增加 20%-40%[^3] 2. **生命周期绑定**:建议在 `onResume()` 中申请,`onPause()` 中释放 3. **Android 6.0+ 限制**:Doze 模式会强制中断非白名单应用的 WakeLock[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值