日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UNP Chapter 22 - 信号驱动I/O

發(fā)布時(shí)間:2023/12/10 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UNP Chapter 22 - 信号驱动I/O 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

22.1. 概述

信號驅(qū)動是指當(dāng)某個(gè)描述字上發(fā)生了某個(gè)事件時(shí),讓內(nèi)核通知進(jìn)程。

這里描述的信號驅(qū)動不是真正的異步I/O。

第15章描述的非阻塞I/O同樣不是異步I/O。在非阻塞I/O中,啟動I/O操作后內(nèi)核并不像真正的異步I/O那樣立即返回,它只有在進(jìn)程非得睡眠才能完成操作時(shí)才立即返回。

?

22.2. 套接口上的信號驅(qū)動I/O

使用套接口上的信號驅(qū)動I/O(SIGIO)需要進(jìn)程執(zhí)行以下三個(gè)步驟:

1. 給SIGIO信號建立信號處理程序

2. 設(shè)置套接口屬主,通常使用fcntl的F_SETOWN命令

3. 激活套接口的信號驅(qū)動I/O,通常使用fcntl的F_SETFL命令打開O_ASYNC標(biāo)志

?

UDP套接口上的SIGIO信號

? UDP上使用信號驅(qū)動I/O是簡單的。當(dāng)下述事件發(fā)生時(shí)產(chǎn)生SIGIO信號:

? 1. 數(shù)據(jù)報(bào)到達(dá)套接口

? 2. 套接口上發(fā)生異步錯(cuò)誤

因此,當(dāng)我們捕獲到SIGIO信號時(shí),我們調(diào)用recvfrom讀取到達(dá)的數(shù)據(jù)報(bào)或者獲取異步錯(cuò)誤。

?

TCP套接口上的SIGIO信號

? 不幸的是,信號驅(qū)動I/O對TCP套接口幾乎是沒用的,原因是該信號產(chǎn)生得過于頻繁,并且該信號的出現(xiàn)并沒有告訴我們發(fā)生了什么事情。

? 下列條件均可在TCP套接口上產(chǎn)生SIGIO信號(假設(shè)信號驅(qū)動I/O是使能的):

??? 1. 在監(jiān)聽套接口上有一個(gè)連接請求已經(jīng)完成

??? 2. 發(fā)起了一個(gè)連接拆除請求

??? 3. 一個(gè)連接拆除請求已經(jīng)完成

??? 4. 一個(gè)連接的一半已經(jīng)關(guān)閉

??? 5. 數(shù)據(jù)到達(dá)了套接口

??? 6. 數(shù)據(jù)已從套接口上發(fā)出(即輸出緩沖區(qū)有空閑時(shí)間)

??? 7. 發(fā)生了一個(gè)異步錯(cuò)誤

例如,如果一個(gè)進(jìn)程既從一個(gè)TCP套接口讀數(shù)據(jù),又向其上寫數(shù)據(jù),當(dāng)新數(shù)據(jù)到達(dá)或者以前所寫數(shù)據(jù)得到確認(rèn)后均會產(chǎn)生SIGIO信號,進(jìn)程無法在信號處理程序中區(qū)分這兩種情況。如果在這種情況下使用SIGIO,TCP套接口應(yīng)該被設(shè)置為非阻塞方式以防止read或write發(fā)生阻塞。我們應(yīng)該考慮只在監(jiān)聽TCP套接口上使用SIGIO,因?yàn)樵诒O(jiān)聽套接口上產(chǎn)生SIGIO的唯一條件是一個(gè)新連接的完成。

?

這里找到的實(shí)際使用信號驅(qū)動I/O的程序是基于UDP的NTP(網(wǎng)絡(luò)時(shí)間協(xié)議)服務(wù)器程序。NTP服務(wù)器的主循環(huán)從客戶接收數(shù)據(jù)報(bào)并送回相應(yīng),但是每個(gè)客戶請求都要進(jìn)行相當(dāng)數(shù)量的處理(比我們簡單的回射服務(wù)器多得多)。對于服務(wù)器來講,很重要的一點(diǎn)是給每個(gè)收到的數(shù)據(jù)報(bào)記錄精確的時(shí)間戳,因?yàn)檫@個(gè)值要返回給客戶,客戶要用它來計(jì)算到達(dá)該服務(wù)器的來回時(shí)間。圖22.1展示了構(gòu)建這樣一個(gè)UDP服務(wù)器的兩種方法。

大多數(shù)UDP服務(wù)器都被設(shè)計(jì)成圖中左邊的方式。但是NTP服務(wù)器采用了圖中右邊的技術(shù): 當(dāng)一個(gè)新數(shù)據(jù)報(bào)到達(dá)時(shí),SIGIO處理程序讀得該數(shù)據(jù)報(bào),同時(shí)記錄數(shù)據(jù)報(bào)到達(dá)的時(shí)刻,然后把它放入進(jìn)程的另一個(gè)隊(duì)列中,由主服務(wù)器循環(huán)移走和處理。雖然這種技巧使服務(wù)器代碼變得復(fù)雜,但是它為到達(dá)的數(shù)據(jù)報(bào)提供了精確的時(shí)間戳。

