网络编程-Linux下四种模型及本地套接字

Linux下四种模型:

阻塞式IO 非阻塞式IO 信号驱动IO(了解) IO多路复用(帮助TCP实现并发)

linux下的四种IO模型假设:

假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?

阻塞式IO: 进到房间陪孩子一起睡觉,孩子醒了吵醒妈妈,不累,但是不能干别的了

非阻塞式IO: 时不时进房间看一下:简单,空闲时间还能干点别的,但是很累

信号驱动IO: 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:互不耽误

假设妈妈有三个孩子, 分别在不同房间睡觉, 需要及时检测每个房间信息, 如何做?

阻塞式IO: 阻塞处理多条路, 得不到该目的;

非阻塞IO: 一直轮询, 大量占用CPU;

多线程多进程, 或IO多路复用, 可以做到

Linux下四种模型的特点:

1. 阻塞式IO: 最简单, 最常用,效率低、不浪费cpu

阻塞I/O 模式是最普遍使用的I/O 模式

系统默认状态,套接字建立后所处于的模式就是阻塞I/O 模式。

目前学习的读写函数中会发生阻塞相关函数如下:

· read、recv、recvfrom

读阻塞--》需要读缓冲区中有数据可读,读阻塞才会解除

· write, send

写阻塞--》阻塞就是写入数据时遇到缓冲区满了的情况,需要等待缓冲区有空间后才能继续写入, 所以写阻塞发生的情况比较少.

· accept connect

需要注意的是使用UDP时,UDP没有发送缓存区 ,则sendto没有阻塞

1)原因:

sendto不是阻塞函数,本身udp通信不是面向链接的,udp无发送缓冲区,即sendto没有发送缓冲区,send是有发送缓存区的,即sendto不是阻塞函数。

2)UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在缓冲区满的情况,在UDP套接字上进行写操作永远不会阻塞。

udp与tcp缓存区 (仅作为了解)

UDP是一种无连接的传输协议,它不保证数据的可靠性和顺序性, 所以不需要考虑连接和缓存,而是将数据尽快发送出去,不关心数据是否到达目标主机或者是否按照发送顺序到达. 但是UDP有接受缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包。

TCP是一种面向连接的传输协议,有发送缓存区和接收缓存区, 如果发送频率过快, 且内容小于发送缓存区的大小 , 可能会导致多个数据的粘包。如果发送的数据大于发送缓存区的剩余大小,send将会阻塞, 在阻塞期间,send函数会自动拆分数据包发送,直到所有数据都被发送完毕或者发送缓冲区的空间不足以继续发送为止, 这就是拆包。

UDP不会造成粘包和拆包, TCP不会造成丢包

UDP_数据报: 本质是独立的包, 有边界;

TCP_流,:本质是字节流形式, 一帧一帧发送,且每一帧没有边界,一帧丢失会重新补发;

2. 非阻塞式IO :可以处理多路IO;需要轮询,大量浪费CPU资源

1. 当一个应用程序使用了非阻塞模式的套接字,则它需要使用一个循环来不停的测试是否一个文件描述符有数据可读(称做polling)

2. 应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作 ,所以说一般不适用

3.当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”

将recv设置为非阻塞时, recv在接收缓存区内, 未拿到客户端发送来的数据, 那么recv就会报错 , 所以会一直打印 recv is err: 资源不可用

fcntl设置文件描述符的属性

声明: int fcntl (int fd, int cmd,  ...arg);
头文件: #include<fcntl.h>      #include<unistd.h>
功能:设置文件描述符的属性
参数:fd:文件描述符
         cmd: 操作功能选项 (可以定义个变量,通过vi -t F_GETFL 来找寻功能赋值 )
          F_GETFL:获取文件描述符的原有的状态信息 
           //不需要第三个参数,返回值为获取到的属性
          F_SETFL:设置文件描述符的状态信息 - 需要填充第三个参数
         //需要填充第三个参数  O_RDONLY, O_RDWR ,O_WRONLY ,O_CREAT
          O_NONBLOCK 非阻塞   O_APPEND追加
          O_ASYNC 异步        O_SYNC  同步 
    
          F_SETOWN:    可以用于实现异步通知机制。
          //当文件描述符上发生特定事件时(例如输入数据到达),内核会向拥有该  文件描述符的进程发送 SIGIO 信号(异步),以便进程能够及时处理这些事件。

        arg:文件描述符的属性      --------------------同上参数


返回值: 特殊选择:根据功能选择返回 (int 类型)   
            其他:  成功0   失败: -1;

使用:  int flag;
 // 1.获取该文件描述符0 (标准输入) 的原属性 : 标准输入原本具有阻塞的功能  
