专题 17 SOCKET并发程序设计

  1. 非阻塞并发模型

函数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));
/*------------------------其他业务逻辑代码-----------------------------------------------------*/
}
}


  1. 信号驱动并发模型

在一个套接字上实现信号驱动的步骤如下:

  • 为信号SIGIO设计处理函数并捕获信号。

voidfunc(int sig) /*信号处理函数*/

{

…………………….

signal(SIGIO,func);

}

…………………………

  • 设定套接字的归属主,设置接收SIGIO信号的进程或进程组。

一般情况下,可以通过函数fcntlF_SETOWN命令设置,比如:

fcntl(nSock,F_SETOWN, getpid()); /*设置接收信号的进程*/

或者

fcntl(nSock,F_SETOWN, 0 – getpgrp());/*设置接收信号的进程组*/

signal(SIGIO,func); /*捕获信号SIGIO*/

  • 设置套接字的异步I/O属性。

一般情况下,可以通过函数fcntlF_SETFL命令设置,比如:

intnType;

nType= fcntl(nSock, F_GETFL, 0);

fcntl(sock,F_SETFL, nType | O_ASYNC);


信号驱动模型主要应用于以下两个方面:

  1. UDP服务器端进程。进程在接收到信号SIGIO后,直接调用函数recvfrom即可,比如网络时间协议NTP(NetworkTime Protocol)服务器。

  2. TCP服务端套接字侦听程序。由于侦听套接字一般不涉及到数据输入输出,故当进程接收到信号SIGIO时,一般意味着客户端发起了连接申请,此时直接调用函数accept即可。

  1. 超时并发模型

可以通过套接字选项SO_SNDTIMEOSO_RCVTIMEO分别设置套接字描述符上的写入和读取操作的超时时间。但本方法有如下缺陷:

  1. 只能设置套接字描述符上的读取和写入操作的超时时间,不能设置connectaccept等连接操作的超时。

  2. 某些版本的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. 多路复用并发模型

步骤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. 多进程并发模型

  1. 不固定进程数的并发模型

流程:

步骤1:创建侦听套接字nLisSock(socket, bind, listen);

步骤2:进程后台运行

步骤3:等待客户端的连接申请(accept),创建与客户端的通信连接nSock

步骤4:创建子进程

步骤5:父进程关闭套接字nSock,此时由于子进程仍然打开了引套接字,故父进程的关闭操作并不真正断开连接,只是把连接数减小1.]

步骤6:父进程回到步骤(3)继续进行

步骤7:子进程关闭侦听套接字nLisSock,由于父进程仍然打开着侦听套接字,故实际上此套接字仅仅把连接数减小1,并不真正关闭它。

步骤8:子进程执行recvsend等操作与客户端进行数据交换

步骤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. 固定进程数的并发模型

父进程流程:

步骤1:创建侦听套接字nLisSock(socket,bind, listen);

步骤2:进程后台运行

步骤3:创建子进程

步骤4wait子进程,如果有子进程退出,则立即创建地子进程,保证子进程在数量上不变。

子进程流程:

步骤1:等待客户端的连接申请,并与客户端建立了通信套接字nSock连接

步骤2:执行recvsend待操作与客户端进行数据交换

步骤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);

}




你可能感兴趣的:(socket,unix,tcp,服务器,null,Signal)