?

22.3. 使用SIGIO的UDP回射服務(wù)器程序

現(xiàn)在舉一個(gè)類似圖22.1.右邊的例子:一個(gè)使用SIGIO信號接收到達(dá)的數(shù)據(jù)報(bào)的UDP服務(wù)器程序。

我們使用圖8.7和8.8中同樣的客戶程序以及圖8.3中同樣的服務(wù)器程序main函數(shù)。我們做的唯一修改是dg_echo函數(shù),下邊四張圖給出這些修改,圖22.2給出了全局變量聲明。

#include "unp.h"
static int sockfd;
#define QSIZE 8 /* size of input queue */
#define MAXDG 4096 /* maximum datagram size */ /* SIGIO信號處理程序?qū)⒌竭_(dá)的數(shù)據(jù)報(bào)放入一個(gè)隊(duì)列中。該隊(duì)列是一個(gè)DG結(jié)構(gòu)數(shù)組,我們將它處理成環(huán)形緩沖區(qū)。 */ /* 每個(gè)DG結(jié)構(gòu)包括一個(gè)指向收到的數(shù)據(jù)報(bào)的指針,數(shù)據(jù)報(bào)的長度,一個(gè)指向包含客戶協(xié)議地址的套接口地址結(jié)構(gòu)的指針以及協(xié)議地址的大小。 */ /* 靜態(tài)分配我們QSIZE個(gè)DG結(jié)構(gòu),從圖22.4我們將看到dg_echo函數(shù)調(diào)用malloc給所有的數(shù)據(jù)報(bào)和套接口地址結(jié)構(gòu)分配內(nèi)存。 */ /* 我們還分配一個(gè)診斷用計(jì)數(shù)器cntread,不久將會解釋到。圖22.3展示了當(dāng)?shù)谝豁?xiàng)指向一個(gè)150字節(jié)數(shù)據(jù)報(bào),與其關(guān)聯(lián)的套接口地址結(jié)構(gòu)長度為16時(shí),DG結(jié)構(gòu)數(shù)組的內(nèi)容 */
typedef struct {
void * dg_data; /* ptr to actual datagram */
size_t dg_len; /* length of datagram */
struct sockaddr * dg_sa; /* ptr to sockaddr{} w/client's address */
socklen_t dg_salen; /* lenght of sockaddr{} */
} DG;
static DG dg[QSIZE]; /* the queue of datagrams to process */
static long cntread[QSIZE+1]; /* diagnostic counter */ /* iget是主循環(huán)將處理的下一個(gè)數(shù)組元素的下標(biāo) */ /* iput是信號處理程序?qū)⒁娣诺南乱粋€(gè)數(shù)組元素的下標(biāo) */ /* nqueue是主循環(huán)將要處理的隊(duì)列中數(shù)據(jù)報(bào)的總數(shù)目 */ 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);

?

dg_echo函數(shù):服務(wù)器主處理循環(huán)

