非阻塞并发模型
函数fcntl设置套接字描述符的O_NONBLOCK标志后,即可将I/O方式更改为非阻塞方式。此时函数read,recv,recvfrom,write, send以及accept在调用不成功后立即返回。
设置套接字描述符nSock设置为非阻塞方式:
int nType;
nType=fcntl(nSock,F_GETFL,0);
fcntl(nSock,F_SETFL,nType|O_NONBLOCK);
设置阻塞套接字程序设计的基本流程:
非阻塞套接字的程序一般包含一段循环代码,在循环代码中采用轮询的方式,分别调用套接字的输入、输出、申请连接或连接处理函数,从而达到并发处理多个套接字的目的。
/**************************************************************************/ /********************套接字的非阻塞并发实例********************************/ /**************************************************************************/ //定义设置非阻塞模式宏 #define Fsetnonblock(a) \ {\ nType = fcntl(a, F_GETFL, 0);\ fcntl(nSock, F_SETFL, nType | O_NONBLOCK);\ } /***************定义变量区*******************************/ int nLisSock;//侦听套接字 int I, n=0, nSoc,kVar[MAX];//连接套接字描述符数组 int nType; char buf[1024]; /*********************************************************/ CreateSock(&nLisSock, PORT, MAX);//创建侦听套接字 Fsetnonblock(nLisSock); //设置侦听套接为非阻塞方式 /*********************************************************/ while(1) { if((nSockVar[n] = accept(nLisSock, NULL, NULL)) > 0) { Fsetnonblock(nSockVar[n++]);//设置连接套接字为非阻塞方式 } /---------------------------------------------------------------------------------------------/ for(I =0; i < n; i++) { /*--------------------遍历每一个连接套接字,并从客户端套接中读入数据*****/ read(nSockVar[i], buf, sizeof(buf)); /*------------------------其他业务逻辑代码-----------------------------------------------------*/ } }
信号驱动并发模型
在一个套接字上实现信号驱动的步骤如下:
为信号SIGIO设计处理函数并捕获信号。
voidfunc(int sig) /*信号处理函数*/
{
…………………….
signal(SIGIO,func);
}
…………………………
设定套接字的归属主,设置接收SIGIO信号的进程或进程组。
一般情况下,可以通过函数fcntl的F_SETOWN命令设置,比如:
fcntl(nSock,F_SETOWN, getpid()); /*设置接收信号的进程*/
或者
fcntl(nSock,F_SETOWN, 0 – getpgrp());/*设置接收信号的进程组*/
signal(SIGIO,func); /*捕获信号SIGIO*/
设置套接字的异步I/O属性。
一般情况下,可以通过函数fcntl的F_SETFL命令设置,比如:
intnType;
nType= fcntl(nSock, F_GETFL, 0);
fcntl(sock,F_SETFL, nType | O_ASYNC);
信号驱动模型主要应用于以下两个方面:
UDP服务器端进程。进程在接收到信号SIGIO后,直接调用函数recvfrom即可,比如网络时间协议NTP(NetworkTime Protocol)服务器。
TCP服务端套接字侦听程序。由于侦听套接字一般不涉及到数据输入输出,故当进程接收到信号SIGIO时,一般意味着客户端发起了连接申请,此时直接调用函数accept即可。
超时并发模型
可以通过套接字选项SO_SNDTIMEO和SO_RCVTIMEO分别设置套接字描述符上的写入和读取操作的超时时间。但本方法有如下缺陷:
只能设置套接字描述符上的读取和写入操作的超时时间,不能设置connect和accept等连接操作的超时。
某些版本的UNIX不支持这两个套接字选项。
还可以通过信号SIGALRM设置超时。
PS:在UNIX下的阻塞函数一般在进程接收到任意信号后中断返回。
步骤1:定义超时标志。
staticint nTimeOut = 0;
0表示不超时,1表示超时。
步骤2:为信号SIGALRM设计处理函数。
voidOnTimeOut(int nSignal)
{
signal(nSignal,SIG_IGN);
nTimeOut= 1;
return;
}
步骤3:捕获信号SIGALRM并设置定时器。
signal(SIGALARM,OnTimeOut); /*捕获信号SIGALRM*/
alarm(TIMEOUT);
步骤4:调用套接字阻塞处理函数,比如(recv,read, recvfrom, send, write, connect, accept)
步骤5:取消定时器并忽略信号SIGALRM。
alarm(0);
signal(SIGALRM,SIG_IGN);
步骤6:函数返回,超时判断。
if(ntimOut== 1)函数超时
通过信号SIGALRM与跳转设置超时
步骤1:定义超时标志和跳转结构。
staticint nTimeOut = 0;
jmp_buf env;
步骤2:为信号SIGALRM设置处理函数。
voidOnTimeOut(int nSignal)
{
signal(nSignal,SIG_IGN);/*超时一次后就忽略,防止循环超时*/
nTimeOut= 1; /*设置超时标志*/
longjmp(env,1); /*语句跳转*/
return;
}
步骤3:记录跳转点
setjmp(env);
步骤4:超时判断
步骤5:捕获信号SIGALRM并设置定时器。
步骤6:调用套接字阻塞处理函数,比如套接字接收等函数。
步骤7:取消定时器并忽略信号SIGALRM
****************套接字连接超时实例****************/ static int nTimeOut = 0; jmp_buf env; void OnTimeOut(int nSignal) { signal(nSignal, SIG_IGN); nTimeOut = 1; longjmp(env, 1); return; } int main(int argc, char *argv[]) { int nSock = -1, ret; if(argc != 3) return 1; nTimeOut = 0; setjmp(env); //记录跳转点 if(nTimeOut == 1) //超时判断 printf(“Connect Timeout. \n”); else { signal(SIGALRM, OnTimeOut); //注册捕获SIGALRM信号函数 alarm(10); //设置超时时间 ret = ConncectSock(&nSock, atoi(argv[2], argv[1]); //执行套接字连接 alarm(0); //取消定时器 signal(SIGALRM, SIG_IGN); //忽略信号SIGALRM if(ret == 0) //非超时处理 printf(“Connect Success.\n”); else printf(“Connect Error!\n”); } iff(nSock != -1) close(nSock); //关闭套接字 return 0; }
多路复用并发模型
步骤1:创建套接字文件描述符集合。
fd_setfdset; /*文件描述符集合*/
FD_ZERO(&fdset); /*清空集合中的元素*/
FD_SET(nLisSock,&fdset); /*监控侦听套接字*/
FD_SET(nSockVar,&fdset); /*监视连接套接字*/
步骤2:准备超时时间。
structtimeval wait;
wait.tv_sec=0; /*定义超时时间0.1秒*/
wait.tv_usec=100000;
步骤3:调用函数select并检测应答结果。
intret;
//假设MAXSOCK是集合fdset中最大的描述符
ret= select(MAXSOCK + 1, &fdset, NULL, NULL, &wait); if(ret ==0) …
/*超时*/
elseif(ret == -1) …. /*错误*/
else… /*产生了套接字连接或数据发送请求*/
步骤4:检测给套接字文件描述符的状态并处理之。
如果是侦测套接字,其操作流程一般为:
if(FD_ISSET(nLisSock,&fdset))//侦听套接字,处理连接申请
{
if((nSockVar= accept(nLisSock, NULL, NULL)) > 0)
….
}
如果是连接套接字操作,其流程一般为:
if(FD_ISSET(nSockVar,&fdset))
{
//连接套接字,读取传输数据
read(nSockVar[i],buf, sizeof(buf));
…………..
}
多进程并发模型
不固定进程数的并发模型
流程:
步骤1:创建侦听套接字nLisSock(socket, bind, listen);
步骤2:进程后台运行
步骤3:等待客户端的连接申请(accept),创建与客户端的通信连接nSock
步骤4:创建子进程
步骤5:父进程关闭套接字nSock,此时由于子进程仍然打开了引套接字,故父进程的关闭操作并不真正断开连接,只是把连接数减小1.]
步骤6:父进程回到步骤(3)继续进行
步骤7:子进程关闭侦听套接字nLisSock,由于父进程仍然打开着侦听套接字,故实际上此套接字仅仅把连接数减小1,并不真正关闭它。
步骤8:子进程执行recv和send等操作与客户端进行数据交换
步骤9:数据交换完毕,关闭套接字,子进程结束。
代码模板:
intnLisSock, nSock;/*侦听套接字和连接套接字描述符*/
pid_tnChild;
/*-----------------------------父进程-------------------------------------*/
CreateSock(&nLisSock,PORT, MAX);//创建侦听套接
InitServer(); //进程转后台运行
while(1)
{
ASSERT(AcceptSock(&nSock,nLisSock) == 0);//创建连接套接字
VERIFY((nChild= fork()) >=0); //创建子进程
if(nChild== 0) break; //子进程跳转到子进程代码
close(nSock); //父进程关闭连接套接字
}
close(nLisSock); //子进程关闭侦听套接字
/*------------------------数据通信开始-------------------*/
………………………….
/*-----------------------数据通信结束-------------------*/
close(nSock);
固定进程数的并发模型
父进程流程:
步骤1:创建侦听套接字nLisSock(socket,bind, listen);
步骤2:进程后台运行
步骤3:创建子进程
步骤4:wait子进程,如果有子进程退出,则立即创建地子进程,保证子进程在数量上不变。
子进程流程:
步骤1:等待客户端的连接申请,并与客户端建立了通信套接字nSock连接
步骤2:执行recv和send待操作与客户端进行数据交换
步骤3:数据交换完毕,关闭套接字nSock
步骤4:回到步骤(1),继续执行。
代码模板:
inti, bShutdown = 0;
intnLisSock, nSock;
pid_t_pid_t, nChild;
CreateSock(&nLisSock,PORT, MAX);
InitServer();
for(i=0; i < MAXNUMBER; i++)
{
VERIFY((nChild= fork()) >= 0);
if(nChild== 0) break;
}
if(nChild> 0)
{
while(!bShutdown)
{
_pid_t= wait(NULL);//一直阻塞,直到捕获子进程结束
if(_pid_t< 0)
{
PrintLog(stderr,“wait error %d %s”, errno, strerror(errno));
continue;
}
PrintLog(stderr,“catch a process %d end %d.”, _pid_t, nState);
VERIFY((nChild= fork()) >= 0);
if(nChild== 0) break;
}
exit(0);
}
while(1)
{
ASSERT(AcceptSock(&nSock,nLisSock) == 0);
/*----------------------数据通信开始--------------------*/
…………………………………………
/*--------------------------数据通信结束--------------------*/
close(nSock);
}