信号驱动式I/O【UNP读书笔记】

信号驱动式I/O

1、概述

​ 信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。

​ 信号驱动式I/O与异步式I/O是有区别的:后者通常定义为进程执行I/O系统调用告知内核启动某个I/O操作,内核启动I/O操作后立即返回到进程中。进程在I/O操作发生期间继续执行。当操作完成或遇到错误时,内核以进程在I/O系统调用中指定的某种方式通知进程。

​ POSIX提供了真正的异步IO操作,函数aio_XXX允许进程指定I/O操作完成时是否由内核产生信号以及产生什么信号。

2、套接字的信号驱动式I/O

​ 一个套接字使用信号驱动式I/O(SIGIO)要求进程执行以下3个步骤:

  • 建立SIGIO信号的信号处理函数(可以使用sigaction重新的signal函数进行绑定)。
  • 设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。
  • 开启该套接字的信号驱动式I/O,通常通过fcntl的F_SETFL命令打开O_ASYNC标志完成。
2.1对于UDP套接字的SIGIO信号

​ 在UDP上使用信号驱动式I/O是简单的,SIGIO信号在发生以下两种事件是产生:

  • 数据报到达套接字;

  • 套接字上发生异步错误。

    其中UDP套接字上发生异步错误的前提是UDP套接字已连接(使用connect函数连接)。

2.2对于TCP套接字的SIGIO信号

​ 信号驱动式I/O对于TCP套接字近乎无用,因为该信号产生的太过频繁了,以下条件都会使TCP套接字产生SIGIO信号:

  • 监听套接字上某个连接请求已经完成;

  • 某个断连请求已经完成;

  • 某个断连请求已经发起;

  • 某个连接之半已经关闭;(这里我的理解应该是半关闭状态吧,主动关闭的一方接受到了被动关闭一方的ACK响应吧)

  • 数据到达套接字;

  • 数据已经从套接字发送走(即输出缓冲区有空闲空间);

  • 发生某个异步的错误。
    在这里插入图片描述

3、使用SIGIO实现一个UDP回射服务器

​ 下面是使用信号驱动式的代码片段。

#include "unp.h"

static int sockfd;
#define QSIZE 8     /* size of input queue */
#define MAXDG 4096  /* max datagram size */

typedef struct {
    void *dg_data;      /* ptr to actual datagram */
    size_t dg_len;      /* length of datagram */
    struct sockaddr *dg_sa; /* ptr to sockaddr{} client's address */
    socklen_t dg_salen; /* length of sockaddr{} */
}DG;
static DG dg[QSIZE];        /* queue of datagrams to process */
static long cntread[QSIZE+1];/* diagnostic counter*/
static int iget;            /* next one for main loop to process */
static int iput;            /* next one for signal handler to read into*/
static int nqueue;          /* on queue for main loop to process */
static socklen_t clilen;    /* max length of sockaddr{} */
static void sig_io(int);
static void sig_hup(int);

void
dg_echo(int sockfd_arg,SA *pcliaddr,socklen_t clilen_arg)
{
    int i;
    const int on = 1;
    sigset_t zeromask,newmask,oldmask;
    sockfd = sockfd_arg;
    clilen = clilen_arg;
    for(i=0;i<QSIZE;++i){
        dg[i].dg_data = Malloc(MAXDG);
        dg[i].dg_sa = Malloc(clilen);
        dg[i].dg_salen = clilen;
    }
    iget = iput = nqueue = 0;
    Signal(SIGHUP,sig_hup);
    Signal(SIGIO,sig_io);//为SIGIO信号添加信号处理函数

    Fcntl(sockfd,F_SETOWN,getpid());//为sockfd绑定属主进程
    Ioctl(sockfd,FIOASYNC,&on);//开启信号驱动式
    Ioctl(sockfd,FIONBIO,&on);//开启该套接字为非阻塞的套接字

    Sigemptyset(&zeromask);
    Sigemptyset(&oldmask);
    Sigemptyset(&newmask);
    Sigaddset(&newmask,SIGIO);//signal we want to block

    Sigprocmask(SIG_BLOCK,&newmask,&oldmask);//先blockSIGIO信号
    for(;;){
        while(nqueue == 0){
            sigsuspend(&zeromask);//wait for datagram to process
        }
        Sigprocmask(SIG_SETMASK,&oldmask,NULL);
        Sendto(sockfd,dg[iget].dg_data,dg[iget].dg_len,0,dg[iget].dg_sa,dg[iget].dg_salen);
        if(++iget >= QSIZE)
            iget = 0;
        Sigprocmask(SIG_BLOCK,&newmask,&oldmask);
        nqueue--;
    }
}

static void
sig_io(int signo)
{
    ssize_t len;
    int nread;
    DG *ptr;

    for(nread = 0;;){
        if(nqueue >= QSIZE)
            err_quit("receive overflow");//接收队列已满
        ptr = &dg[iput];
        ptr->dg_salen = clilen;
        len = recvfrom(sockfd,ptr->dg_data,MAXDG,0,ptr->dg_sa,&ptr->dg_salen);
        if(len < 0){
            /* 由于sockfd设置为非阻塞的,故而在没有数据可读的时候,会直接返回,errno将被设置为EWOULDBLOCK */
            if(errno == EWOULDBLOCK)//没有数据到达
                break;
            else
                err_sys("recvfrom error");//recvfrom函数出现错误
        }
        ptr->dg_len = len;
        nread++;
        nqueue++;
        if(++iput >=QSIZE)
            iput = 0;
    }
    cntread[nread]++;
}

static void
sig_hup(int signo)
{
    int i;
    for(i=0;i<=QSIZE;++i){
        printf("cntread[%d] = %ld\n",i,cntread[i]);
    }
}

4、小结

​ 信号驱动式I/O就是让内核在套接字上发生“某事”时使用SIGIO信号通知进程。

  • 对于已连接TCP套接字,因为通知的条件太多,导致这个特性近乎无用。
  • 对于TCP监听套接字,当通知发生时表示有一个新连接已准备好接受了。
  • 对于UDP套接字,这个通知意味着或者到达一个数据报,或者到达一个异步错误。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值