void dg_echo(int sockfd_arg, SA * pcliaddr, socklen_t clilen_arg)
{
int on = 1;
sigset_t zeromask, newmask, oldmask;
sockfd = sockfd_arg;
clilen = clilen_arg; /* 套接口描述字保存在一個(gè)全局變量中,因?yàn)樾盘柼幚沓绦蛞玫剿R咽盏綌?shù)據(jù)報(bào)隊(duì)列被初始化 */
for ( i = 0; i < QSIZE; i++) { /* init queue of buffers */
dg[i].dg_data = Malloc(MAXDG);
dg[i].dg_sa = Malloc(clilen);
dg[i].dg_salen = clilen;
}
iget = iput = nqueue = 0; /* 給SIGHUP和SIGIO建立信號處理程序 */
Signal(SIGHUP, sig_hup);
Signal(SIGIO, sig_io); /* 用fcntl設(shè)置套接口屬主 */
Fcntl(sockfd, F_SETOWN, getpid()); /* 用ioctl設(shè)置信號驅(qū)動和非阻塞I/O標(biāo)志 */
ioctl(sockfd, FIOASYNC, &on);
ioctl(sockfd, FIONBIO, &on); /* 初始化三個(gè)信號集:zeromask(從不改變)、oldmask(記錄我們阻塞SIGIO時(shí)的老信號掩碼)和newmask */
Sigemptyset(&zeromask); /* init three signal sets */
Sigemptyset(&oldmask);
Sigemptyset(&newmask); /* sigaddset打開newmask中與SIGIO對應(yīng)的位 */
Sigaddset(&newmask, SIGIO); /* the signal we want to block */
/* sigprocmask將進(jìn)程當(dāng)前信號掩碼存入oldmask中,然后將newmask與當(dāng)前的信號掩碼進(jìn)行邏輯或。這將阻塞SIGIO并返回當(dāng)前的信號掩碼 */ Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* 進(jìn)入for循環(huán)并測試nqueue計(jì)數(shù)器。只要這個(gè)計(jì)數(shù)器為0,進(jìn)程就無事可做。這時(shí)我們調(diào)用sigsuspend。 */ /* 因?yàn)閦eromask是一個(gè)空信號集,所有的信號將被解阻塞。sigsuspend在捕獲一個(gè)信號并在其信號處理程序返回后返回 */
/* 但在返回前,sigsuspend總是將信號掩碼恢復(fù)為調(diào)用它時(shí)的信號掩碼值,在這里這個(gè)掩碼值為newmask,所以我們能夠保證sigsuspend返回后,SIGIO仍被阻塞 */ /* 這就是為什么我們能夠測試nqueue標(biāo)志,因?yàn)楫?dāng)我們測試時(shí),SIGIO信號不可能被遞交。 */ for( ; ; ) {
while (nqueue == 0)
sigsuspend(&zeromask); /* wait for a datagram to process */
/* unblock SIGIO */
Sigprocmask(SIG_SETMASK, &oldmask, NULL); /* 調(diào)用sigprocmask將進(jìn)程的信號掩碼設(shè)置為先前保存的oldmask的舊值,從而解除了SIGIO阻塞 */
Sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, dg[iget].dg_sa, dg[iget].dg_salen);/* 然后調(diào)用sendto發(fā)送應(yīng)答 */
if( ++iget >= QSIZE) /* 下標(biāo)iget加1,如果其值等于數(shù)據(jù)元素個(gè)數(shù),則置iget為0,因?yàn)槲覀儼褦?shù)組當(dāng)作環(huán)形緩沖區(qū)對待 */
iget = 0; /* 當(dāng)修改iget時(shí),我們不需要阻塞SIGIO,因?yàn)閕get只被主循環(huán)使用,信號處理程序永遠(yuǎn)不會修改它 */
/* block SIGIO */
Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* 阻塞SIGIO,nqueue減1,我們在修改nqueue是必須阻塞SIGIO,因?yàn)橹餮h(huán)和信號處理程序在共享這個(gè)變量 */
nqueue--;
}
}

SIGIO處理程序

static void sig_io(int signo)
{
ssize_t len;
int nread;
DG *ptr;
for(nread = 0; ; ) {
if(nqueue >= QSIZE) /* 如果隊(duì)列滿,進(jìn)程就終止 */
err_quit("receive overflow");
ptr = &dg[iput]; /* 在非阻塞的套接口上調(diào)用recvfrom,iput做下標(biāo)的數(shù)組項(xiàng)是數(shù)據(jù)報(bào)存儲的地方,如果沒有可讀數(shù)據(jù)報(bào),則跳出for循環(huán) */
ptr->dg_salen = clilen;
len = recvfrom(sockfd, ptr->dg_data, MAXDG, 0, ptr->dg_sa, &ptr->dg_salen);
if(len<0) {
if(errno == EWOULDBLOCK)
break; /* all done; no more queued to read */
else
err_sys("recvfrom error");
}
ptr->dg_len = len;
nread++; /* nread是一個(gè)計(jì)數(shù)器,記錄每個(gè)信號讀的數(shù)據(jù)報(bào)數(shù) */
nqueue++; /* nqueue是主循環(huán)將要處理的數(shù)據(jù)報(bào)數(shù) */
if(++iput >= QSIZE)
iput = 0;
}
cntread[nread]++; /* histogram of #datagrams read per signal */
/* 信號處理程序在返回前,將與每個(gè)信號讀到的數(shù)據(jù)報(bào)數(shù)目對應(yīng)的計(jì)數(shù)器加1,當(dāng)SIGHUP遞交后,該數(shù)組的內(nèi)容做為診斷信息輸出 */ }

SIGHUP信號處理程序

static void sig_hup(int signo)
{/* SIGHUP信號處理程序,它輸出cntread數(shù)組的內(nèi)容,cntread數(shù)組統(tǒng)計(jì)每個(gè)信號讀到的數(shù)據(jù)報(bào)數(shù)目 */
int i;
for(i = 0; i <= QSIZE; i++)
printf("cntread[%d] = %d\n", i, cntread[i];
}


22.4. 小結(jié)

?

轉(zhuǎn)載于:https://www.cnblogs.com/s7vens/archive/2012/03/28/2421602.html

總結(jié)

以上是生活随笔為你收集整理的UNP Chapter 22 - 信号驱动I/O的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。