• Linux 网络编程的5种IO模型:信号驱动IO模型


    介绍

    情景引入:

    在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情

    applicationkernel应用程序调用系统调用signaction返回递交SIGIO信号信号处理recvfrom准备好数据,拷贝到用户空间拷贝完成,返回成功applicationkernel信号驱动IO模型

    在UDP上,SIGIO信号会在下面两个事件的时候产生:

    1 数据报到达套接字

    2 套接字上发生错误

    因此我们很容易判断SIGIO出现的时候,如果不是发生错误,那么就是有数据报到达了。

    而在TCP上,由于TCP是双工的,它的信号产生过于频繁,并且信号的出现几乎没有告诉我们发生了什么事情。因此对于TCP套接字,SIGIO信号是没有什么使用的。

    有关函数

    c
    #include<signal.h>
    
    intsigaction(int signum, const struct sigaction *act,
                 struct sigaction *oldact);
    

    关于有关内容的讲解,请参考:Linux 系统编程 学习:进程间通信-Unix IPC-信号

    例程

    这对例程是不太规范的,因为有BUG。但因为这种消息模型用的比较少所以我就不改了。

    server.c

    c
    #include<stdio.h>
    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    #include<signal.h>
    #include<fcntl.h>
    
    int listenfd1;
    volatile int read_flag = 0;
    static char buf[256] = { 0 };
    voiddo_sigio(int sig){
        structsockaddr_incli_addr;
        int clifd, clilen;
        read_flag = 1;
    
        memset(buf, 0, sizeof(buf));
        recvfrom(listenfd1, buf, sizeof(buf), 0, (struct sockaddr *)&cli_addr, &clilen);
        printf("Listenfd1 Message %s 
    ", buf);
        perror("recvfrom");
        sendto(listenfd1, "Reply", sizeof("Reply"),0, (struct sockaddr *)&cli_addr, sizeof(cli_addr));
        perror("sendto");
        printf("sigio end
    ");
        read_flag = 0;
    }
    
    
    intmain(int argc, char *argv[]){
        //绑定监听7779端口的fd
        structsockaddr_inserv_addr;
        listenfd1 = socket(AF_INET, SOCK_DGRAM, 0);
    
        bzero((char *) &serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(7779);
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    
        structsigactionsigio_action;
        memset(&sigio_action, 0, sizeof(sigio_action));
        sigio_action.sa_flags = 0;
        sigio_action.sa_handler = do_sigio;
        sigaction(SIGIO, &sigio_action, NULL);
    
        fcntl(listenfd1, F_SETOWN, getpid());
        int flags;
        flags = fcntl(listenfd1, F_GETFL, 0);
        flags |= O_ASYNC ;//| O_NONBLOCK;
        fcntl(listenfd1, F_SETFL, flags);
    
    
        bind(listenfd1, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    
        while(1)
        {
            sleep(2);
        }
        close(listenfd1);
    
        return 0;
    
    }
    

    client.c

    c
    #include<stdio.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    
    intmain(int argc, char* argv[]){
        int socketfd;
        socklen_t n;
        socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    
        structsockaddr_inserv_addr;
        structsockaddr_inaddr;
    
        bzero((char *)&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(7779);
    
        char buf[64] = {0};
        //write(socketfd, "client message", sizeof("client message"));
        sendto(socketfd, "client message", sizeof("client message"),0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    
        memset(buf, 0, sizeof(buf));
        //read(socketfd, buf, sizeof(buf));
        recvfrom(socketfd, buf, sizeof(buf), 0, (struct sockaddr *)&serv_addr, &n);
        printf("%u %s
    ",n ,buf);
        return 0;
    
    }
    

    附录:异步通知

    ref :异步通知

    注意:异步通知只有SIGIO信号,没有别的信号可用,其他各种信号在app空间可以任意使用.

    通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。

    启用步骤:

    (1)它们指定一个进程作为文件的拥有者:使用 fcntl 系统调用发出 F_SETOWN 命令,这个拥有者进程的 ID 被保存在 filp->f_owner。目的:让内核知道信号到达时该通知哪个进程。

    (2)使用 fcntl 系统调用,通过 F_SETFL 命令设置 FASYNC 标志。

    内核操作过程

    1.F_SETOWN被调用时filp->f_owner被赋值。

    1. 当 F_SETFL 被执行来打开 FASYNC, 驱动的 fasync 方法被调用.这个标志在文件被打开时缺省地被清除。

    2. 当数据到达时,所有的注册异步通知的进程都会被发送一个 SIGIO 信号。

    Linux 提供的通用方法是基于一个数据结构和两个函数,定义在。

    数据结构:

    c
     structfasync_struct{
        int    magic;
        int    fa_fd;
        structfasync_struct    *fa_next;/* singly linked list */
        structfile         *fa_file;
    }; 
    

    驱动调用的两个函数的原型:

    c
    intfasync_helper(int fd,structfile*filp,int mode, struct fasync_struct**fa);
    voidkill_fasync(struct fasync_struct**fa,int sig, int band); 
    

    当一个打开的文件的FASYNC标志被修改时,调用 fasync_helper 来从相关的进程列表中添加或去除文件。除了最后一个参数, 其他所有参数都时被提供给 fasync 方法的相同参数并被直接传递。 当数据到达时,kill_fasync 被用来通知相关的进程,它的参数是被传递的信号(常常是 SIGIO)和 band(几乎都是 POLL_IN)。

    这是 scullpipe 实现 fasync 方法的:

    c
     staticint scull_p_fasync(int fd,struct file*filp,int mode){
     structscull_pipe *dev = filp->private_data;
     return fasync_helper(fd, filp, mode,&dev->async_queue);
    } 
    

    当数据到达, 下面的语句必须被执行来通知异步读者. 因为对 sucllpipe 读者的新数据通过一个发出 write 的进程被产生, 这个语句出现在 scullpipe 的 write 方法中:

     
     if (dev->async_queue)  kill_fasync(&dev->async_queue, SIGIO, POLL_IN); /* 注意, 一些设备也针对设备可写而实现了异步通知,在这个情况,kill_fasnyc 必须以 POLL_OUT 模式调用.*/ 
    

    当文件被关闭时必须 调用fasync 方法,来从活动的异步读取进程列表中删除该文件。尽管这个调用仅当 filp->f_flags 被设置为 FASYNC 时才需要,但不管什么情况,调用这个函数不会有问题,并且是普遍的实现方法。 以下是 scullpipe 的 release 方法的一部分:

     
     /* remove this filp from the asynchronously notified filp's */ scull_p_fasync(-1, filp, 0); 
    

    异步通知使用的数据结构和 struct wait_queue 几乎相同,因为他们都涉及等待事件。区别异步通知用 struct file 替代 struct task_struct. 队列中的 file 用获取 f_owner, 一边给进程发送信号。

    介绍

    情景引入:

    在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情

    applicationkernel应用程序调用系统调用signaction返回递交SIGIO信号信号处理recvfrom准备好数据,拷贝到用户空间拷贝完成,返回成功applicationkernel信号驱动IO模型

    在UDP上,SIGIO信号会在下面两个事件的时候产生:

    1 数据报到达套接字

    2 套接字上发生错误

    因此我们很容易判断SIGIO出现的时候,如果不是发生错误,那么就是有数据报到达了。

    而在TCP上,由于TCP是双工的,它的信号产生过于频繁,并且信号的出现几乎没有告诉我们发生了什么事情。因此对于TCP套接字,SIGIO信号是没有什么使用的。

    有关函数

    c
    #include<signal.h>
    
    intsigaction(int signum, const struct sigaction *act,
                 struct sigaction *oldact);
    

    关于有关内容的讲解,请参考:Linux 系统编程 学习:进程间通信-Unix IPC-信号

    例程

    这对例程是不太规范的,因为有BUG。但因为这种消息模型用的比较少所以我就不改了。

    server.c

    c
    #include<stdio.h>
    #include<stdio.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    #include<signal.h>
    #include<fcntl.h>
    
    int listenfd1;
    volatile int read_flag = 0;
    static char buf[256] = { 0 };
    voiddo_sigio(int sig){
        structsockaddr_incli_addr;
        int clifd, clilen;
        read_flag = 1;
    
        memset(buf, 0, sizeof(buf));
        recvfrom(listenfd1, buf, sizeof(buf), 0, (struct sockaddr *)&cli_addr, &clilen);
        printf("Listenfd1 Message %s 
    ", buf);
        perror("recvfrom");
        sendto(listenfd1, "Reply", sizeof("Reply"),0, (struct sockaddr *)&cli_addr, sizeof(cli_addr));
        perror("sendto");
        printf("sigio end
    ");
        read_flag = 0;
    }
    
    
    intmain(int argc, char *argv[]){
        //绑定监听7779端口的fd
        structsockaddr_inserv_addr;
        listenfd1 = socket(AF_INET, SOCK_DGRAM, 0);
    
        bzero((char *) &serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(7779);
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    
        structsigactionsigio_action;
        memset(&sigio_action, 0, sizeof(sigio_action));
        sigio_action.sa_flags = 0;
        sigio_action.sa_handler = do_sigio;
        sigaction(SIGIO, &sigio_action, NULL);
    
        fcntl(listenfd1, F_SETOWN, getpid());
        int flags;
        flags = fcntl(listenfd1, F_GETFL, 0);
        flags |= O_ASYNC ;//| O_NONBLOCK;
        fcntl(listenfd1, F_SETFL, flags);
    
    
        bind(listenfd1, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    
        while(1)
        {
            sleep(2);
        }
        close(listenfd1);
    
        return 0;
    
    }
    

    client.c

    c
    #include<stdio.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<string.h>
    #include<unistd.h>
    
    intmain(int argc, char* argv[]){
        int socketfd;
        socklen_t n;
        socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    
        structsockaddr_inserv_addr;
        structsockaddr_inaddr;
    
        bzero((char *)&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(7779);
    
        char buf[64] = {0};
        //write(socketfd, "client message", sizeof("client message"));
        sendto(socketfd, "client message", sizeof("client message"),0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    
        memset(buf, 0, sizeof(buf));
        //read(socketfd, buf, sizeof(buf));
        recvfrom(socketfd, buf, sizeof(buf), 0, (struct sockaddr *)&serv_addr, &n);
        printf("%u %s
    ",n ,buf);
        return 0;
    
    }
    

    附录:异步通知

    ref :异步通知

    注意:异步通知只有SIGIO信号,没有别的信号可用,其他各种信号在app空间可以任意使用.

    通过使用异步通知,应用程序可以在数据可用时收到一个信号,而无需不停地轮询。

    启用步骤:

    (1)它们指定一个进程作为文件的拥有者:使用 fcntl 系统调用发出 F_SETOWN 命令,这个拥有者进程的 ID 被保存在 filp->f_owner。目的:让内核知道信号到达时该通知哪个进程。

    (2)使用 fcntl 系统调用,通过 F_SETFL 命令设置 FASYNC 标志。

    内核操作过程

    1.F_SETOWN被调用时filp->f_owner被赋值。

    1. 当 F_SETFL 被执行来打开 FASYNC, 驱动的 fasync 方法被调用.这个标志在文件被打开时缺省地被清除。

    2. 当数据到达时,所有的注册异步通知的进程都会被发送一个 SIGIO 信号。

    Linux 提供的通用方法是基于一个数据结构和两个函数,定义在。

    数据结构:

    c
     structfasync_struct{
        int    magic;
        int    fa_fd;
        structfasync_struct    *fa_next;/* singly linked list */
        structfile         *fa_file;
    }; 
    

    驱动调用的两个函数的原型:

    c
    intfasync_helper(int fd,structfile*filp,int mode, struct fasync_struct**fa);
    voidkill_fasync(struct fasync_struct**fa,int sig, int band); 
    

    当一个打开的文件的FASYNC标志被修改时,调用 fasync_helper 来从相关的进程列表中添加或去除文件。除了最后一个参数, 其他所有参数都时被提供给 fasync 方法的相同参数并被直接传递。 当数据到达时,kill_fasync 被用来通知相关的进程,它的参数是被传递的信号(常常是 SIGIO)和 band(几乎都是 POLL_IN)。

    这是 scullpipe 实现 fasync 方法的:

    c
     staticint scull_p_fasync(int fd,struct file*filp,int mode){
     structscull_pipe *dev = filp->private_data;
     return fasync_helper(fd, filp, mode,&dev->async_queue);
    } 
    

    当数据到达, 下面的语句必须被执行来通知异步读者. 因为对 sucllpipe 读者的新数据通过一个发出 write 的进程被产生, 这个语句出现在 scullpipe 的 write 方法中:

     
     if (dev->async_queue)  kill_fasync(&dev->async_queue, SIGIO, POLL_IN); /* 注意, 一些设备也针对设备可写而实现了异步通知,在这个情况,kill_fasnyc 必须以 POLL_OUT 模式调用.*/ 
    

    当文件被关闭时必须 调用fasync 方法,来从活动的异步读取进程列表中删除该文件。尽管这个调用仅当 filp->f_flags 被设置为 FASYNC 时才需要,但不管什么情况,调用这个函数不会有问题,并且是普遍的实现方法。 以下是 scullpipe 的 release 方法的一部分:

     
     /* remove this filp from the asynchronously notified filp's */ scull_p_fasync(-1, filp, 0); 
    

    异步通知使用的数据结构和 struct wait_queue 几乎相同,因为他们都涉及等待事件。区别异步通知用 struct file 替代 struct task_struct. 队列中的 file 用获取 f_owner, 一边给进程发送信号。

  • 相关阅读:
    Linux上查找
    Linux进程
    Linux重定向
    Linux上常用的基本命令
    LInux上返回到切换目录前的目录
    【网络知识之一】4/7层网络模型
    【操作系统之十五】iptables黑白名单、自定义链、网络防火墙、常用动作
    【操作系统之十四】iptables扩展模块
    【操作系统之十三】Netfilter与iptables
    【操作系统之十二】分支预测、CPU亲和性(affinity)
  • 原文地址:https://www.cnblogs.com/wt88/p/15100304.html
Copyright © 2020-2023  润新知