一、信号驱动IO的套接字
*信号驱动IO进程执行步骤:
1)、建立SIGIO的信号处理函数。
2)、设置套接字的属主, 通常使用fcntl的F_SETOWN命令设置。
3)、开启套接字的信号驱动IO, 通常使用fcntl的F_SETFL命令打开O_ASYNC标志完成。
*UDP套接字的SIGIO信号的产生:
1)、数据报到达套接字。
2)、套接字上发生了异步错误(UDP已经连接)。
*TCP套接字下的SIGIO信号的产生:
1)、监听套接上的某个请求已经完成。
2)、某个断开请求发起。
3)、某个断开请求完成。
4)、数据到达套接字。
5)、数据已经冲套接字发走(即输出缓冲区有空闲)
6)、发生某个异步错误
二、UDP服务器程序
结合上图可视,当数据报到达数据缓冲区的时候, 触发SIGIO信号, 那么系统进入信号处理函数, 把数据报从缓冲区读入到应用层缓冲区当中, 再由服务器应用程序处理。
伪代码如下:
void do_echo(int sockfd_arg, struct sockaddr *cliaddr, socklen_t clilen){
设置套接字属主
设置为信号驱动IO和非堵塞IO
安装信号处理函数
初始化并设置信号屏蔽集掩码
堵塞SIGIO
while(true){
若循环队列的接受缓冲区为空
sigsuspend(sigset_t) //等待IO驱动信号并且等待过程中, 暂时设置屏蔽信号掩码为sigset_t。接受完毕后,sigsuspend在信号处理函数完毕后返回
接触SIGIO的堵塞
处理循环缓冲区数据
....
再次堵塞SIGIO
}
}
void sig_io(int signo){
for(;;){
若缓冲区已满
exit(0); //测试无法在接受任何数据, 但是UDP数据又会被送过来, 所以数据被摒弃
接收数据
非堵塞IO判断
无数据接受跳出循环
....
}
}
服务器核心程序源代码如下:
dg_echo.c
#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 autual datagram
size_t dg_len; // length of datagram
struct sockaddr *dg_sa; //ptr to sockaddr /*client's addr*/
socklen_t dg_salen; //length of sockaddr{}
}DG;
static DG dg[QSIZE]; //queue of datagram 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 nqueue; //on queue for main loop to process
static socklen_t clilen; //maxlen length of sockaddr{}
static void sig_io(int);
static void sig_hup(int);
void dg_echo(int sockfd_arg, struct sockaddr* pcliaddr, socklen_t clilen_arg)
{
int i;
const int on = 1;
sigset_t zeromask, newmask, oldmask;
sockfd = sockfd_arg;
clilen = clilen_arg;
//allocate the space of circul queue
for(i = 0; i < QSIZE; ++i){
dg[i].dg_data = malloc(MAXDG);
dg[i].dg_len = 0;
dg[i].dg_sa = malloc(clilen);
dg[i].dg_salen = clilen;
}
for(i = 0; i<= QSIZE; ++i)
cntread[i] = 0;
iget = iput = nqueue = 0;
signal(SIGHUP, sig_hup);
signal(SIGIO, sig_io);
fcntl(sockfd, F_SETOWN, getpid());// 设置套接字属主
ioctl(sockfd, FIOASYNC, &on); //套接字设置成信号驱动IO
ioctl(sockfd, FIONBIO, &on);
sigemptyset(&zeromask); // init three signal sets
sigemptyset(&newmask);
sigemptyset(&oldmask);
sigaddset(&newmask, SIGIO); //signal we want to block
sigprocmask(SIG_BLOCK, &newmask, &oldmask);//堵塞信号
for(;;){
while(nqueue == 0)
sigsuspend(&zeromask);//在此期间, 等待SIGIO信号,该函数在信号处理函数结束后但会
sigprocmask(SIG_SETMASK, &oldmask, NULL);// 接触SIGIO的信号堵塞, 禁用SIG_UNBLOCK命令接触信号堵塞
sendto(sockfd, dg[iget].dg_data, dg[iget].dg_len, 0, dg[iget].dg_sa, dg[iget].dg_salen);
if(++iget >= QSIZE) // iget表示输出数据指针, 越界是iget指向0, 体现了该循环缓冲区的性质
iget = 0;
/*block SIGIO*/
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){//缓冲区数据已满, 若在来数据将会丢失数据
fprintf(stderr, "receive overflow\n");
break;
}
ptr = &dg[iput];
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;
else{
fprintf(stderr, "recvfrom error\n");
exit(0);
}
}
ptr->dg_len = len;
nread++;
nqueue++;
if(++iput >= QSIZE)
iput = 0;
}
cntread[nread]++;
}
static void sig_hup(int signo)
{
int i = 0;
int sum = 0;
for(i = 0; i <= QSIZE; ++i){
fprintf(stderr, "cntread[%d] = %ld\n", i, cntread[i]);
sum += i * cntread[i];
}
printf("sum = %d\n", sum);
exit(0);
}
注意事项:
1、sigsuspend函数, 将信号屏蔽字掩码设置为参数的掩码, 若为空, 表示不堵塞任何信号, 睡眠并且等待系统递交信号, 在进入信号处理函数的时候, 该信号屏蔽字恢复函数调用之前, 信号处理函数执行完毕后, 该函数才返回。
2、nqueue表示应用程序缓冲区, 已经存入的数据报数量
运行方案
客服端程序, 想服务器发送20000个大小为1400B的数据包, 因为可能考虑到服务器的运行速度那么客户端每次发送一个数据报后休息1us,
运行结果
三、总结
1、熟记udp的信号驱动模型
2、了解SIGIO信号产生的原因
3、了解sigsuspend函数原理
4、判断其中的竞争条件。
5、了解F_SETOWN O_ASYNC FIOASYNC的应用
6、sigprocmask函数了解,熟记SIG_BLOCK SIG_SETMASK SIG_UNBLOCK(
慎用)