int flag = fcntl(0, F_GETFL); //获取文件描述符原有信息后,保存在flag变量内
 //2.修改对应的位nonblock(非阻塞)
int flag |= O_NONBLOCK;  ( flag = flag | O_NONBLOCK)
// 3. 将修改好的属性写回去 (0 标准输入 -- 阻塞  改为  非阻塞)
 fcntl (0, F_SETFL, flag); //文件描述符   设置状态  添加的新属性

实现: 将输入改为非阻塞功能;

int main(int argc, char const*argv[])
{
    char buf[128];
    while(1)
    {
        //先获取原文件描述符的原属性
        int flag = fcntl(0,F_GETFL);
        //修改,添加权限
        flag = flag | O_NONBLOCK;
        //将修改好的权限 写回去
        fcntl(0,F_SETFL,flag);

        fgets(buf,sizeof(buf),stdin);
        sleep(1);
        printf("buf: %s\n",buf);
    }
    return 0;

3. 信号驱动IO -异步通知模式,底层驱动支持 (非重点)

特点:

异步通知模式,需要底层驱动的支持

异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。通知发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知。

信号提供了一种处理异步事件的方法。举一个不恰当的例子,比如你正兴奋在玩游戏,突然你手机响了,你立马放下手上的游戏,去接听电话。手机随时都会响,随时都会中断你当下的事情。所以称之为异步事件。

1. 通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。

2. 应用程序收到信号后做异步处理即可。

3. 应用程序需要把自己的进程号告诉内核,并打开异步通知机制。

思路 代码

//1.设置将文件描述符和进程号提交给内核驱动
//一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号
   fcntl(fd,F_SETOWN,getpid());

//2.设置异步通知
    int flags;
    flags = fcntl(fd, F_GETFL); //获取原属性
    flags |= O_ASYNC;       //给flags设置异步   O_ASUNC 通知
    fcntl(fd, F_SETFL, flags);  //修改的属性设置进去,此时fd属于异步
    
//3.signal捕捉SIGIO信号 --- SIGIO:内核通知会进程有新的IO信号可用
//一旦内核给进程发送sigio信号,则执行handler
    signal(SIGIO,handler);
// 设置套接字的拥有者
 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();
 }

signal信号处理相关函数

头文件: #include <signal.h>
        typedef void (*sighandler_t)(int);
        sighandler_t   signal(int signum, sighandler_t handler)
功能:信号处理函数(注册信号)
参数: int signum:要处理的信号(要修改的信号)
           sighandler_t handler: 函数指针: void(*handler)(int) (修改的功能:)
           handler:------void handler(int num) 自定义的信号处理函数指针
返回值: 成功:设置之前的信号处理方式
失败:   SIG_ERR

代码实例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

int fd;

void handler(int sig)
{
    char buf[128] = "";
    int ret = read(fd, buf, sizeof(buf));
    buf[ret] = '\0';

    printf("mouse: %s\n", buf);
}

int main(int argc, char const *argv[])
{
    //1.打开鼠标文件
    fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0)
    {
        perror("open is err:");
        return -1;
    }

    //1.把文件描述符和进程号上传给底层驱动  //SIGIO
    fcntl(fd, F_SETOWN, getpid());

    //1.先获取文件描述符的原属性
    int flag = fcntl(fd, F_GETFL);
    //2.加入一个异步的机制
    flag = flag | O_ASYNC;

    fcntl(fd, F_SETFL, flag);

    //3.signal
    signal(SIGIO, handler);

    char buf[128];
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        printf("%s\n", buf);
    }

    return 0;
}
//异步 I/O 机制和信号处理来实现在鼠标事件发生时进行响应。
//它的优点是在等待数据到达时不会阻塞进程,但需要处理信号和异步机制

4. I/O多路复用 - 帮助TCP实现并发服务器(重点)

1. 进程中若需要同时处理多路输入输出 ,在使用单进程和单线程的情况下, 可使用IO多路复用处理多个请求;

2. IO多路复用不需要创建新的进程和线程, 有效减少了系统的资源开销。

就比如服务员给50个顾客点餐,分两步:

顾客思考要吃什么(等待客户端数据发送)

顾客想好了,开始点餐(接收客户端数据)

要提高效率有几种方法?

1. 安排50个服务员 (类似于多进程/多线程实现服务器连接多个客户端,太占用资源)

2. 哪个顾客想好了吃啥, 那个顾客来柜台点菜 (类似IO多路复用机制实现并发服务器)

实现IO多路复用的方式: select poll epoll

基本流程是:

1. 先构造一张有关文件描述符的表;

2. 清空表

3. 将你关心的文件描述符加入到这个表中;

4. 调用select函数。

5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);

6. 做对应的逻辑处理;

1) select

 头文件: #includ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值